Implement CI workflow for Gitea. Include provision scripts for Gitea runners.
build-linux / build-linux-x64 (push) Successful in 5m23s
build-macos / build-macos-x64 (push) Successful in 9m4s
build-windows / build-windows-x64 (push) Successful in 10m13s

This commit is contained in:
2026-05-07 09:39:23 +02:00
parent 5abae617dc
commit 47f0d0fff2
9 changed files with 3147 additions and 1 deletions
+250
View File
@@ -0,0 +1,250 @@
name: build-linux
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.75"
LLVM_VERSION: "15.0.6"
# Where provision.sh installs LLVM (binary tarball from llvm.org). Same exact
# version as the Windows runner uses, distro-portable.
LLVM_HOME: '/opt/llvm-15.0.6'
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-linux-x64
# Pinned to ubuntu-22.04 because flutter_rust_bridge_codegen 1.80.1 emits
# broken Dart on Debian 13 (matches upstream rustdesk's bridge.yml host).
# provision.sh tags Ubuntu 22.04 hosts with the `ubuntu-22.04` label.
runs-on: [self-hosted, Linux, X64, ubuntu-22.04]
timeout-minutes: 240
env:
VCPKG_ROOT: /opt/vcpkg
VCPKG_BINARY_SOURCES: "clear;files,/var/cache/vcpkg,readwrite"
LIBCLANG_PATH: /opt/llvm-15.0.6/lib
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Verify host toolchain
shell: bash
run: |
required=(node git bash python3 rustc cargo rustup clang flutter cmake ninja nasm pkg-config dpkg-deb)
missing=()
for t in "${required[@]}"; do
if command -v "$t" >/dev/null 2>&1; then
printf '%-20s %s\n' "$t" "$(command -v "$t")"
else
missing+=("$t")
printf '%-20s 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; }
[[ -f "$LIBCLANG_PATH/libclang.so" ]] || {
echo "libclang.so not found at $LIBCLANG_PATH"; 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 "Base : $base"
echo "Suffix : $suffix"
echo "Display : $display"
echo "VERSION_DISPLAY=$display" >> "$GITHUB_ENV"
- name: Patch Cargo.toml with display version
shell: bash
run: |
sed -i -E "0,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/}" Cargo.toml
grep '^version' Cargo.toml | head -1
- name: Ensure Rust toolchain configured
shell: bash
run: |
# provision.sh installs Rust machine-wide at /opt/cargo + /opt/rustup,
# so this is normally a no-op verification. Kept as a guardrail.
rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
rustup default "$RUST_VERSION"
rustup target add x86_64-unknown-linux-gnu
rustc --version
cargo --version
- name: Install flutter_rust_bridge codegen tools
shell: bash
run: |
# Pin install destination so binaries land in a deterministic path
# regardless of CARGO_HOME.
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
# flutter_rust_bridge_codegen 1.80.1 + freezed produces broken Dart
# output (wrong FFI types, unprefixed Int/Pointer in part files) when
# run under Flutter 3.24.5 on Linux. Upstream's bridge.yml works around
# this by running the bridge generation under Flutter 3.22.3 -- the
# produced .dart/.freezed.dart files are then compatible with the
# 3.24.5 build that follows.
#
# provision.sh installs Flutter 3.22.3 at /opt/flutter-bridge alongside
# /opt/flutter (3.24.5). Use the bridge SDK ONLY for pub get + codegen,
# then unset PATH overrides so subsequent steps use the build SDK.
export PATH="/opt/cargo-tools/bin:/opt/flutter-bridge/bin:$PATH"
flutter --version
command -v flutter_rust_bridge_codegen
# CRITICAL: use the apt-installed libclang (libclang-dev), NOT the
# LLVM 15.0.6 tarball at /opt/llvm-15.0.6 that the rest of the build
# uses. fr_bridge_codegen 1.80.1 emits broken Dart (stray
# `typedef bool = NativeFunction<...>`, unprefixed Int/Pointer in
# part files) when its internal ffigen runs against the tarball
# libclang. Upstream bridge.yml uses apt's libclang and the same
# codegen produces clean output. Don't pass --llvm-path either; let
# ffigen find libclang via system defaults.
unset LIBCLANG_PATH
# extended_text 14.0.0 requires Dart >=3.5 (Flutter 3.24+). Flutter
# 3.22.3 has Dart 3.4.4, so downgrade to 13.0.0 for the duration of
# bridge generation. Mirrors upstream bridge.yml.
(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 invokes `flutter pub run ffigen`
# internally, which re-validates pubspec.yaml -- so pubspec.yaml must
# still be the downgraded version when this runs.
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
# Now bridge gen is done. Restore the original pubspec.yaml and
# re-resolve under the build SDK (3.24.5) so pubspec.lock has the
# correct entries for the final flutter build linux step.
(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 " generated_bridge.freezed.dart (first 80 lines)"
echo "============================================================"
head -80 flutter/lib/generated_bridge.freezed.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)"
echo
echo "============================================================"
echo " Search: imports that might pull weird symbols"
echo "============================================================"
grep -nE '^import|^export|^part ' \
flutter/lib/generated_bridge.dart \
flutter/lib/generated_bridge.freezed.dart 2>/dev/null
echo
echo "============================================================"
echo " store_dart_post_cobject signature in generated_bridge.dart"
echo "============================================================"
grep -nA2 'store_dart_post_cobject' flutter/lib/generated_bridge.dart 2>/dev/null | head -30
echo
echo "============================================================"
echo " Line 25 of generated_bridge.freezed.dart (the failing one)"
echo "============================================================"
sed -n '20,30p' flutter/lib/generated_bridge.freezed.dart 2>/dev/null
echo
echo "============================================================"
echo " flutter_rust_bridge package version actually resolved"
echo "============================================================"
grep -A2 'flutter_rust_bridge:' flutter/pubspec.lock | head -10
- name: vcpkg install dependencies (x64-linux)
shell: bash
env:
VCPKG_DEFAULT_HOST_TRIPLET: x64-linux
run: |
mkdir -p /var/cache/vcpkg
if ! "$VCPKG_ROOT/vcpkg" install \
--triplet x64-linux \
--x-install-root="$VCPKG_ROOT/installed"; then
find "$VCPKG_ROOT/" -name '*.log' -exec sh -c 'echo "===== {} ====="; cat "{}"' \;
exit 1
fi
- name: Build RustDesk (.deb)
shell: bash
run: |
set -e
# build.py on Linux (no pacman/yum/zypper detected) goes to
# build_flutter_deb() which does:
# - cargo build --features <features> --lib --release
# - flutter build linux --release
# - assembles tmpdeb/ and runs dpkg-deb -b
# Output: ./rustdesk-<version-from-Cargo.toml>.deb in the repo root.
python3 build.py --flutter --hwcodec --unix-file-copy-paste
mkdir -p ./SignOutput
# build.py names the .deb after Cargo.toml's version which we patched
# above, so the file should already carry $VERSION_DISPLAY.
mv "./rustdesk-${VERSION_DISPLAY}.deb" "./SignOutput/rustdesk-${VERSION_DISPLAY}-amd64.deb"
- name: Report signing status of build artifacts
shell: bash
run: |
# .deb files are typically signed with debsign or via the apt repo
# signing pipeline, not the .deb itself. Just list contents for now.
for f in ./SignOutput/*.deb; do
[[ -f "$f" ]] || continue
size=$(stat -c%s "$f")
printf '[UNSIGNED] %s (%d bytes)\n' "$(basename "$f")" "$size"
done
# Gitea/GHA-style annotation so it surfaces in the run summary.
echo "::warning title=Unsigned .deb::Wire up debsigs / repo signing before distributing."
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: rustdesk-linux-x64-${{ github.sha }}
path: SignOutput/rustdesk-*.deb
if-no-files-found: warn
retention-days: 14
+231
View File
@@ -0,0 +1,231 @@
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
# 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
+487
View File
@@ -0,0 +1,487 @@
name: build-windows
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.75"
LLVM_VERSION: "15.0.6"
# Where provision.ps1 installs LLVM. ffigen and flutter_rust_bridge_codegen need
# the install root (they append bin\libclang.dll themselves).
LLVM_HOME: 'C:\tools\llvm-15.0.6'
FLUTTER_VERSION: "3.24.5"
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
CARGO_EXPAND_VERSION: "1.0.95"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
# Numeric base, must match Cargo.toml's <major>.<minor>.<patch>.
# MSI ProductVersion is forced to this (Windows Installer rejects non-numeric).
VERSION_BASE: "1.4.6"
# Default suffix on push events. workflow_dispatch can override per-run.
VERSION_SUFFIX: ${{ inputs.version_suffix || 'cst' }}
jobs:
build-x64:
name: build-windows-x64
runs-on: [self-hosted, windows-10]
timeout-minutes: 240
env:
VCPKG_ROOT: C:\vcpkg
VCPKG_BINARY_SOURCES: "clear;files,C:\\vcpkg-cache,readwrite"
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Verify host toolchain
shell: pwsh
run: |
$required = 'node','pwsh','git','bash','python','rustc','cargo','rustup','clang','flutter','nuget','cmake','ninja','dotnet'
$missing = @()
foreach ($tool in $required) {
$cmd = Get-Command $tool -ErrorAction SilentlyContinue
if (-not $cmd) { $missing += $tool; continue }
$ver = & $tool --version 2>&1 | Select-Object -First 1
Write-Host ("{0,-10} {1} ({2})" -f $tool, $cmd.Source, $ver)
}
if ($missing.Count -gt 0) {
Write-Error ("Missing tools on runner: {0}. Re-run provision.ps1 or install manually." -f ($missing -join ', '))
exit 1
}
if (-not $env:VCPKG_ROOT -or -not (Test-Path "$env:VCPKG_ROOT\vcpkg.exe")) {
Write-Error "VCPKG_ROOT not set or vcpkg.exe missing at $env:VCPKG_ROOT"
exit 1
}
Write-Host "VCPKG_ROOT $env:VCPKG_ROOT"
- name: Compute version strings
shell: pwsh
run: |
$base = "${env:VERSION_BASE}"
$suffix = "${env:VERSION_SUFFIX}"
if ($base -notmatch '^\d+\.\d+\.\d+$') {
Write-Error "VERSION_BASE '$base' must be major.minor.patch numeric"
exit 1
}
if ($suffix) { $display = "$base-$suffix" } else { $display = $base }
Write-Host ("Base : {0} (used for MSI ProductVersion)" -f $base)
Write-Host ("Suffix : {0}" -f $suffix)
Write-Host ("Display : {0} (used for exe filename + Cargo.toml)" -f $display)
"VERSION_DISPLAY=$display" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Patch Cargo.toml with display version
shell: bash
run: |
# Cargo accepts SemVer-style suffix with a hyphen (e.g. 1.4.6-cst).
sed -i -E "0,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/}" Cargo.toml
echo "--- Cargo.toml [package] ---"
awk '/^\[package\]/{f=1} f; /^\[/&&!/^\[package\]/{f=0}' Cargo.toml | head -10
- name: Ensure Rust toolchain configured for runner user
shell: pwsh
run: |
# provision.ps1 installs rust machine-wide (CARGO_HOME=C:\cargo,
# RUSTUP_HOME=C:\rustup) so this is normally a no-op verification.
# Kept as a guardrail in case the runner was provisioned with an
# older script or by hand.
rustup --version
rustup toolchain install $env:RUST_VERSION --profile minimal --component rustfmt
if ($LASTEXITCODE -ne 0) { throw "rustup toolchain install failed ($LASTEXITCODE)" }
rustup default $env:RUST_VERSION
if ($LASTEXITCODE -ne 0) { throw "rustup default failed ($LASTEXITCODE)" }
rustup target add x86_64-pc-windows-msvc
rustc --version
cargo --version
- name: Install flutter_rust_bridge codegen tools
shell: pwsh
run: |
# Pin the install destination with --root so the resulting binaries land
# in a deterministic, machine-wide path regardless of which user the
# runner service is configured to run as.
$tools = 'C:\cargo-tools'
New-Item -ItemType Directory -Force -Path "$tools\bin" | Out-Null
cargo install --root $tools cargo-expand --version "$env:CARGO_EXPAND_VERSION" --locked
if ($LASTEXITCODE -ne 0) { throw "cargo install cargo-expand failed ($LASTEXITCODE)" }
cargo install --root $tools flutter_rust_bridge_codegen --version "$env:FLUTTER_RUST_BRIDGE_VERSION" --features uuid --locked
if ($LASTEXITCODE -ne 0) { throw "cargo install flutter_rust_bridge_codegen failed ($LASTEXITCODE)" }
Write-Host "--- $tools\bin ---"
Get-ChildItem "$tools\bin" | Format-Table Name, Length, LastWriteTime
$expected = Join-Path $tools 'bin\flutter_rust_bridge_codegen.exe'
if (-not (Test-Path $expected)) { throw "missing: $expected" }
Add-Content -Path $env:GITHUB_PATH -Value "$tools\bin"
- name: Generate Rust <-> Dart bridge
shell: pwsh
env:
LIBCLANG_PATH: '${{ env.LLVM_HOME }}\bin'
run: |
$ErrorActionPreference = 'Stop'
# Force-prepend cargo-tools and Git so they win the lookup race even if
# something earlier in PATH owns the same name.
$env:PATH = "C:\cargo-tools\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\bin;$env:PATH"
Write-Host "== Tool resolution =="
foreach ($t in 'git','flutter','flutter_rust_bridge_codegen','dart') {
$cmd = Get-Command $t -ErrorAction SilentlyContinue
if ($cmd) { Write-Host ("{0,-30} {1}" -f $t, $cmd.Source) }
else { Write-Host ("{0,-30} MISSING" -f $t) }
}
# `where git` shows ALL git.exe matches on PATH. If multiple, the first
# is what CreateProcess will pick -- and it might be a broken shim.
Write-Host "`n== where git =="
where.exe git 2>&1
Write-Host "`n== git --version =="
git --version
# Diagnose the real reason flutter pub get says "Unable to find git in
# your PATH". On a self-hosted runner where the Flutter SDK at
# C:\tools\flutter was provisioned by a different user, git refuses to
# touch it with "fatal: detected dubious ownership". Flutter's tool
# catches that ProcessException and (misleadingly) reports it as a
# missing-git-on-PATH error.
Write-Host "`n== git ops on flutter SDK (probe for dubious ownership) =="
git -C 'C:\tools\flutter' rev-parse HEAD 2>&1 | Out-Host
Write-Host "`n== Configuring safe.directory globally for runner user =="
git config --global --add safe.directory '*'
git config --global --get-all safe.directory
# Workaround: stage git.exe next to dart.exe. CreateProcessW searches
# the calling exe's directory before PATH -- so dropping git.exe here
# bypasses any PATH oddities the dart child may inherit from its
# parents (powershell -> cmd -> flutter.bat -> dart.exe).
$dartBin = 'C:\tools\flutter\bin\cache\dart-sdk\bin'
$stagedGit = Join-Path $dartBin 'git.exe'
$sourceGit = 'C:\Program Files\Git\cmd\git.exe'
if ((Test-Path $dartBin) -and -not (Test-Path $stagedGit)) {
Write-Host "`nStaging $sourceGit -> $stagedGit"
Copy-Item -Force $sourceGit $stagedGit
}
if (-not (Test-Path "$env:LIBCLANG_PATH\libclang.dll")) {
throw "libclang.dll not found at $env:LIBCLANG_PATH"
}
Write-Host "`n== flutter pub get =="
Push-Location flutter
flutter pub get
$rc = $LASTEXITCODE
Pop-Location
if ($rc -ne 0) { throw "flutter pub get failed ($rc)" }
Write-Host "`n== flutter_rust_bridge_codegen =="
flutter_rust_bridge_codegen `
--llvm-path "$env:LLVM_HOME" `
--rust-input ./src/flutter_ffi.rs `
--dart-output ./flutter/lib/generated_bridge.dart `
--c-output ./flutter/macos/Runner/bridge_generated.h
if ($LASTEXITCODE -ne 0) { throw "flutter_rust_bridge_codegen failed ($LASTEXITCODE)" }
Copy-Item -Force `
./flutter/macos/Runner/bridge_generated.h `
./flutter/ios/Runner/bridge_generated.h
- name: Replace Flutter engine with rustdesk custom engine
shell: pwsh
run: |
flutter precache --windows
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
Expand-Archive -Force -Path windows-x64-release.zip -DestinationPath windows-x64-release
$engineDir = "C:\tools\flutter\bin\cache\artifacts\engine\windows-x64-release"
New-Item -ItemType Directory -Force -Path $engineDir | Out-Null
Move-Item -Force windows-x64-release\* $engineDir\
- name: Patch Flutter (dropdown_menu enableFilter)
shell: bash
run: |
patch_file=".github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff"
if [ -f "$patch_file" ]; then
flutter_root="$(dirname "$(dirname "$(which flutter)")")"
cp "$patch_file" "$flutter_root/"
(cd "$flutter_root" && git apply "$(basename "$patch_file")" || true)
fi
- name: vcpkg install dependencies (x64-windows-static)
shell: bash
env:
VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static
run: |
mkdir -p /c/vcpkg-cache
if ! "$VCPKG_ROOT/vcpkg" install \
--triplet x64-windows-static \
--x-install-root="$VCPKG_ROOT/installed"; then
find "$VCPKG_ROOT/" -name "*.log" -exec sh -c 'echo "===== {} ====="; cat "{}"' \;
exit 1
fi
- name: Diagnose + restore flutter/windows if missing
shell: bash
run: |
echo "== pwd =="
pwd
echo
echo "== flutter/ (top level) =="
ls -la flutter/ 2>/dev/null || echo "flutter/ missing"
echo
if [ -d flutter/windows ]; then
echo "== flutter/windows/ exists, file count =="
find flutter/windows -type f | wc -l
ls -la flutter/windows/
else
echo "!! flutter/windows MISSING !!"
echo "git status (filtered to flutter/windows):"
git status --porcelain | grep -E "flutter/windows" | head -30 || echo "(no entries)"
echo
echo "Restoring flutter/windows from git index..."
git checkout HEAD -- flutter/windows
if [ -d flutter/windows ]; then
echo "Restored: $(find flutter/windows -type f | wc -l) files"
else
echo "RESTORE FAILED -- check git ls-files:"
git ls-files flutter/windows | head
exit 1
fi
fi
# The upstream GitHub workflow runs `python build.py` which chains cargo +
# `flutter build windows` in one step. We split that here so we can:
# - get a checkpoint between cargo and flutter (~14 min apart)
# - restore flutter/windows from git index if anything has clobbered it
# (early in our self-hosted bring-up, flutter/windows occasionally
# vanished during the cargo phase under LocalSystem; the restore is
# defensive and a no-op once the runner is on a normal user)
- name: Cargo build (virtual_display dylib + main lib)
shell: pwsh
run: |
$features = "hwcodec,vram,flutter"
Push-Location libs\virtual_display\dylib
cargo build --release
if ($LASTEXITCODE -ne 0) { throw "cargo build (virtual_display) failed" }
Pop-Location
cargo build --features $features --lib --release
if ($LASTEXITCODE -ne 0) { throw "cargo build (rustdesk lib) failed" }
if (-not (Test-Path target\release\librustdesk.dll)) {
throw "target\release\librustdesk.dll missing after cargo build"
}
# Restore + build are intentionally one step. Closes any potential window
# for flutter/windows to vanish between a separate restore step and the
# build, regardless of root cause.
- name: Flutter build windows (with last-second restore)
shell: pwsh
run: |
$cmake = '.\flutter\windows\CMakeLists.txt'
Write-Host "== Pre-flight check =="
Write-Host "pwd: $(Get-Location)"
if (Test-Path .\flutter\windows) {
Write-Host "flutter/windows present: $((Get-ChildItem -Recurse .\flutter\windows -File).Count) files"
} else {
Write-Host "flutter/windows MISSING"
}
if (-not (Test-Path $cmake)) {
Write-Host "`n== Restoring flutter/windows from git index =="
git status --porcelain | Where-Object { $_ -match 'flutter/windows' } | Select-Object -First 10
git checkout HEAD -- flutter/windows
if ($LASTEXITCODE -ne 0) { throw "git checkout HEAD -- flutter/windows failed" }
if (-not (Test-Path $cmake)) {
Write-Host "git ls-files flutter/windows:"
git ls-files flutter/windows | Select-Object -First 5
throw "Restore did not produce $cmake"
}
Write-Host "Restored: $((Get-ChildItem -Recurse .\flutter\windows -File).Count) files"
}
# Touch a marker so we can confirm later (in logs) that this guard ran.
$marker = ".\flutter\windows\.restored-at"
Set-Content -Path $marker -Value (Get-Date -Format o)
Write-Host "`n== Running flutter build windows --release =="
Push-Location flutter
flutter build windows --release
$rc = $LASTEXITCODE
Pop-Location
if ($rc -ne 0) {
Write-Host "`n!! flutter build failed -- post-mortem state of flutter/windows: !!"
if (Test-Path .\flutter\windows) { Get-ChildItem .\flutter\windows -Force }
else { Write-Host "(does not exist)" }
throw "flutter build windows failed ($rc)"
}
# Mirror what build.py's build_flutter_windows() does after flutter build.
Copy-Item -Force `
target\release\deps\dylib_virtual_display.dll `
flutter\build\windows\x64\runner\Release\
Move-Item -Force flutter\build\windows\x64\runner\Release rustdesk
- name: Bundle usbmmidd_v2 + printer driver
shell: pwsh
continue-on-error: true
run: |
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
Expand-Archive -Force usbmmidd_v2.zip -DestinationPath .
Remove-Item -Recurse -Force usbmmidd_v2\Win32
Remove-Item -Force usbmmidd_v2\deviceinstaller64.exe, usbmmidd_v2\deviceinstaller.exe, usbmmidd_v2\usbmmidd.bat
Move-Item -Force .\usbmmidd_v2 .\rustdesk\
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/rustdesk_printer_driver_v4-1.4.zip -OutFile printer.zip
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/printer_driver_adapter.zip -OutFile printer_adapter.zip
Expand-Archive -Force printer.zip -DestinationPath .
Expand-Archive -Force printer_adapter.zip -DestinationPath .
New-Item -ItemType Directory -Force -Path .\rustdesk\drivers | Out-Null
Move-Item -Force .\rustdesk_printer_driver_v4-1.4 .\rustdesk\drivers\RustDeskPrinterDriver
Move-Item -Force .\printer_driver_adapter.dll .\rustdesk\
- name: Copy Runner.res for portable packer
shell: bash
continue-on-error: true
run: |
runner_res=$(find . -name Runner.res | head -1)
[ -n "$runner_res" ] && cp "$runner_res" ./libs/portable/Runner.res
- name: Build portable self-extracting exe
shell: bash
run: |
sed -i '/dpiAware/d' res/manifest.xml
pushd ./libs/portable
pip install -r requirements.txt
python ./generate.py -f ../../rustdesk/ -o . -e ../../rustdesk/rustdesk.exe
popd
mkdir -p ./SignOutput
mv ./target/release/rustdesk-portable-packer.exe "./SignOutput/rustdesk-${VERSION_DISPLAY}-x86_64.exe"
- name: Build MSI installer
shell: pwsh
run: |
Push-Location .\res\msi
# Pass numeric VERSION_BASE explicitly: MSI ProductVersion must be numeric,
# so we cannot let preprocess.py auto-detect from rustdesk.exe (which now
# carries the suffixed VERSION_DISPLAY).
python preprocess.py --arp -d ..\..\rustdesk -v "${env:VERSION_BASE}"
# Resolve MSBuild from the installed VS Build Tools.
# Resolve vswhere.exe explicitly. ${env:ProgramFiles(x86)} interpolation
# has parser-version quirks; use [Environment] to avoid them.
$pfx86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)')
$vswhere = Join-Path $pfx86 'Microsoft Visual Studio\Installer\vswhere.exe'
if (-not (Test-Path $vswhere)) {
throw "vswhere.exe not found at $vswhere -- VS Installer missing?"
}
Write-Host "vswhere: $vswhere"
# Diagnostic: show what VS installs vswhere can see.
Write-Host "`n== vswhere installations =="
& $vswhere -all -prerelease -products '*' -property installationPath
Write-Host "==="
# Try the targeted -requires first; fall back to a broader search if the
# component name doesn't match (e.g. on Build Tools the component IDs differ).
$msbuild = & $vswhere -latest -products '*' `
-requires Microsoft.Component.MSBuild `
-find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1
if (-not $msbuild) {
Write-Host "Targeted lookup failed; trying broader search."
$msbuild = & $vswhere -latest -products '*' -prerelease `
-find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1
}
if (-not $msbuild) {
# Last resort: any MSBuild.exe under any VS install path.
$vsRoots = & $vswhere -all -prerelease -products '*' -property installationPath
foreach ($r in $vsRoots) {
$candidate = Get-ChildItem -Path $r -Recurse -Filter 'MSBuild.exe' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like '*\Bin\MSBuild.exe' -and $_.FullName -notlike '*\amd64\*' } |
Select-Object -First 1
if ($candidate) { $msbuild = $candidate.FullName; break }
}
}
if (-not $msbuild) { throw "MSBuild not found via vswhere or filesystem search" }
Write-Host "msbuild: $msbuild"
# Two-stage restore covers both project flavors in this solution:
# - CustomActions.vcxproj uses old-style packages.config -> nuget restore
# - Package.wixproj is SDK-style PackageReference -> msbuild -t:Restore
nuget restore msi.sln
& $msbuild msi.sln -t:Restore -p:Configuration=Release -p:Platform=x64
if ($LASTEXITCODE -ne 0) { throw "MSBuild restore failed ($LASTEXITCODE)" }
# WiX's WindowsInstallerValidation target invokes ICEs through the
# local Windows Installer service (msiserver). When the runner user
# lacks the COM/RPC rights to that service, every ICE check fails
# with WIX0217. Skip ICE validation -- it's a development-time lint,
# not a functional requirement, and the produced MSI is identical.
# Defensively also nudge the service in case some other validation
# path (e.g. signing tools) needs it.
try { Start-Service msiserver -ErrorAction SilentlyContinue } catch {}
& $msbuild msi.sln `
-p:Configuration=Release -p:Platform=x64 `
/p:TargetVersion=Windows10 `
/p:SuppressValidation=true
if ($LASTEXITCODE -ne 0) { throw "MSBuild build failed ($LASTEXITCODE)" }
Move-Item -Force .\Package\bin\x64\Release\en-us\Package.msi "..\..\SignOutput\rustdesk-${env:VERSION_DISPLAY}-x86_64.msi"
Pop-Location
- name: Report signing status of build artifacts
shell: pwsh
run: |
$artifacts = Get-ChildItem .\SignOutput -Include *.exe,*.msi -File
if (-not $artifacts) {
Write-Warning "No artifacts found in SignOutput\"
return
}
$unsigned = @()
foreach ($f in $artifacts) {
$sig = Get-AuthenticodeSignature -FilePath $f.FullName
$size = '{0,8:N0}' -f $f.Length
switch ($sig.Status) {
'Valid' {
Write-Host ("[ SIGNED ] {0} ({1} bytes) signed by: {2}" -f $f.Name, $size, $sig.SignerCertificate.Subject)
}
'NotSigned' {
Write-Host ("[UNSIGNED] {0} ({1} bytes)" -f $f.Name, $size)
$unsigned += $f.Name
}
default {
Write-Host ("[ {0,-7} ] {1} ({2} bytes) -- {3}" -f $sig.Status, $f.Name, $size, $sig.StatusMessage)
$unsigned += $f.Name
}
}
}
if ($unsigned.Count -gt 0) {
# Render a Gitea/GHA-style annotation so it shows up prominently in the run summary.
$list = $unsigned -join ', '
Write-Host "::warning title=Unsigned artifacts::$list -- SmartScreen will warn end users. Wire up signing before distributing."
}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: rustdesk-windows-x64-${{ github.sha }}
path: |
SignOutput/rustdesk-*.exe
SignOutput/rustdesk-*.msi
if-no-files-found: warn
retention-days: 14