Skip to content

Offload macOS SwiftPM Debug builds to build machines #2476

Closed

Description

@janicduplessis

Track D of #2403. Contracts: contracts comment.

Problem

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.

Independent of #2473, #2474 and #2475. It can start now.

Activity

  1. janicduplessis commented on Oct 5, 2026

    @janicduplessis
    CollaboratorAuthor

    Claimed: feat/2476-macos-build-offload

  2. janicduplessis commented on Oct 5, 2026

    @janicduplessis
    CollaboratorAuthor

    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.

  3. janicduplessis commented on Oct 5, 2026

    @janicduplessis
    CollaboratorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions