Skip to content

CI: GitHub Actions workflow to build and upload PR artifacts #575

Description

@dustinrue

Overview

Add a GitHub Actions workflow that builds the app on every pull request, assembles a .app bundle, and uploads it as a downloadable artifact so reviewers can test changes without building locally.

Signing approach for PR builds

PR artifacts use ad-hoc signing (codesign --sign -) — the same approach the Makefile already uses. This requires no Apple Developer account, no secrets, and no CI configuration beyond the build itself. Reviewers download the artifact, right-click → Open to bypass Gatekeeper on first launch (one-time prompt), and test normally.

Ad-hoc signing is appropriate for PR review builds. Proper Developer ID signing and notarization is tracked separately in #523 and will apply to release builds.

Makefile prerequisite

The current make run target assembles the bundle and immediately kills/relaunches the app — this cannot run in CI. A new make bundle target is needed that assembles the .app without launching it:

## Assemble the app bundle without launching (used by CI)
bundle: build install
	@mkdir -p "$(APP_BUNDLE)/Contents/MacOS"
	@mkdir -p "$(APP_BUNDLE)/Contents/Resources"
	cp $(INFO_PLIST) "$(APP_BUNDLE)/Contents/Info.plist"
	cp $(UNIV_DIR)/ControlPlane "$(APP_BINARY)"
	cp $(UNIV_DIR)/cpctl "$(APP_BUNDLE)/Contents/MacOS/cpctl"
	cp $(ICON) "$(APP_BUNDLE)/Contents/Resources/AppIcon.icns"
	codesign --force --deep --sign - --identifier "com.controlplane.app" "$(APP_BUNDLE)"
	@echo "Bundle assembled → $(APP_BUNDLE)"

GitHub Actions workflow

File: .github/workflows/pr-build.yml

name: PR Build

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode.app

      - name: Build and assemble bundle
        run: make bundle

      - name: Zip app bundle
        run: |
          cd .build/universal/debug
          zip -r ControlPlane.zip ControlPlane.app

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ControlPlane-PR${{ github.event.pull_request.number }}
          path: .build/universal/debug/ControlPlane.zip
          retention-days: 14

Notes

  • macos-latest runners include Swift and the full Xcode toolchain
  • Universal binary build (arm64 + x86_64 via lipo) works on GitHub-hosted runners — both architectures are cross-compiled on the same machine
  • Build time will be several minutes on first run (cold SPM cache); subsequent runs benefit from dependency caching via actions/cache on .build/checkouts
  • The artifact is named after the PR number for easy identification
  • 14-day retention keeps artifacts available for the review window without accumulating indefinitely

Future work

When #523 (code signing and notarization) is implemented, a separate release.yml workflow can be added that triggers on version tags, uses stored Developer ID certificates, notarizes the bundle, and publishes a GitHub Release.

Activity

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

    area: coreCore engine, scheduling, architectureroadmapOfficially planned — tracked in CLAUDE.md and actively managed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions