ci(macos): add macos build
build-linux / build-linux-x64 (push) Successful in 5m3s
build-windows / build-windows-x64 (push) Successful in 9m54s
build-macos / build-macos-x64 (push) Failing after 11m5s

This commit is contained in:
2026-05-05 23:49:00 +02:00
parent 463d56e1e4
commit 27ea622e7b
2 changed files with 503 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
name: build-macos
on:
push:
branches: [pro-features]
workflow_dispatch:
inputs:
version_suffix:
description: "Version suffix (e.g. 'cst', 'beta1'). Empty = vanilla."
type: string
default: "cst"
env:
RUST_VERSION: "1.81" # MAC_RUST_VERSION upstream (cidre needs >=1.81)
FLUTTER_VERSION: "3.24.5"
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
CARGO_EXPAND_VERSION: "1.0.95"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
VERSION_BASE: "1.4.6"
VERSION_SUFFIX: ${{ inputs.version_suffix || 'cst' }}
jobs:
build-x64:
name: build-macos-x64
# Intel Mac runner. provision.sh tags x86_64 macOS hosts with the X64 label.
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 240
env:
VCPKG_ROOT: /opt/vcpkg
VCPKG_BINARY_SOURCES: "clear;files,/var/cache/vcpkg,readwrite"
VCPKG_DEFAULT_TRIPLET: x64-osx
VCPKG_DEFAULT_HOST_TRIPLET: x64-osx
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Verify host toolchain
shell: bash
run: |
required=(git bash python3 rustc cargo rustup clang flutter cmake ninja nasm pkg-config create-dmg)
missing=()
for t in "${required[@]}"; do
if command -v "$t" >/dev/null 2>&1; then
printf '%-15s %s\n' "$t" "$(command -v "$t")"
else
missing+=("$t")
printf '%-15s MISSING\n' "$t"
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Missing tools: ${missing[*]}. Re-run provision.sh on the runner host."
exit 1
fi
[[ -d "$VCPKG_ROOT" && -x "$VCPKG_ROOT/vcpkg" ]] || {
echo "VCPKG_ROOT=$VCPKG_ROOT invalid"; exit 1; }
- name: Compute version strings
shell: bash
run: |
base="${VERSION_BASE}"
suffix="${VERSION_SUFFIX}"
[[ "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "VERSION_BASE '$base' must be major.minor.patch"; exit 1; }
if [[ -n "$suffix" ]]; then display="${base}-${suffix}"; else display="${base}"; fi
echo "Display : $display"
echo "VERSION_DISPLAY=$display" >> "$GITHUB_ENV"
- name: Patch Cargo.toml with display version
shell: bash
run: |
# BSD sed (macOS): -i requires an empty backup-suffix arg.
sed -i '' -E "1,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/;}" Cargo.toml
grep '^version' Cargo.toml | head -1
# No deployment-target patch on x86_64: build.py's build_flutter_dmg()
# already exports MACOSX_DEPLOYMENT_TARGET=10.14 internally, matching the
# Flutter Xcode project. Upstream only patches the target for arm64
# (which needs 12.3+).
- name: Ensure Rust toolchain configured
shell: bash
run: |
rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
rustup default "$RUST_VERSION"
rustup target add x86_64-apple-darwin
rustc --version
cargo --version
- name: Install flutter_rust_bridge codegen tools
shell: bash
run: |
tools=/opt/cargo-tools
mkdir -p "$tools/bin"
cargo install --root "$tools" cargo-expand --version "$CARGO_EXPAND_VERSION" --locked
cargo install --root "$tools" flutter_rust_bridge_codegen --version "$FLUTTER_RUST_BRIDGE_VERSION" --features uuid --locked
ls -la "$tools/bin"
[[ -x "$tools/bin/flutter_rust_bridge_codegen" ]] || { echo "missing fr_bridge_codegen"; exit 1; }
echo "$tools/bin" >> "$GITHUB_PATH"
- name: Generate Rust <-> Dart bridge (with Flutter 3.22.3)
shell: bash
run: |
set -e
# Same dual-SDK trick as Linux: bridge codegen 1.80.1 produces broken
# Dart on Flutter 3.24.5; provision.sh installs 3.22.3 at
# /opt/flutter-bridge for codegen only. Switch back to 3.24.5 after.
export PATH="/opt/cargo-tools/bin:/opt/flutter-bridge/bin:$PATH"
flutter --version
command -v flutter_rust_bridge_codegen
# Match the Linux fix: don't pass --llvm-path, let ffigen find Xcode
# CLT's libclang via system defaults. Passing brew's llvm or a custom
# libclang path triggered fr_bridge_codegen 1.80.1's bad-emission bug
# (stray `typedef bool = NativeFunction<...>`) on Linux; same logic
# carries over here.
unset LIBCLANG_PATH
(cd flutter && sed -i '' -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml)
(cd flutter && flutter pub get)
flutter_rust_bridge_codegen \
--rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart \
--c-output ./flutter/macos/Runner/bridge_generated.h
cp ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h
(cd flutter && git checkout -- pubspec.yaml)
(cd flutter && /opt/flutter/bin/flutter pub get)
- name: Diagnose generated bridge files
shell: bash
run: |
set +e
echo "============================================================"
echo " generated_bridge.dart (first 80 lines)"
echo "============================================================"
head -80 flutter/lib/generated_bridge.dart 2>/dev/null || echo "(missing)"
echo
echo "============================================================"
echo " Search: typedef declarations that might shadow bool/Int/Pointer"
echo "============================================================"
grep -nE 'typedef (bool|Int|Pointer|Bool)' \
flutter/lib/generated_bridge.dart \
flutter/lib/generated_bridge.freezed.dart 2>/dev/null || echo "(no shadowing typedefs)"
- name: vcpkg install dependencies (x64-osx)
shell: bash
run: |
mkdir -p /var/cache/vcpkg
if ! "$VCPKG_ROOT/vcpkg" install \
--triplet x64-osx \
--x-install-root="$VCPKG_ROOT/installed"; then
find "$VCPKG_ROOT/" -name '*.log' -exec sh -c 'echo "===== {} ====="; cat "{}"' \;
exit 1
fi
- name: Build RustDesk (.app + .dmg)
shell: bash
run: |
set -e
# build.py -> build_flutter_dmg() does:
# - MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features <features> --release
# - cp liblibrustdesk.dylib librustdesk.dylib
# - flutter build macos --release
# - cp -rf ../target/release/service .../RustDesk.app/Contents/MacOS/
# It does NOT package the .dmg (the create-dmg call is commented out).
# We package below.
#
# No --screencapturekit on x86_64: upstream's matrix only enables it
# on aarch64 (cidre's ScreenCaptureKit bindings target arm64-only APIs).
python3 build.py --flutter --hwcodec --unix-file-copy-paste
mkdir -p ./SignOutput
# Patch create-dmg's unmount-attempts upward (CI runners are flaky).
CREATE_DMG="$(command -v create-dmg)"
CREATE_DMG_REAL="$(readlink "$CREATE_DMG" 2>/dev/null || echo "$CREATE_DMG")"
# readlink may return relative; resolve.
[[ "$CREATE_DMG_REAL" == /* ]] || CREATE_DMG_REAL="$(cd "$(dirname "$CREATE_DMG")" && pwd)/$CREATE_DMG_REAL"
sed -i '' -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG_REAL" || true
create-dmg \
--icon "RustDesk.app" 200 190 \
--hide-extension "RustDesk.app" \
--window-size 800 400 \
--app-drop-link 600 185 \
"./SignOutput/rustdesk-${VERSION_DISPLAY}-x86_64.dmg" \
./flutter/build/macos/Build/Products/Release/RustDesk.app
- name: Report signing status of build artifacts
shell: bash
run: |
for f in ./SignOutput/*.dmg; do
[[ -f "$f" ]] || continue
size=$(stat -f%z "$f")
sig=$(codesign -dv "$f" 2>&1 | grep -E '^Authority' | head -1 || true)
if [[ -z "$sig" ]]; then
printf '[UNSIGNED] %s (%d bytes)\n' "$(basename "$f")" "$size"
else
printf '[ SIGNED ] %s (%d bytes) %s\n' "$(basename "$f")" "$size" "$sig"
fi
done
echo "::warning title=Unsigned .dmg::Wire up codesign + notarytool before distributing."
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: rustdesk-macos-x64-${{ github.sha }}
path: SignOutput/rustdesk-*.dmg
if-no-files-found: warn
retention-days: 14
+291
View File
@@ -0,0 +1,291 @@
#!/usr/bin/env bash
# Provisions a macOS host (Apple Silicon, macOS 14+) as a Gitea Actions runner
# for RustDesk desktop (.dmg) builds. Idempotent: safe to re-run.
#
# Versions are pinned to .gitea/workflows/build-macos.yml. Bump them there and
# here together.
#
# Usage:
# sudo ./provision.sh \
# --gitea-url https://gitea.example.com \
# --runner-token <token>
#
# Toolchains land in /opt/* (chowned to the runner user). Service is installed
# as a LaunchDaemon running as that user.
set -euo pipefail
# ---- pinned versions (mirror .gitea/workflows/build-macos.yml env block) ----
RUST_VERSION="1.81.0" # MAC_RUST_VERSION upstream (cidre crate needs >=1.81)
FLUTTER_VERSION="3.24.5" # used for `flutter build macos`
FLUTTER_BRIDGE_VERSION="3.22.3" # used for `flutter pub get` + flutter_rust_bridge_codegen
VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b"
NASM_VERSION="2.16.03" # 3.x has incompatible CLI; aom/dav1d need 2.x
RUNNER_VERSION="0.2.11"
# ---- defaults ----
RUNNER_NAME="$(hostname -s)-rustdesk"
RUNNER_LABELS=""
SERVICE_USER="gitea-runner"
GITEA_URL=""
RUNNER_TOKEN=""
# ---- arg parse ----
while [[ $# -gt 0 ]]; do
case "$1" in
--gitea-url) GITEA_URL="$2"; shift 2 ;;
--runner-token) RUNNER_TOKEN="$2"; shift 2 ;;
--runner-name) RUNNER_NAME="$2"; shift 2 ;;
--runner-labels) RUNNER_LABELS="$2"; shift 2 ;;
--service-user) SERVICE_USER="$2"; shift 2 ;;
-h|--help)
sed -n '2,15p' "$0"
exit 0 ;;
*) echo "Unknown arg: $1" >&2; exit 2 ;;
esac
done
[[ "$EUID" -eq 0 ]] || { echo "Run as root (use sudo)." >&2; exit 1; }
[[ -n "$GITEA_URL" && -n "$RUNNER_TOKEN" ]] \
|| { echo "Missing --gitea-url or --runner-token" >&2; exit 2; }
# ---- arch + macOS version detection ----
ARCH="$(uname -m)"
case "$ARCH" in
arm64) HOMEBREW_PREFIX="/opt/homebrew"; ARCH_LABEL="ARM64" ;;
x86_64) HOMEBREW_PREFIX="/usr/local"; ARCH_LABEL="X64" ;;
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
esac
OS_MAJOR="$(sw_vers -productVersion | cut -d. -f1)"
[[ "$OS_MAJOR" -ge 14 ]] || {
echo "WARNING: tested only on macOS 14+. You're on $(sw_vers -productVersion)."
sleep 3
}
DISTRO_LABEL="macos-${OS_MAJOR}"
if [[ -z "$RUNNER_LABELS" ]]; then
RUNNER_LABELS="${DISTRO_LABEL},self-hosted,${ARCH_LABEL},macOS"
fi
log() { printf '\n==> %s\n' "$*"; }
# ---- 1. Xcode Command Line Tools ----
log "Verifying Xcode Command Line Tools"
if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
echo "Xcode Command Line Tools not installed. Run:" >&2
echo " xcode-select --install" >&2
echo "Then re-run this script." >&2
exit 1
fi
echo " $(xcode-select -p)"
# ---- 2. Homebrew (machine-wide) ----
# Homebrew refuses to install under root (its installer aborts with
# "Don't run this as root!"). It must be installed manually by a regular
# user before this script runs.
log "Verifying Homebrew"
if [[ ! -x "$HOMEBREW_PREFIX/bin/brew" ]]; then
echo "Homebrew not installed at $HOMEBREW_PREFIX." >&2
echo "Install it as your regular user (NOT root), then re-run this script:" >&2
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" >&2
exit 1
fi
export PATH="$HOMEBREW_PREFIX/bin:$PATH"
echo " $(brew --version | head -1)"
# brew install must also run as a non-root user. Determine which user invoked
# sudo so we can drop privileges for brew commands below.
BREW_USER="${SUDO_USER:-}"
if [[ -z "$BREW_USER" || "$BREW_USER" == "root" ]]; then
echo "Could not determine the non-root user that ran sudo (SUDO_USER unset)." >&2
echo "Re-run with: sudo ./provision.sh ..." >&2
exit 1
fi
brew_as_user() { sudo -u "$BREW_USER" -H "$HOMEBREW_PREFIX/bin/brew" "$@"; }
# ---- 3. brew packages ----
log "Installing brew packages"
brew_pkgs=(llvm create-dmg pkg-config cmake ninja yasm autoconf automake libtool wget)
for p in "${brew_pkgs[@]}"; do
if brew_as_user list --versions "$p" >/dev/null 2>&1; then
echo " $p (already installed)"
else
brew_as_user install "$p"
fi
done
# ---- 4. NASM 2.16.x (NOT brew's nasm 3.x; aom/dav1d need 2.x) ----
if ! /usr/local/bin/nasm --version 2>/dev/null | grep -q "version $NASM_VERSION"; then
log "Installing NASM $NASM_VERSION"
tmp="$(mktemp -d)"
curl -fsSL -o "$tmp/nasm.zip" \
"https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/macosx/nasm-${NASM_VERSION}-macosx.zip"
unzip -q "$tmp/nasm.zip" -d "$tmp"
install -m 0755 "$tmp/nasm-${NASM_VERSION}/nasm" /usr/local/bin/nasm
rm -rf "$tmp"
fi
/usr/local/bin/nasm --version | head -1
# ---- 5. dedicated runner user ----
if ! /usr/bin/id -u "$SERVICE_USER" >/dev/null 2>&1; then
log "Creating user $SERVICE_USER"
# Find an unused UID >= 600
uid=600
while dscl . -list /Users UniqueID | awk '{print $2}' | grep -qx "$uid"; do
uid=$((uid + 1))
done
dscl . -create "/Users/$SERVICE_USER"
dscl . -create "/Users/$SERVICE_USER" UserShell /bin/bash
dscl . -create "/Users/$SERVICE_USER" RealName "Gitea Runner"
dscl . -create "/Users/$SERVICE_USER" UniqueID "$uid"
dscl . -create "/Users/$SERVICE_USER" PrimaryGroupID 20
dscl . -create "/Users/$SERVICE_USER" NFSHomeDirectory "/Users/$SERVICE_USER"
mkdir -p "/Users/$SERVICE_USER"
chown "$SERVICE_USER:staff" "/Users/$SERVICE_USER"
fi
RUNNER_HOME="/Users/$SERVICE_USER"
# ---- 6. Rust (machine-wide) ----
export RUSTUP_HOME=/opt/rustup
export CARGO_HOME=/opt/cargo
mkdir -p "$RUSTUP_HOME" "$CARGO_HOME"
if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then
log "Installing rustup at $RUSTUP_HOME / $CARGO_HOME"
curl -fsSL https://sh.rustup.rs | RUSTUP_HOME="$RUSTUP_HOME" CARGO_HOME="$CARGO_HOME" \
sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
fi
"$CARGO_HOME/bin/rustup" toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
"$CARGO_HOME/bin/rustup" target add --toolchain "$RUST_VERSION" aarch64-apple-darwin x86_64-apple-darwin
"$CARGO_HOME/bin/rustup" default "$RUST_VERSION"
# ---- 7. Flutter (two SDKs: 3.24.5 for build, 3.22.3 for bridge gen) ----
# Same rationale as Linux: bridge codegen 1.80.1 + freezed produces broken Dart
# under newer Flutter. Run codegen under 3.22.3, build under 3.24.5.
install_flutter() {
local ver="$1" dir="$2"
if [[ ! -x "$dir/bin/flutter" ]]; then
log "Installing Flutter $ver -> $dir"
local tmp; tmp="$(mktemp -d)"
local parent; parent="$(dirname "$dir")"
# Flutter URL pattern differs between archs: Apple Silicon has an
# `_arm64_` infix, Intel has no arch infix at all.
local flutter_url_base="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos"
local flutter_url
case "$ARCH" in
arm64) flutter_url="${flutter_url_base}_arm64_${ver}-stable.zip" ;;
x86_64) flutter_url="${flutter_url_base}_${ver}-stable.zip" ;;
esac
curl -fsSL -o "$tmp/flutter.zip" "$flutter_url"
mkdir -p "$parent"
unzip -q "$tmp/flutter.zip" -d "$tmp"
mv "$tmp/flutter" "$dir"
rm -rf "$tmp"
fi
"$dir/bin/flutter" config --no-analytics >/dev/null
"$dir/bin/flutter" precache --macos >/dev/null
}
install_flutter "$FLUTTER_VERSION" /opt/flutter
install_flutter "$FLUTTER_BRIDGE_VERSION" /opt/flutter-bridge
# ---- 8. vcpkg ----
VCPKG_DIR=/opt/vcpkg
if [[ ! -d "$VCPKG_DIR/.git" ]]; then
log "Cloning vcpkg"
git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR"
fi
git -C "$VCPKG_DIR" fetch --tags origin
git -C "$VCPKG_DIR" -c advice.detachedHead=false checkout "$VCPKG_COMMIT"
[[ -x "$VCPKG_DIR/vcpkg" ]] || "$VCPKG_DIR/bootstrap-vcpkg.sh" -disableMetrics
mkdir -p /var/cache/vcpkg
chown -R "$SERVICE_USER:staff" /var/cache/vcpkg
# ---- 9. Permissions ----
log "Setting up permissions for $SERVICE_USER"
chown -R "$SERVICE_USER:staff" "$CARGO_HOME" "$RUSTUP_HOME" \
/opt/flutter /opt/flutter-bridge "$VCPKG_DIR"
mkdir -p /opt/cargo-tools
chown -R "$SERVICE_USER:staff" /opt/cargo-tools
git config --system --add safe.directory '*' || true
# ---- 10. act_runner ----
RUNNER_DIR="/usr/local/var/gitea-runner"
mkdir -p "$RUNNER_DIR"
chown -R "$SERVICE_USER:staff" "$RUNNER_DIR"
if [[ ! -x "$RUNNER_DIR/act_runner" ]]; then
log "Downloading act_runner $RUNNER_VERSION"
case "$ARCH" in
arm64) rarch="arm64" ;;
x86_64) rarch="amd64" ;;
esac
curl -fsSL -o "$RUNNER_DIR/act_runner" \
"https://gitea.com/gitea/act_runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-darwin-${rarch}"
chmod +x "$RUNNER_DIR/act_runner"
chown "$SERVICE_USER:staff" "$RUNNER_DIR/act_runner"
fi
if [[ ! -f "$RUNNER_DIR/.runner" ]]; then
log "Registering runner with $GITEA_URL"
sudo -u "$SERVICE_USER" -H bash -c "
cd '$RUNNER_DIR' && \
./act_runner register --no-interactive \
--instance '$GITEA_URL' \
--token '$RUNNER_TOKEN' \
--name '$RUNNER_NAME' \
--labels '$RUNNER_LABELS'
"
fi
# ---- 11. launchd service ----
log "Installing LaunchDaemon"
PLIST=/Library/LaunchDaemons/com.rustdesk.gitea-runner.plist
cat > "$PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.rustdesk.gitea-runner</string>
<key>UserName</key>
<string>${SERVICE_USER}</string>
<key>WorkingDirectory</key>
<string>${RUNNER_DIR}</string>
<key>ProgramArguments</key>
<array>
<string>${RUNNER_DIR}/act_runner</string>
<string>daemon</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>RUSTUP_HOME</key> <string>${RUSTUP_HOME}</string>
<key>CARGO_HOME</key> <string>${CARGO_HOME}</string>
<key>VCPKG_ROOT</key> <string>${VCPKG_DIR}</string>
<key>HOMEBREW_PREFIX</key> <string>${HOMEBREW_PREFIX}</string>
<key>PATH</key>
<string>${CARGO_HOME}/bin:/opt/flutter/bin:/opt/cargo-tools/bin:${HOMEBREW_PREFIX}/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key><string>${RUNNER_DIR}/stdout.log</string>
<key>StandardErrorPath</key><string>${RUNNER_DIR}/stderr.log</string>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key> <integer>65535</integer>
</dict>
</dict>
</plist>
EOF
chmod 0644 "$PLIST"
launchctl bootout system "$PLIST" 2>/dev/null || true
launchctl bootstrap system "$PLIST"
launchctl enable "system/com.rustdesk.gitea-runner"
log "Done."
echo " Verify with: sudo launchctl print system/com.rustdesk.gitea-runner | head"
echo " Tail logs with: tail -F $RUNNER_DIR/stderr.log"
echo " Runner should appear (online) at $GITEA_URL > Site Admin > Actions > Runners"