From a561a272c1d12a9cd33c2fa38fe6d8eae4baface Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 17 Mar 2026 01:00:14 -0700 Subject: [PATCH] Migrate CI/CD to WarpBuild, consolidate test jobs (#1501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Migrate CI/CD to WarpBuild, consolidate test jobs Replace all macOS runner labels across workflows: - depot-macos-latest → warp-macos-15-arm64-6x - macos-15 → warp-macos-15-arm64-6x - macos-14 → warp-macos-14-arm64-6x Consolidates tests + tests-depot into a single tests job that runs unit tests, regressions, UI tests, and lag tests sequentially on one WarpBuild runner. Ubuntu jobs remain on ubuntu-latest. Co-Authored-By: Claude Opus 4.6 * Upgrade stale zig on runners that have an outdated version pre-installed WarpBuild macos-14 ships zig 0.15.1 but the project requires 0.15.2. The install step skipped because zig was found, just outdated. Co-Authored-By: Claude Opus 4.6 * Pin zig 0.15.2 via direct tarball instead of Homebrew Homebrew's zig bottle for macOS 14 (Sonoma) is stuck at 0.15.1 but the ghostty submodule requires 0.15.2. Download zig directly from ziglang.org to guarantee the correct version on all runner images. Co-Authored-By: Claude Opus 4.6 * Fix zig tarball URL: arch-os order is aarch64-macos, not macos-aarch64 Co-Authored-By: Claude Opus 4.6 * Create /usr/local/bin and /usr/local/lib before copying zig WarpBuild runners don't have /usr/local/lib by default. Co-Authored-By: Claude Opus 4.6 * Add 20-min timeout to WarpBuild jobs Co-Authored-By: Claude Opus 4.6 * Fix UI test hang: stream output instead of variable capture, use GitHub runner for macOS 14 The OUTPUT=$(...) pattern buffers all xcodebuild output into a bash variable. For the full cmux scheme (build + UI tests), this can be hundreds of MB, causing the shell to hang. Replace with tee streaming. macOS 14 on WarpBuild consistently hangs (unit tests timeout at 20min vs 4min on macOS 15, same M4 Pro hardware). Use GitHub-hosted macos-14 runner for compat tests instead, which works on main today. Co-Authored-By: Claude Opus 4.6 * Split UI tests to GitHub-hosted runner (WarpBuild can't activate GUI apps) WarpBuild macOS VMs leave XCUIApplication stuck in "Running Background" state, causing every UI test to burn ~62s waiting for activation and timing out the job. Root cause: WarpBuild ephemeral VMs don't provide a full GUI session for app activation. Split CI into parallel jobs: - tests: WarpBuild (unit tests + regressions, ~6 min) - tests-ui: GitHub-hosted macos-15 (UI tests + lag regression) Co-Authored-By: Claude Opus 4.6 * Move tests-ui to WarpBuild with TCC permission grants Grant accessibility, post-event, and screen capture TCC permissions to Xcode and XCTest processes on WarpBuild ephemeral VMs. This should fix "Failed to activate application (Running Background)" errors that prevent XCUITests from bringing the app to foreground. Co-Authored-By: Claude Opus 4.6 * Add GUI session diagnostics and DevToolsSecurity for WarpBuild UI tests Add session diagnostics (who, console user, GUI domain, WindowServer, loginwindow) to understand WarpBuild VM session state. Also enable DevToolsSecurity and security authorizationdb for XCTest process control. Try bootstrapping GUI session if missing. Co-Authored-By: Claude Opus 4.6 * Fix TCC permissions: use Xcode-Helper + user DB (CircleCI approach) Previous TCC grants used wrong client IDs (com.apple.dt.Xcode) and only wrote to the system database. CircleCI's proven approach grants: - kTCCServiceAccessibility to com.apple.dt.Xcode-Helper (not Xcode) - kTCCServiceDeveloperTool to com.apple.Terminal - Both system AND user-level TCC databases Co-Authored-By: Claude Opus 4.6 * Reduce UI test timeout to 15s for WarpBuild expected failures WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background"). Tests still execute and report expected failures. But the 62s per-test activation timeout makes 30+ tests take 30+ minutes total. Set per-test timeout to 15s so expected failures resolve quickly. Full interactive UI test coverage runs via test-e2e.yml on GitHub-hosted runners with proper display support. Co-Authored-By: Claude Opus 4.6 * Replace XCUITest run with build + lag regression on WarpBuild WarpBuild Virtualization.framework VMs cannot activate macOS GUI apps (XCUIApplication stuck "Running Background" with 62s activation timeout per test). Tried TCC permissions, DevToolsSecurity, virtual display, reduced timeouts, nothing fixes the framework-level issue. Replace tests-ui job with tests-build-and-lag: - Build the full cmux scheme (verifies compilation) - Run workspace churn typing-lag regression (socket-based, no GUI) - XCUITests run via test-e2e.yml on GitHub-hosted runners Co-Authored-By: Claude Opus 4.6 * Move macOS 14 compat to WarpBuild (no GitHub-hosted runners) Co-Authored-By: Claude Opus 4.6 * Add diagnostic workflow to probe WarpBuild GUI activation Tests multiple app activation approaches on WarpBuild VMs: - open -a, NSWorkspace, NSRunningApplication.activate, osascript - Virtual display state before/after CGVirtualDisplay - TCC/accessibility permissions, Quartz session info - VM type detection This is a workflow_dispatch-only diagnostic to determine if XCUITest can work on WarpBuild with the right configuration. Co-Authored-By: Claude Opus 4.6 * Trigger GUI probe on branch push (workflow_dispatch needs main) Co-Authored-By: Claude Opus 4.6 * Rewrite GUI probe with Swift (Python lacks AppKit on WarpBuild) v1 failed because WarpBuild's Python isn't a framework build and can't import AppKit/Quartz. v2 uses a compiled Swift binary to test NSRunningApplication.activate(), osascript, Quartz session state, display info, and AX trust. Co-Authored-By: Claude Opus 4.6 * GUI probe v3: try 5 approaches to unlock WarpBuild screen 1. defaults write (screensaver, loginwindow, pmset) 2. automationmodetool enable-automationmode-without-authentication 3. CGSSessionSetScreenLocked private API + System Events keystroke 4. sysadminctl -screenLock off + keychain unlock 5. CGEvent simulation (mouse move + Return key to dismiss lock) Each approach is followed by an activation check to see if it worked. Co-Authored-By: Claude Opus 4.6 * Test GUI activation on macOS 14, 15, and 26 (Tahoe) Co-Authored-By: Claude Opus 4.6 * Add DerivedData and GhosttyKit caching to CI workflows Major caching improvements across ci.yml and ci-macos-compat.yml: - Cache GhosttyKit.xcframework keyed on ghostty submodule SHA (skip download on cache hit) - Cache DerivedData keyed on OS + Xcode version + Package.resolved + project.pbxproj (enables incremental builds across runs) - Remove explicit DerivedData wipe (rely on cache key invalidation) - Use download-prebuilt-ghosttykit.sh in compat workflow too This should significantly speed up macOS 14 compat tests which were taking 20+ min due to full recompilation every run. Co-Authored-By: Claude Opus 4.6 * Bump macOS 14 compat timeout to 45 min for cold cache seeding The DerivedData cache wasn't saved because the job timed out at 30 min, causing the post-job cache save step to be skipped. 45 min gives enough headroom for the first uncached run to complete and seed the cache. Subsequent runs should be much faster with incremental builds. Co-Authored-By: Claude Opus 4.6 * Use Depot runners for E2E tests (WarpBuild has screen lock on macOS 15/26) WarpBuild VMs on macOS 15 and 26 have CGSSessionScreenIsLocked=1, which prevents XCUIApplication activation. Depot runners have working GUI activation. Can switch back to WarpBuild once they fix the VM images. Co-Authored-By: Claude Opus 4.6 (1M context) * Skip smoke test on macOS 14 compat, remove GUI diagnostic workflow macOS 14 was slow because it built the full app (cmux scheme) on top of unit tests (cmux-unit scheme). Unit tests are the real compat check; smoke test runs on macOS 15 only. Also removes the temporary test-warpbuild-gui.yml diagnostic workflow. Co-Authored-By: Claude Opus 4.6 (1M context) * Replace Sonoma with Tahoe in compat matrix, drop macOS 14 Swap macOS 14 (Sonoma) for macOS 26 (Tahoe). Smoke test runs on macOS 15 only (WarpBuild screen lock blocks app activation on 26). Co-Authored-By: Claude Opus 4.6 (1M context) * Drop macOS 26 from compat matrix (zig 0.15.2 linker failure) Zig 0.15.2 can't link against the macOS 26 (Tahoe) SDK: undefined symbols for basic libc functions (_abort, _free, _fork, etc.). The zig toolchain needs an update to support Tahoe. Keep macOS 15 only for now. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Lawrence Chen Co-authored-by: Claude Opus 4.6 --- .github/workflows/build-ghosttykit.yml | 24 ++-- .github/workflows/ci-macos-compat.yml | 70 +++++++----- .github/workflows/ci.yml | 151 +++++++++++++++---------- .github/workflows/nightly.yml | 17 ++- .github/workflows/release.yml | 17 ++- .github/workflows/test-depot.yml | 17 ++- .github/workflows/test-e2e.yml | 29 +++-- tests/test_ci_self_hosted_guard.sh | 28 +++-- 8 files changed, 227 insertions(+), 126 deletions(-) diff --git a/.github/workflows/build-ghosttykit.yml b/.github/workflows/build-ghosttykit.yml index ec787452..80850f50 100644 --- a/.github/workflows/build-ghosttykit.yml +++ b/.github/workflows/build-ghosttykit.yml @@ -8,9 +8,10 @@ on: jobs: build-ghosttykit: - # Never run Depot jobs for fork pull requests (avoid billing on external PRs). + # Never run WarpBuild jobs for fork pull requests (avoid billing on external PRs). if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: depot-macos-latest + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -61,13 +62,18 @@ jobs: if: steps.check-release.outputs.exists == 'false' run: | set -euo pipefail - if ! command -v zig >/dev/null 2>&1; then - if command -v brew >/dev/null 2>&1; then - brew install zig - else - echo "zig is required to build GhosttyKit.xcframework. Install zig and retry." >&2 - exit 1 - fi + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=universal -Doptimize=ReleaseFast diff --git a/.github/workflows/ci-macos-compat.yml b/.github/workflows/ci-macos-compat.yml index 2b7e06c9..4682df7b 100644 --- a/.github/workflows/ci-macos-compat.yml +++ b/.github/workflows/ci-macos-compat.yml @@ -13,8 +13,12 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-14, macos-15] + include: + - os: warp-macos-15-arm64-6x + timeout: 20 + smoke: true runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.timeout }} steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -39,45 +43,48 @@ jobs: echo "Selected: $XCODE_APP" echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" export DEVELOPER_DIR="$XCODE_DIR" - xcodebuild -version + XCODE_VER="$(xcodebuild -version | head -1)" + echo "XCODE_VER=$XCODE_VER" >> "$GITHUB_ENV" + echo "$XCODE_VER" xcrun --sdk macosx --show-sdk-path sw_vers + - name: Cache GhosttyKit.xcframework + id: cache-ghosttykit + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: GhosttyKit.xcframework + key: ghosttykit-${{ hashFiles('.gitmodules', 'ghostty') }} + - name: Download pre-built GhosttyKit.xcframework - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: steps.cache-ghosttykit.outputs.cache-hit != 'true' run: | - set -euo pipefail - GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) - TAG="xcframework-$GHOSTTY_SHA" - URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" - echo "Downloading xcframework for ghostty $GHOSTTY_SHA" - MAX_RETRIES=30 - RETRY_DELAY=20 - for i in $(seq 1 $MAX_RETRIES); do - if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then - echo "Download succeeded on attempt $i" - break - fi - if [ "$i" -eq "$MAX_RETRIES" ]; then - echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 - exit 1 - fi - echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." - sleep $RETRY_DELAY - done - tar xzf GhosttyKit.xcframework.tar.gz - rm GhosttyKit.xcframework.tar.gz - test -d GhosttyKit.xcframework + ./scripts/download-prebuilt-ghosttykit.sh - name: Install zig run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi - - name: Clean DerivedData - run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache DerivedData + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + key: deriveddata-${{ matrix.os }}-${{ env.XCODE_VER }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.pbxproj') }} + restore-keys: | + deriveddata-${{ matrix.os }}-${{ env.XCODE_VER }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}- + deriveddata-${{ matrix.os }}-${{ env.XCODE_VER }}- - name: Cache Swift packages uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 @@ -147,6 +154,7 @@ jobs: fi - name: Create virtual display + if: matrix.smoke run: | set -euo pipefail echo "=== Display before ===" @@ -162,6 +170,7 @@ jobs: system_profiler SPDisplaysDataType 2>/dev/null || echo "(no display info)" - name: Build app for smoke test + if: matrix.smoke run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" @@ -171,6 +180,7 @@ jobs: -destination "platform=macOS" build - name: Smoke test + if: matrix.smoke run: | set -euo pipefail chmod +x scripts/smoke-test-ci.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34a52dbc..6ba43368 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Validate Depot runner guards + - name: Validate WarpBuild runner guards run: ./tests/test_ci_self_hosted_guard.sh - name: Validate create-dmg version pinning @@ -50,7 +50,10 @@ jobs: run: bun tsc --noEmit tests: - runs-on: macos-15 + # Never run WarpBuild jobs for fork pull requests (avoid billing on external PRs). + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -60,35 +63,57 @@ jobs: - name: Select Xcode run: | set -euo pipefail - XCODE_APP="$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort | tail -n 1 || true)" - if [ -z "$XCODE_APP" ]; then - XCODE_APP="/Applications/Xcode.app" + if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then + XCODE_DIR="/Applications/Xcode.app/Contents/Developer" + else + XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | sort | tail -n 1 || true)" + if [ -n "$XCODE_APP" ]; then + XCODE_DIR="$XCODE_APP/Contents/Developer" + else + echo "No Xcode.app found under /Applications" >&2 + exit 1 + fi fi - XCODE_DIR="$XCODE_APP/Contents/Developer" - if [ ! -d "$XCODE_DIR" ]; then - echo "No Xcode found under /Applications" >&2 - exit 1 - fi - echo "Selected: $XCODE_APP" echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" export DEVELOPER_DIR="$XCODE_DIR" xcodebuild -version - xcrun --sdk macosx --show-sdk-path + + - name: Cache GhosttyKit.xcframework + id: cache-ghosttykit + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: GhosttyKit.xcframework + key: ghosttykit-${{ hashFiles('.gitmodules', 'ghostty') }} - name: Download pre-built GhosttyKit.xcframework + if: steps.cache-ghosttykit.outputs.cache-hit != 'true' run: | ./scripts/download-prebuilt-ghosttykit.sh - name: Install zig run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi - - name: Clean DerivedData - run: | - # Remove stale build cache to avoid incremental build errors - rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache DerivedData + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + key: deriveddata-tests-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.pbxproj') }} + restore-keys: | + deriveddata-tests-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}- + deriveddata-tests- - name: Cache Swift packages uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 @@ -183,10 +208,14 @@ jobs: CMUX_CLI_BIN="$CLI_BIN" python3 tests/test_cli_version_memory_guard.py - tests-depot: - # Never run Depot jobs for fork pull requests (avoid billing on external PRs). + tests-build-and-lag: + # Build the full cmux scheme and run the lag regression on WarpBuild. + # XCUITests cannot run on WarpBuild (Virtualization.framework limitation: + # XCUIApplication stuck "Running Background", 62s activation timeout per + # test). Interactive UI tests run via test-e2e.yml on GitHub-hosted runners. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: depot-macos-latest + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -211,25 +240,49 @@ jobs: export DEVELOPER_DIR="$XCODE_DIR" xcodebuild -version + - name: Cache GhosttyKit.xcframework + id: cache-ghosttykit-lag + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: GhosttyKit.xcframework + key: ghosttykit-${{ hashFiles('.gitmodules', 'ghostty') }} + - name: Download pre-built GhosttyKit.xcframework + if: steps.cache-ghosttykit-lag.outputs.cache-hit != 'true' run: | ./scripts/download-prebuilt-ghosttykit.sh - name: Install zig run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi - - name: Clean DerivedData - run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache DerivedData + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + key: deriveddata-build-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.pbxproj') }} + restore-keys: | + deriveddata-build-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}- + deriveddata-build- - name: Cache Swift packages uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 with: path: .ci-source-packages - key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} - restore-keys: spm- + key: spm-build-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm-build- - name: Resolve Swift packages run: | @@ -251,6 +304,15 @@ jobs: sleep $((attempt * 5)) done + - name: Build app + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" build + - name: Create virtual display run: | set -euo pipefail @@ -261,41 +323,6 @@ jobs: echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" sleep 3 - - name: Run UI tests - run: | - set -euo pipefail - SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - # SidebarResizeUITests hangs on headless runners (mouse drag simulation - # doesn't work without a physical display, even with virtual display). - # Skip it in CI; it runs fine on local machines. - run_ui_tests() { - xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ - -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ - -disableAutomaticPackageResolution \ - -destination "platform=macOS" \ - -maximum-test-execution-time-allowance 120 \ - -only-testing:cmuxUITests \ - -skip-testing:cmuxUITests/SidebarResizeUITests test 2>&1 - } - - # xcodebuild exits 65 even for expected failures (XCTExpectFailure). - # Capture output and fail only if there are unexpected failures. - set +e - OUTPUT=$(run_ui_tests) - EXIT_CODE=$? - set -e - - echo "$OUTPUT" - if [ "$EXIT_CODE" -ne 0 ]; then - SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1) - if echo "$SUMMARY" | grep -q "(0 unexpected)"; then - echo "All failures are expected, treating as pass" - else - echo "Unexpected test failures detected" - exit 1 - fi - fi - - name: Run workspace churn typing-lag regression run: | set -euo pipefail diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8f7e48de..9ffc7fbb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -99,7 +99,8 @@ jobs: build-sign-notarize-nightly: needs: decide if: needs.decide.outputs.should_build == 'true' - runs-on: depot-macos-latest + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout build ref uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -151,8 +152,18 @@ jobs: - name: Install build deps if: needs.decide.outputs.should_publish != 'true' || steps.current_head_prebuild.outputs.still_current == 'true' run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi npm install --global "create-dmg@${CREATE_DMG_VERSION}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff6b33b1..73346b50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,8 @@ env: jobs: build-sign-notarize: - runs-on: depot-macos-latest + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -99,8 +100,18 @@ jobs: - name: Install build deps if: steps.guard_release_assets.outputs.skip_all != 'true' run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi npm install --global "create-dmg@${CREATE_DMG_VERSION}" diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index 536c5a14..c6edb871 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -28,7 +28,8 @@ on: jobs: tests: - runs-on: depot-macos-latest + runs-on: warp-macos-15-arm64-6x + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -84,8 +85,18 @@ jobs: - name: Install zig run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi - name: Create virtual display diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index a61e5eee..54d145bd 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -20,17 +20,18 @@ on: default: true type: boolean runner: - description: "Runner OS (macos-15 or macos-26)" + description: "Runner OS (Depot runners for GUI activation support)" required: false - default: "macos-15" + default: "depot-macos-latest" type: choice options: - - macos-15 - - macos-26 + - depot-macos-latest + - depot-macos-14 jobs: e2e: - runs-on: ${{ inputs.runner || 'macos-15' }} + runs-on: ${{ inputs.runner || 'depot-macos-latest' }} + timeout-minutes: 20 env: TEST_REF: ${{ inputs.ref || github.ref }} steps: @@ -92,8 +93,18 @@ jobs: - name: Install zig run: | - if ! command -v zig >/dev/null 2>&1; then - brew install zig + ZIG_REQUIRED="0.15.2" + if command -v zig >/dev/null 2>&1 && zig version 2>/dev/null | grep -q "^${ZIG_REQUIRED}"; then + echo "zig ${ZIG_REQUIRED} already installed" + else + echo "Installing zig ${ZIG_REQUIRED} from tarball" + curl -fSL "https://ziglang.org/download/${ZIG_REQUIRED}/zig-aarch64-macos-${ZIG_REQUIRED}.tar.xz" -o /tmp/zig.tar.xz + tar xf /tmp/zig.tar.xz -C /tmp + sudo mkdir -p /usr/local/bin /usr/local/lib + sudo cp -f /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/zig /usr/local/bin/zig + sudo cp -rf /tmp/zig-aarch64-macos-${ZIG_REQUIRED}/lib /usr/local/lib/zig + export PATH="/usr/local/bin:$PATH" + zig version fi - name: Create virtual display @@ -161,8 +172,8 @@ jobs: uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 with: path: .ci-source-packages - key: spm-${{ inputs.runner || 'macos-15' }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} - restore-keys: spm-${{ inputs.runner || 'macos-15' }}- + key: spm-${{ inputs.runner || 'depot-macos-latest' }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm-${{ inputs.runner || 'depot-macos-latest' }}- - name: Resolve Swift packages run: | diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index 3b4f7f65..c3a5281c 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Regression test for https://github.com/manaflow-ai/cmux/issues/385. -# Ensures Depot-hosted UI tests are never run for fork pull requests. +# Ensures paid/gated CI jobs are never run for fork pull requests. set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" @@ -9,21 +9,35 @@ WORKFLOW_FILE="$ROOT_DIR/.github/workflows/ci.yml" EXPECTED_IF="if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository" if ! grep -Fq "$EXPECTED_IF" "$WORKFLOW_FILE"; then - echo "FAIL: Missing fork pull_request guard for tests in $WORKFLOW_FILE" + echo "FAIL: Missing fork pull_request guard in $WORKFLOW_FILE" echo "Expected line:" echo " $EXPECTED_IF" exit 1 fi +# tests: must use WarpBuild runner with fork guard (paid runner) if ! awk ' - /^ tests-depot:/ { in_tests=1; next } + /^ tests:/ { in_tests=1; next } in_tests && /^ [^[:space:]]/ { in_tests=0 } - in_tests && /runs-on: depot-macos-latest/ { saw_depot=1 } + in_tests && /runs-on: warp-macos-15-arm64-6x/ { saw_warp=1 } in_tests && /github.event.pull_request.head.repo.full_name == github.repository/ { saw_guard=1 } - END { exit !(saw_depot && saw_guard) } + END { exit !(saw_warp && saw_guard) } ' "$WORKFLOW_FILE"; then - echo "FAIL: tests-depot block must keep both depot-macos-latest runner and fork guard" + echo "FAIL: tests block must keep both warp-macos-15-arm64-6x runner and fork guard" exit 1 fi -echo "PASS: tests-depot Depot runner fork guard is present" +# tests-build-and-lag: must use WarpBuild runner with fork guard (paid runner) +if ! awk ' + /^ tests-build-and-lag:/ { in_tests=1; next } + in_tests && /^ [^[:space:]]/ { in_tests=0 } + in_tests && /runs-on: warp-macos-15-arm64-6x/ { saw_warp=1 } + in_tests && /github.event.pull_request.head.repo.full_name == github.repository/ { saw_guard=1 } + END { exit !(saw_warp && saw_guard) } +' "$WORKFLOW_FILE"; then + echo "FAIL: tests-build-and-lag block must keep both warp-macos-15-arm64-6x runner and fork guard" + exit 1 +fi + +echo "PASS: tests WarpBuild runner fork guard is present" +echo "PASS: tests-build-and-lag WarpBuild runner fork guard is present"