From e64d767948559b51b3015b84dc03f4d1c500c545 Mon Sep 17 00:00:00 2001 From: Ajoy L Date: Sun, 13 Sep 2026 18:38:06 -0500 Subject: [PATCH] ci(binaries): build the macOS Intel asset on an Intel runner and assert binary architecture The aisbom-macos-amd64 leg ran on an arm64 runner with an x64 Python requested. PyInstaller builds for the running interpreter's architecture, and the published Intel asset came out arm64. Build it on macos-15-intel instead, and read each binary's architecture with lipo/file before the smoke test so a mismatched build fails the release rather than being uploaded under the wrong name. --- .github/workflows/binaries.yml | 53 ++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 608a075..3301649 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -40,7 +40,9 @@ jobs: # Build CLI binaries for v1.x.x releases (aligned CLI + Action). if: startsWith(github.event.release.tag_name, 'v1.') needs: gate - name: Build binary on ${{ matrix.os }} + # Named by asset, not runner: two legs used to share a runner label, which + # made their logs indistinguishable in `gh run view`. + name: Build ${{ matrix.asset_name }} runs-on: ${{ matrix.os }} strategy: matrix: @@ -48,12 +50,19 @@ jobs: - os: ubuntu-latest asset_name: aisbom-linux-amd64 python_arch: x64 - - os: macos-14 # Run Intel build on M1 via Rosetta 2 + expected_arch: x86_64 + # PyInstaller builds for the architecture of the interpreter running + # it. Requesting an x64 Python on an ARM runner does not reliably + # yield one, and past Intel assets silently came out arm64. Build + # Intel on an Intel runner so the smoke test also executes it there. + - os: macos-15-intel asset_name: aisbom-macos-amd64 - python_arch: x64 - - os: macos-14 # Run Silicon build natively + python_arch: x64 + expected_arch: x86_64 + - os: macos-14 asset_name: aisbom-macos-arm64 python_arch: arm64 + expected_arch: arm64 steps: - name: Checkout code @@ -62,8 +71,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v7 with: - python-version: "3.12" - architecture: ${{ matrix.python_arch }} + python-version: "3.12" + architecture: ${{ matrix.python_arch }} - name: Install Poetry uses: snok/install-poetry@v1 @@ -77,6 +86,38 @@ jobs: - name: Build binary run: ./scripts/build_binaries.sh + # The smoke test below executes the binary on the runner that built it, + # so it cannot tell a wrong-architecture build from a correct one when + # the runner can run both. Read the architecture out of the executable + # itself and refuse to publish it under a name that says otherwise. + - name: Assert binary architecture + shell: bash + env: + EXPECTED_ARCH: ${{ matrix.expected_arch }} + run: | + set -euo pipefail + BIN="dist/aisbom" + echo "runner: $(uname -s) $(uname -m)" + file "$BIN" + + if [ "$(uname -s)" = "Darwin" ]; then + # Exact match: a universal binary would list both architectures + # and is not what these per-arch assets promise. + actual="$(lipo -archs "$BIN")" + else + case "$(file -b "$BIN")" in + *x86-64*) actual="x86_64" ;; + *aarch64*) actual="arm64" ;; + *) actual="unknown" ;; + esac + fi + + if [ "$actual" != "$EXPECTED_ARCH" ]; then + echo "FAIL: ${{ matrix.asset_name }} is '$actual', expected '$EXPECTED_ARCH'" + exit 1 + fi + echo "OK: ${{ matrix.asset_name }} is $actual" + # This workflow is the only path these executables take to users, and it # runs solely on `release: published` — no PR check ever exercises it. # A successful build does not prove the binary runs: a PyInstaller