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 --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 # Ad-hoc re-sign the whole bundle in one pass. # `flutter build macos --release` ad-hoc signs the main binary, but # FlutterMacOS.framework already carries its own ad-hoc signature # from Flutter's engine artifacts. dyld on Apple Silicon (macOS 13+) # enforces Team ID match between the main process and every loaded # framework -- two ad-hoc signatures from different signing passes # have different per-binary cdhashes and fail the check, producing # `mapping process and mapped file have different Team IDs` at # launch time on M-series Macs. `codesign --deep --sign -` re-signs # every nested binary/framework/dylib with the same ad-hoc identity # in one pass, so all components share a consistent signing context. # When we wire up real Developer ID + notarization later, replace # `-` with the cert identity and drop --deep in favor of inside-out # signing. codesign --force --deep --sign - \ ./flutter/build/macos/Build/Products/Release/RustDesk.app codesign --verify --deep --strict --verbose=2 \ ./flutter/build/macos/Build/Products/Release/RustDesk.app mkdir -p ./SignOutput # Use hdiutil (not create-dmg) because the runner is a LaunchDaemon # with no GUI/Finder session. create-dmg drives Finder via AppleScript # for icon layout and fails with `-10810` in daemon context. hdiutil # produces a fully functional compressed DMG with no GUI calls. dmg_staging="$(mktemp -d -t rustdesk-dmg)" cp -R ./flutter/build/macos/Build/Products/Release/RustDesk.app "$dmg_staging/" ln -s /Applications "$dmg_staging/Applications" hdiutil create \ -volname "RustDesk" \ -srcfolder "$dmg_staging" \ -ov \ -format UDZO \ "./SignOutput/rustdesk-${VERSION_DISPLAY}-x86_64.dmg" rm -rf "$dmg_staging" - 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