CI / GitHub Actions

Workflow: .github/workflows/build.yml

Job

One job, build-and-test, on the self-hosted macOS runner. It runs on every push to main, on pull requests targeting main, and on a manual run (workflow_dispatch). Steps run in order and the job stops at the first failure:

StepWhat it does
Build & test Shared packagecd Shared && swift build && swift test
Build iOS appxcodebuild build, scheme Minuta, iPhone 17 simulator, Debug
Run unit testsxcodebuild test -only-testing:MinutaTests on the simulator
Run UI testsxcodebuild test -only-testing:MinutaUITests on the simulator
Build Mac Catalyst appxcodebuild build, scheme Minuta, platform=macOS,variant=Mac Catalyst, Debug
Run unit tests (Mac Catalyst)xcodebuild test -only-testing:MinutaTests on Catalyst
Run UI tests (Mac Catalyst)xcodebuild test -only-testing:MinutaUITests on Catalyst; manual runs only

Signing

The simulator steps turn code signing off (CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO): the simulator runs unsigned binaries.

The Catalyst steps cannot do that and pass two other settings instead:

  • CODE_SIGN_IDENTITY=- signs ad hoc. A Mac Catalyst test runner has to be signed to launch at all, and ad-hoc signing needs no certificate or provisioning profile on the runner.
  • ENABLE_HARDENED_RUNTIME=NO. With the hardened runtime on, library validation refuses to load an ad-hoc signed test bundle into the host process, so the tests would never start.

The Minuta scheme builds the app and the two test bundles only. The MinutaWidgets extension is not listed in the scheme: on iOS it is built through the app’s platformFilter: iOS dependency, and a Catalyst build never builds it. When the scheme listed it, every Catalyst build of the scheme failed on the widget’s missing development profile.

Mac Catalyst UI tests run by hand

The Catalyst UI tests drive real windows in the runner’s interactive session, and the runner is the development Mac: on every push they took the screen over for about 20 minutes and failed in bulk (“unable to find hit point”) whenever someone was using the machine. The step has if: github.event_name == 'workflow_dispatch', so a push or a pull request skips it and still builds the Catalyst app and runs MinutaTests on it. Start a full run when the machine is free:

gh workflow run build.yml --ref main

Locally the same suites run through ./scripts/run-uitests.sh (iPhone simulator) and ./scripts/run-uitests.sh --mac (Mac Catalyst), which also export the screenshots.

Self-hosted runner

Runs on the development Mac as a launchd service. Setup lives in the hogen monorepo: ../hogen/gh-mac-runner.

Runner labels: self-hosted, macOS, X64

Why self-hosted

GitHub-hosted macOS runners cost $0.08/min with a 10x multiplier on the free tier (2000 min/month = effectively 200 macOS minutes). A single build uses ~50 billed minutes, so the free tier covers ~4 builds/month. Self-hosted is free with no limits.

Management

~/actions-runner/svc.sh status   # check
~/actions-runner/svc.sh stop     # stop
~/actions-runner/svc.sh start    # start

Workflow file

runs-on: [self-hosted, macOS, X64]

No xcode-select step needed — uses whatever Xcode is installed on the runner Mac.