diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 54f286f..0a14c71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -259,3 +259,151 @@ jobs: path: build/scan-build retention-days: 7 if-no-files-found: ignore + runtime-macos: + name: Runtime (macOS Apple Silicon, HVF) + needs: build-macos + if: > + github.repository == 'sysprog21/elfuse' && + (github.event_name == 'push' || github.event_name == 'pull_request') + runs-on: [self-hosted, macOS, arm64] + timeout-minutes: 20 + + # contents: read for the checkout; pull-requests: read so the guard can + # query the PR's current HEAD. (actions: write would let the guard + # cancel the run instead of failing it, but repo policy caps the token + # at actions: read, so the guard fails fast with a clear reason instead.) + permissions: + contents: read + pull-requests: read + + concurrency: + group: runtime-macos-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + env: + LINUX_TOOLCHAIN: /opt/toolchain/aarch64-linux-gnu + GNU_OBJCOPY: /opt/homebrew/opt/binutils/bin/objcopy + HOMEBREW_NO_INSTALL_CLEANUP: 1 + HOMEBREW_NO_AUTO_UPDATE: 1 + BREW_PKGS: binutils qemu + + steps: + # Fail fast if this run targets a commit that is no longer the PR's + # HEAD. cancel-in-progress covers "commit 2 pushed while commit 1 is + # still running", but NOT a manual "Re-run jobs" on an old run: a + # re-run replays the original event payload (a frozen head.sha) + # against this single self-hosted runner, which would otherwise burn + # the full job timeout re-testing stale code. Compare the frozen + # head.sha against the live PR HEAD; when they differ, exit 1 with a + # clear "commit is no longer the latest" message. We fail (rather than + # cancel) because repo policy caps the token at actions: read, so the + # cancel API is unavailable. exit 1 also stops the job, so the later + # steps are skipped automatically -- no per-step guard needed. The + # lookup fails open: if HEAD can't be determined the job runs. + - name: Fail fast if superseded by a newer PR commit + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -uo pipefail + # curl and system python3 are always present on macOS; jq/gh are + # not guaranteed on a self-hosted runner, so don't depend on them. + latest=$(curl -fsSL \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/pulls/$PR_NUMBER" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["head"]["sha"])') \ + || latest="" + echo "Run targets : $RUN_SHA" + echo "PR HEAD now : ${latest:-}" + if [ -n "$latest" ] && [ "$latest" != "$RUN_SHA" ]; then + echo "::error::This run targets $RUN_SHA, but PR #$PR_NUMBER HEAD is now $latest -- the commit is no longer the latest. Failing instead of re-testing stale code on the self-hosted runner; re-run CI on the current commit." + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v6 + + - name: Host info + run: | + sw_vers + uname -a + uname -m + sysctl kern.hv_support || true + test "$(uname -m)" = "arm64" + + - name: Cache Homebrew downloads + uses: actions/cache@v5 + with: + path: ~/Library/Caches/Homebrew/downloads + key: brew-runtime-${{ runner.os }}-${{ runner.arch }}-${{ env.BREW_PKGS }} + + - name: Install missing Homebrew packages + run: | + missing=() + + for pkg in $BREW_PKGS; do + if ! brew list --formula "$pkg" >/dev/null 2>&1; then + missing+=("$pkg") + fi + done + + if [ "${#missing[@]}" -gt 0 ]; then + brew install --quiet "${missing[@]}" + else + echo "All Homebrew packages are already installed: $BREW_PKGS" + fi + + - name: Tool versions + run: | + command -v make + command -v "$GNU_OBJCOPY" + make -V .MAKE.VERSION 2>/dev/null || true + "$GNU_OBJCOPY" --version | head -1 + qemu-aarch64 --version | head -1 || true + python3 --version + + - name: Check Rosetta for Linux + run: | + ROSETTA=/Library/Apple/usr/libexec/oah/RosettaLinux/rosetta + + if [ ! -x "$ROSETTA" ]; then + echo "::error::Rosetta for Linux runtime was not found at $ROSETTA" + echo + echo "Install Rosetta on the self-hosted Mac runner first:" + echo " sudo softwareupdate --install-rosetta --agree-to-license" + echo + echo "Current /Library/Apple/usr/libexec/oah contents:" + ls -R /Library/Apple/usr/libexec/oah || true + exit 1 + fi + + ls -l "$ROSETTA" + + - name: Build elfuse + run: | + make elfuse + + - name: Verify HVF entitlement is embedded + run: | + codesign -d --entitlements - build/elfuse 2>&1 \ + | grep -q 'com\.apple\.security\.hypervisor' + + - name: test-hello + run: | + make test-hello + + - name: test-multi-vcpu + run: | + make test-multi-vcpu + + - name: make check + run: | + make check + + - name: Test matrix + run: | + bash tests/test-matrix.sh all