You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Offload macOS SwiftPM Debug builds to build machines #2476
Building a native macOS app, Stim Desktop above all, still uses the main Mac's CPU. The mini-desktop.sh workflow builds on the mini over SSH. Stim should build macOS Debug products on an approved build machine through the existing offload path (offload.machines, build capability, build.* protocol), with no SSH.
Evidence
packages/server/src/build.ts and packages/stim-cli/src/offload/client.ts support iOS simulator and Android emulator Debug builds only.
packages/stim-cli/src/commands/macos.ts runs swift build -c debug --product <p> --scratch-path ... and stageBundle locally.
Stim Desktop itself is built with apps/desktop/scripts/bundle.sh. Whether the stim macos SwiftPM path (macos.product / macos.infoPlist) can produce a usable Desktop test copy is unverified, and finding out is part of this track.
Fix idea / scope
Add a macOS SwiftPM Debug build job kind to the offload protocol, with fixed swift build arguments, the same source sync, worker area, claim and slot rules, and a staged .app artifact returned digest-verified.
stim macos uses offload.mode the same way iOS and Android do: every offload failure falls back to a local build, and no failure writes the cache.
When the build machine and the hosting machine are the same node, a later step may hand the artifact over on the host without a round trip. That needs its own contract comment first.
Acceptance
Unit tests for the job kind and its fallback.
A real two-Mac run: build the HostFixture, and then Stim Desktop if the SwiftPM path works, on the mini through offload. The artifact is identical in content to a local build.
Out of scope
Hosted launch (#2473 and #2475), Xcode-project macOS targets, and caching of macOS artifacts beyond what offload already does.
Step 1 findings: can the SwiftPM stim macos build run on a build machine
Measured on this Mac with a worktree build of main (8c52b89) and the mini's build.offer. No SSH was used.
Toolchain. The mini's offer reports arm64 and Xcode 27.0 / Build version 27A266a, the same as this Mac. swift build runs from that Xcode (swift 6.4, macOS SDK 27.0 here), so the offload comparison for macOS is stimBuild, arch, xcode and the macOS SDK version. The offer has no macOS SDK field yet; the PR adds one next to simulatorSdk.
SwiftPM on the worker.apps/desktop needs only swift build -c debug --product StimDesktop and network access to GitHub for its pinned packages and the Lottie, Sparkle and Sentry binary artifacts (it resolves by revision, so Package.resolved is exact). Nothing else is installed on the build host (no pnpm, no Pods). Plan for caches: pass --cache-path <client area>/cache/swiftpm so each client has its own checkouts and artifact cache instead of sharing the worker user's ~/Library/Caches/org.swift.swiftpm, and keep the scratch path inside the client's repo area so a repeated build is incremental, as Gradle and Xcode are today. The worker must not run pnpm install for this job kind, which the current worker would do for a repo with pnpm-lock.yaml.
Signing.stageBundle already signs with codesign --sign -, which needs no account and no keychain, so it runs unchanged on the worker. The bundle id is derived from sha256(root), so the client sends the id in build.start and the worker stamps it. A later hosted launch re-stamps and re-signs on its host anyway.
Timing. Cold swift build -c debug --product StimDesktop --jobs 2 took 1 min 7 s end to end on this loaded MacBook (55 s compile, dependencies resolved from cache). A stim macos run from the CLI took 67 s, then launched and was stopped.
Is the stim macos copy a usable Stim Desktop test copy? It builds, launches, runs under its own bundle id (dev.stim.desktop.development.stim.<hash>), signs ad hoc and is thin arm64 (94 MB). It is not equivalent to bundle.sh. stageBundle copies only the executable, *.framework and SwiftPM *.bundle directories, and Contents/Resources ends up empty. bundle.sh additionally adds the compiled asset catalog and app icon, the Inter and JetBrains Mono fonts, the Lottie JSON and branding artwork from ../../website, the license files and the sim-fold helper. So the SwiftPM path gives a working app with fallback fonts and no artwork or sim-fold, not a replacement for mini-desktop.sh's fidelity. That is independent of offload and out of scope here; it belongs to whichever track wants Desktop as the main use case, and I will file it separately.
Cache key and artifact.stim macos has no build cache today: every run rebuilds into macos/build under the workspace, so there is nothing to key or store, and offload adds none. The artifact is the staged <Product>.app, tarred by the worker and returned digest-verified through build.artifact. The fingerprint that build.start requires is, for macOS, a digest over the synced manifest (path, kind, sha256 of every file, sorted). The client computes it, the worker recomputes it from the manifest it mirrored and refuses on a mismatch, and no artifact is stored under it.
"Identical in content". Not achievable as byte equality, even locally: two local builds of the same commit at different scratch paths differ in the executable (UUID, __text, 18 MB of differing bytes) because Debug binaries embed absolute build and source paths. I will check the achievable criterion in the real run instead: the same file set, Info.plist, frameworks and resources, and an executable that launches and passes codesign --verify.
Blocker for the real two-Mac run.build.start refuses unless the worker's stimBuild (a digest of its bundled Stim .mjs files) equals the client's. Any worker change therefore needs the mini's stim-server to run this PR's build before a two-Mac run can succeed, and SSH is off the table. Until then I verify the worker against real swift, codesign and tar locally, with unit tests for the job kind, the fallback and the stale-cache invariants, and run the mini once it carries this build.
Scope. The artifact comes back to the client. The same-node hand-over to track A's hosted session stays out of this PR, as the issue says, until it has its own contract comment. The client helper returns a staged .app with a caller-chosen bundle id, so #2475 can call it with the base id for hosted delivery.
Merged in #2482 (4c6542d). The offload path was verified end to end against real swift, codesign and tar through a local stand-in for the tailnet (this branch's client, stim-server and worker). The two-Mac run against the mini is still open: build.start requires the worker's Stim build digest to equal the client's, so it needs the mini's stim-server to run a build that includes #2482 (no SSH was used). After that, run STIM_OFFLOAD_MODE=force stim macos from apps/desktop and from a hosted stim macos --host run, to confirm the artifact reaches track A's delivery.
Activity
janicduplessis commentedon Oct 5, 2026
Claimed: feat/2476-macos-build-offload
janicduplessis commentedon Oct 5, 2026
Step 1 findings: can the SwiftPM
stim macosbuild run on a build machineMeasured on this Mac with a worktree build of
main(8c52b89) and the mini'sbuild.offer. No SSH was used.Toolchain. The mini's offer reports arm64 and
Xcode 27.0 / Build version 27A266a, the same as this Mac.swift buildruns from that Xcode (swift 6.4, macOS SDK 27.0 here), so the offload comparison for macOS isstimBuild,arch,xcodeand the macOS SDK version. The offer has no macOS SDK field yet; the PR adds one next tosimulatorSdk.SwiftPM on the worker.
apps/desktopneeds onlyswift build -c debug --product StimDesktopand network access to GitHub for its pinned packages and the Lottie, Sparkle and Sentry binary artifacts (it resolves by revision, soPackage.resolvedis exact). Nothing else is installed on the build host (no pnpm, no Pods). Plan for caches: pass--cache-path <client area>/cache/swiftpmso each client has its own checkouts and artifact cache instead of sharing the worker user's~/Library/Caches/org.swift.swiftpm, and keep the scratch path inside the client's repo area so a repeated build is incremental, as Gradle and Xcode are today. The worker must not runpnpm installfor this job kind, which the current worker would do for a repo withpnpm-lock.yaml.Signing.
stageBundlealready signs withcodesign --sign -, which needs no account and no keychain, so it runs unchanged on the worker. The bundle id is derived fromsha256(root), so the client sends the id inbuild.startand the worker stamps it. A later hosted launch re-stamps and re-signs on its host anyway.Timing. Cold
swift build -c debug --product StimDesktop --jobs 2took 1 min 7 s end to end on this loaded MacBook (55 s compile, dependencies resolved from cache). Astim macosrun from the CLI took 67 s, then launched and was stopped.Is the
stim macoscopy a usable Stim Desktop test copy? It builds, launches, runs under its own bundle id (dev.stim.desktop.development.stim.<hash>), signs ad hoc and is thin arm64 (94 MB). It is not equivalent tobundle.sh.stageBundlecopies only the executable,*.frameworkand SwiftPM*.bundledirectories, andContents/Resourcesends up empty.bundle.shadditionally adds the compiled asset catalog and app icon, the Inter and JetBrains Mono fonts, the Lottie JSON and branding artwork from../../website, the license files and thesim-foldhelper. So the SwiftPM path gives a working app with fallback fonts and no artwork or sim-fold, not a replacement formini-desktop.sh's fidelity. That is independent of offload and out of scope here; it belongs to whichever track wants Desktop as the main use case, and I will file it separately.Cache key and artifact.
stim macoshas no build cache today: every run rebuilds intomacos/buildunder the workspace, so there is nothing to key or store, and offload adds none. The artifact is the staged<Product>.app, tarred by the worker and returned digest-verified throughbuild.artifact. Thefingerprintthatbuild.startrequires is, for macOS, a digest over the synced manifest (path, kind, sha256 of every file, sorted). The client computes it, the worker recomputes it from the manifest it mirrored and refuses on a mismatch, and no artifact is stored under it."Identical in content". Not achievable as byte equality, even locally: two local builds of the same commit at different scratch paths differ in the executable (UUID,
__text, 18 MB of differing bytes) because Debug binaries embed absolute build and source paths. I will check the achievable criterion in the real run instead: the same file set,Info.plist, frameworks and resources, and an executable that launches and passescodesign --verify.Blocker for the real two-Mac run.
build.startrefuses unless the worker'sstimBuild(a digest of its bundled Stim.mjsfiles) equals the client's. Any worker change therefore needs the mini's stim-server to run this PR's build before a two-Mac run can succeed, and SSH is off the table. Until then I verify the worker against realswift,codesignandtarlocally, with unit tests for the job kind, the fallback and the stale-cache invariants, and run the mini once it carries this build.Scope. The artifact comes back to the client. The same-node hand-over to track A's hosted session stays out of this PR, as the issue says, until it has its own contract comment. The client helper returns a staged
.appwith a caller-chosen bundle id, so #2475 can call it with the base id for hosted delivery.janicduplessis commentedon Oct 5, 2026
Merged in #2482 (4c6542d). The offload path was verified end to end against real swift, codesign and tar through a local stand-in for the tailnet (this branch's client, stim-server and worker). The two-Mac run against the mini is still open:
build.startrequires the worker's Stim build digest to equal the client's, so it needs the mini's stim-server to run a build that includes #2482 (no SSH was used). After that, runSTIM_OFFLOAD_MODE=force stim macosfromapps/desktopand from a hostedstim macos --hostrun, to confirm the artifact reaches track A's delivery.