Add BUILD.md and res/post-build-mac.sh
- BUILD.md: complete build instructions for macOS desktop (Xcode 26.5, Flutter 3.44, Dart 3.12, VCPKG, all patches explained) - res/post-build-mac.sh: re-sign + install to /Applications + create dmg, run after every 'flutter build macos --release' Key issues documented in BUILD.md: * Re-signing is mandatory (codesign --force --deep -s -) * Use hdiutil for dmg, not create-dmg (macos-alias is x86_64 broken) * libclang must be arm64 (use Xcode CLT, not homebrew llvm) * EXCLUDED_ARCHS=x86_64 for arm64-only Rust dylib
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# Building RustDesk for macOS (Jerry's MacBook Air, Apple Silicon)
|
||||
|
||||
This is a fork of [rustdesk/rustdesk](https://github.com/rustdesk/rustdesk) v1.4.7,
|
||||
patched to build against current macOS tooling (Xcode 26.5, Flutter 3.44, Dart 3.12).
|
||||
|
||||
Last verified build: **2026-06-08**, RustDesk 1.4.7 (arm64), dmg size 27MB.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR (already-done state)
|
||||
|
||||
If this Mac still has everything set up, you can re-build with:
|
||||
|
||||
```bash
|
||||
cd ~/rustdesk-master2
|
||||
export VCPKG_ROOT=~/vcpkg PATH="$VCPKG_ROOT:$PATH" \
|
||||
LIBCLANG_PATH=/Library/Developer/CommandLineTools/usr/lib/libclang.dylib
|
||||
cargo build --release --features flutter
|
||||
flutter build macos --release
|
||||
./res/post-build-mac.sh # re-sign, install to /Applications, create dmg
|
||||
open /Applications/RustDesk.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. One-time environment setup (if starting fresh)
|
||||
|
||||
### 1.1. Xcode (App Store)
|
||||
|
||||
- Install full **Xcode 26.5** from App Store. CommandLineTools alone is not enough —
|
||||
Flutter's `xcodebuild` and `pod install` need the full Xcode.
|
||||
- After install:
|
||||
```bash
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
sudo xcodebuild -license accept
|
||||
sudo xcodebuild -runFirstLaunch # installs CoreSimulator.framework etc.
|
||||
```
|
||||
|
||||
### 1.2. VCPKG (C++ deps: libvpx, libyuv, opus, aom)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/microsoft/vcpkg ~/vcpkg
|
||||
cd ~/vcpkg && ./bootstrap-vcpkg.sh
|
||||
vcpkg install --triplet=arm64-osx libvpx libyuv opus aom
|
||||
```
|
||||
|
||||
Add to `~/.zshrc`:
|
||||
```sh
|
||||
export VCPKG_ROOT="$HOME/vcpkg"
|
||||
export PATH="$VCPKG_ROOT:$PATH"
|
||||
```
|
||||
|
||||
### 1.3. Flutter 3.44+
|
||||
|
||||
Download from https://docs.flutter.dev/get-started/install/macos, then:
|
||||
```sh
|
||||
export FLUTTER_ROOT="$HOME/flutter"
|
||||
export PATH="$FLUTTER_ROOT/bin:$PATH"
|
||||
flutter doctor # should show green checkmarks for Xcode and Chrome
|
||||
```
|
||||
|
||||
### 1.4. CocoaPods (needed by Flutter macOS plugins)
|
||||
|
||||
```bash
|
||||
brew install cocoapods
|
||||
```
|
||||
|
||||
**Note:** On this Mac `homebrew` is **x86_64** (prefix `/usr/local`), not arm64 native. Most
|
||||
things work but see the libclang note below.
|
||||
|
||||
### 1.5. flutter_rust_bridge_codegen 1.80.1
|
||||
|
||||
```bash
|
||||
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Project-specific patches (all in git, see `git log`)
|
||||
|
||||
### 2.1. `flutter/pubspec.yaml`
|
||||
|
||||
```yaml
|
||||
extended_text: ^15.0.2 # was 14.0.0; old missing SelectionHandler methods
|
||||
|
||||
dependency_overrides:
|
||||
google_fonts: 5.0.0 # 6.x breaks with const Map<FontWeight, ...>
|
||||
```
|
||||
|
||||
### 2.2. `flutter/lib/generated_bridge.dart` (codegen output, 2 fixes)
|
||||
|
||||
After running `flutter_rust_bridge_codegen` (see step 3), patch:
|
||||
- `asTypedList(raw.length).setAll(0, raw)` → `cast<ffi.Int32>().asTypedList(...)` and `cast<ffi.Uint8>()` in `api2wire_int_32_list` / `api2wire_uint_8_list`
|
||||
- `typedef DartPort = ffi.Int;` → `typedef DartPort = ffi.Int64;`
|
||||
|
||||
### 2.3. `flutter/lib/common.dart` (4 call sites)
|
||||
|
||||
- `DialogTheme(` → `DialogThemeData(`
|
||||
- `const TabBarTheme(` → `TabBarThemeData(`
|
||||
|
||||
### 2.4. `flutter/macos/Runner/Configs/Release.xcconfig`
|
||||
|
||||
```xcconfig
|
||||
EXCLUDED_ARCHS[sdk=macosx*] = x86_64
|
||||
```
|
||||
|
||||
Without this, release builds fail because Xcode 26.5's `ARCHS_STANDARD` includes x86_64
|
||||
but our Rust dylib is arm64-only.
|
||||
|
||||
### 2.5. `~/.pub-cache/.../google_fonts-5.0.0/lib/src/google_fonts_variant.dart`
|
||||
|
||||
Dart 3.12 rejects `const Map<FontWeight, String>{...}`. Change `const` to `final` on
|
||||
`_fontWeightToFilenameWeightParts`. **This patch is in the global pub cache and will
|
||||
be wiped by `flutter pub cache clean`.** To make it permanent, copy the patched file into
|
||||
`./local_google_fonts/` and use `dependency_overrides: google_fonts: { path: ... }`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Build steps (from a clean checkout)
|
||||
|
||||
```bash
|
||||
cd ~/rustdesk-master2
|
||||
|
||||
# 3.1. Pull hbb_common submodule (only matters if you switch to submodule setup;
|
||||
# we currently have it tracked as a regular directory, see [[git-remote-cardsoon]])
|
||||
# git submodule update --init --recursive
|
||||
|
||||
# 3.2. Environment
|
||||
export VCPKG_ROOT=~/vcpkg
|
||||
export PATH="$VCPKG_ROOT:$PATH"
|
||||
export LIBCLANG_PATH="/Library/Developer/CommandLineTools/usr/lib/libclang.dylib"
|
||||
# ^^^ must be the Xcode CLT one (arm64-compatible), NOT /usr/local/opt/llvm/
|
||||
|
||||
# 3.3. Flutter pub get
|
||||
cd flutter && flutter pub get && cd ..
|
||||
|
||||
# 3.4. Generate FFI bindings (Flutter <-> Rust)
|
||||
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 \
|
||||
--llvm-path /Library/Developer/CommandLineTools/usr/
|
||||
# Then apply the 2 hand-patches in 2.2 above.
|
||||
|
||||
# 3.5. Rust release build
|
||||
cargo build --release --features flutter
|
||||
|
||||
# 3.6. Flutter macOS app build
|
||||
flutter build macos --release
|
||||
|
||||
# 3.7. Re-sign, install, package
|
||||
./res/post-build-mac.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Critical gotchas (learned the hard way)
|
||||
|
||||
### 4.1. Re-sign after every build
|
||||
|
||||
`flutter build macos` produces an `.app` that **does not launch on macOS 26.5**.
|
||||
The main executable and the embedded Flutter plugin frameworks end up with
|
||||
**different ad-hoc Team IDs**, and macOS's strict dyld refuses the mismatch.
|
||||
|
||||
**Always run** after every `flutter build macos`:
|
||||
```bash
|
||||
codesign --force --deep -s - path/to/RustDesk.app
|
||||
```
|
||||
|
||||
The `res/post-build-mac.sh` script does this for you. The `--deep` flag is
|
||||
essential so that all framework bundles inside the .app get re-signed together.
|
||||
|
||||
### 4.2. Use hdiutil for .dmg, not create-dmg
|
||||
|
||||
`create-dmg` (the official RustDesk build tool) depends on Node's `macos-alias`
|
||||
native module, which is x86_64-built and won't load on this arm64 Mac.
|
||||
`npm rebuild` also fails because `macos-alias` uses the outdated `nan@2` N-API
|
||||
shim that's incompatible with Node 18+.
|
||||
|
||||
Use `hdiutil` instead (see `res/post-build-mac.sh`).
|
||||
|
||||
### 4.3. libclang must be arm64
|
||||
|
||||
For `flutter_rust_bridge_codegen` to work, `libclang.dylib` must be loadable.
|
||||
The Homebrew one at `/usr/local/opt/llvm/lib/libclang.dylib` is **x86_64**.
|
||||
The Xcode CLT one at `/Library/Developer/CommandLineTools/usr/lib/libclang.dylib`
|
||||
is **universal** (arm64 + x86_64). Use the latter.
|
||||
|
||||
### 4.4. EXCLUDED_ARCHS for release
|
||||
|
||||
Xcode 26.5's default `ARCHS_STANDARD` includes x86_64, but our Rust dylib
|
||||
is arm64-only. Set `EXCLUDED_ARCHS[sdk=macosx*] = x86_64` in `Release.xcconfig`,
|
||||
or build a universal dylib with `lipo`.
|
||||
|
||||
### 4.5. Debug config doesn't need EXCLUDED_ARCHS
|
||||
|
||||
`flutter build macos --debug` works without the EXCLUDED_ARCHS hack because
|
||||
debug builds use `ONLY_ACTIVE_ARCH=YES` which only targets the host arch.
|
||||
|
||||
---
|
||||
|
||||
## 5. What you get
|
||||
|
||||
After `res/post-build-mac.sh`:
|
||||
|
||||
| File | Location | Size |
|
||||
|------|----------|------|
|
||||
| Release `.app` | `flutter/build/macos/Build/Products/Release/RustDesk.app` | 57 MB |
|
||||
| Installed `.app` | `/Applications/RustDesk.app` | 57 MB |
|
||||
| `.dmg` installer | `rustdesk-1.4.7-arm64.dmg` | 27 MB |
|
||||
| Rust lib | `target/release/liblibrustdesk.dylib` | 22.6 MB |
|
||||
|
||||
Bundle ID: `com.carriez.rustdesk` (NOT the official `com.rustdesk.client` —
|
||||
change in `flutter/macos/Runner/Configs/AppInfo.xcconfig` if needed).
|
||||
|
||||
Architecture: **arm64 only**. For Intel Mac support, build a universal
|
||||
dylib with `lipo` and drop the EXCLUDED_ARCHS hack.
|
||||
|
||||
---
|
||||
|
||||
## 6. First-launch permissions
|
||||
|
||||
When you launch the app for the first time, macOS will prompt for:
|
||||
|
||||
- **Screen Recording** (System Settings → Privacy & Security → Screen Recording) —
|
||||
needed to capture the screen when you are being controlled
|
||||
- **Microphone** — for audio forwarding
|
||||
- **Accessibility** — for keyboard/mouse input forwarding
|
||||
|
||||
Grant these or the corresponding features won't work.
|
||||
|
||||
---
|
||||
|
||||
## 7. NOT covered here (out of scope)
|
||||
|
||||
- iOS / Android builds (different toolchains)
|
||||
- Apple notarization (would need an Apple Developer ID account)
|
||||
- Self-hosted rendezvous/relay server (use public `rs-ny.rustdesk.com` for now)
|
||||
- Universal binary (arm64 + x86_64)
|
||||
- Code signing with a real developer certificate
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# post-build-mac.sh
|
||||
# Re-sign and package a release .app into a .dmg for RustDesk (macOS).
|
||||
#
|
||||
# Must be run AFTER `flutter build macos --release` finishes.
|
||||
# The .app will NOT launch on macOS 26.5 without re-signing — see BUILD.md.
|
||||
#
|
||||
# Usage:
|
||||
# ./res/post-build-mac.sh # signs build output, installs to /Applications, creates dmg
|
||||
# ./res/post-build-mac.sh --no-dmg # just re-sign and install, skip dmg
|
||||
# ./res/post-build-mac.sh --no-install # just re-sign and create dmg in build dir
|
||||
# VERSION=1.4.7 ./res/post-build-mac.sh # custom version label in dmg filename
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------- Config ----------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
VERSION="${VERSION:-1.4.7}"
|
||||
ARCH="${ARCH:-arm64}"
|
||||
APP_REL="flutter/build/macos/Build/Products/Release/RustDesk.app"
|
||||
APP_PATH="$PROJECT_ROOT/$APP_REL"
|
||||
DMG_NAME="rustdesk-${VERSION}-${ARCH}.dmg"
|
||||
STAGING="/tmp/dmg_staging_postbuild"
|
||||
INSTALL_TARGET="${INSTALL_TARGET:-/Applications/RustDesk.app}"
|
||||
|
||||
SKIP_DMG=0
|
||||
SKIP_INSTALL=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-dmg) SKIP_DMG=1 ;;
|
||||
--no-install) SKIP_INSTALL=1 ;;
|
||||
-h|--help)
|
||||
grep -E '^#( |!)' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------- Sanity checks ----------
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: $APP_PATH does not exist." >&2
|
||||
echo "Run 'flutter build macos --release' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------- Step 1: Re-sign the .app ----------
|
||||
echo "==> Re-signing $APP_PATH"
|
||||
codesign --force --deep -s - "$APP_PATH"
|
||||
codesign --verify --strict --verbose=2 "$APP_PATH" 2>&1 | head -3 || true
|
||||
echo " Signature: $(codesign -dvv "$APP_PATH" 2>&1 | grep -E '^Signature=' | head -1)"
|
||||
|
||||
# ---------- Step 2: Install to /Applications ----------
|
||||
if [ "$SKIP_INSTALL" -eq 0 ]; then
|
||||
echo "==> Installing to $INSTALL_TARGET"
|
||||
pkill -9 -f "RustDesk.app/Contents/MacOS/RustDesk" 2>/dev/null || true
|
||||
sleep 1
|
||||
rm -rf "$INSTALL_TARGET" 2>/dev/null || true
|
||||
cp -R "$APP_PATH" "$INSTALL_TARGET"
|
||||
# Re-sign the installed copy (path is now different, so signatures may differ)
|
||||
codesign --force --deep -s - "$INSTALL_TARGET"
|
||||
echo " Installed: $INSTALL_TARGET"
|
||||
fi
|
||||
|
||||
# ---------- Step 3: Build the .dmg ----------
|
||||
if [ "$SKIP_DMG" -eq 0 ]; then
|
||||
echo "==> Building $DMG_NAME"
|
||||
rm -rf "$STAGING"
|
||||
mkdir -p "$STAGING"
|
||||
cp -R "$APP_PATH" "$STAGING/RustDesk.app"
|
||||
ln -sf /Applications "$STAGING/Applications"
|
||||
# CRITICAL: re-sign the staging copy too — hdiutil snapshots whatever's there
|
||||
codesign --force --deep -s - "$STAGING/RustDesk.app"
|
||||
|
||||
DMG_OUT="$PROJECT_ROOT/$DMG_NAME"
|
||||
rm -f "$DMG_OUT"
|
||||
hdiutil create -volname "RustDesk Installer" \
|
||||
-srcfolder "$STAGING" \
|
||||
-ov -format UDZO \
|
||||
"$DMG_OUT"
|
||||
echo " DMG: $DMG_OUT ($(du -h "$DMG_OUT" | awk '{print $1}'))"
|
||||
fi
|
||||
|
||||
# ---------- Step 4: Cleanup ----------
|
||||
rm -rf "$STAGING"
|
||||
|
||||
echo ""
|
||||
echo "Done. To launch:"
|
||||
echo " open $INSTALL_TARGET"
|
||||
if [ "$SKIP_DMG" -eq 0 ]; then
|
||||
echo "To distribute:"
|
||||
echo " open $PROJECT_ROOT/$DMG_NAME"
|
||||
fi
|
||||
Reference in New Issue
Block a user