From 93bba872fbdf2aec491253135684d110527c8b11 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Thu, 17 Sep 2026 01:32:48 +0800 Subject: [PATCH] Link macOS releases with Xcode 26.3 Every main push since the linkage guard landed fails it: Xcode 16.4 on the macos-15 runner drops WebRTC's NSString category even with -ObjC, while a 26 linker keeps it. Build with Xcode 26.3 through DEVELOPER_DIR and pin MACOSX_DEPLOYMENT_TARGET to 11.0, since cc would otherwise target the 26 SDK, so the release still loads on macOS 15. The verify step now also checks the linker, the minimum OS and a launch. --- .github/workflows/check.yml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 94278b6d..f41ced47 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -549,6 +549,7 @@ jobs: - name: macOS arm64 os: macos-15 target: aarch64-apple-darwin + xcode: '26.3' extension: '' archive: zip - name: Windows x86_64 @@ -571,12 +572,23 @@ jobs: ${{ matrix.target == 'x86_64-pc-windows-msvc' && '-C target-feature=+crt-static' || contains(matrix.target, '-apple-') && '-Clink-arg=-ObjC' || '' }} + # The runner's default Xcode 16.4 links WebRTC's NSString category away + # even with -ObjC, while a 26 linker, matching the 26.0 SDK the archive + # was built with, keeps it. DEVELOPER_DIR picks the newer Xcode for every + # tool without touching the machine-wide xcode-select. + DEVELOPER_DIR: >- + ${{ matrix.xcode && format('/Applications/Xcode_{0}.app/Contents/Developer', matrix.xcode) || '' }} + # Unset, cc targets the selected SDK, so Xcode 26 would build the C and + # C++ objects for macOS 26 while rustc stamps the binary 11.0. Pin both + # to the 11.0 floor earlier releases shipped with. + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.xcode && '11.0' || '' }} steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - # The image is part of the key, because on this leg it is the compiler. + # The toolchain is part of the key: the image on Linux, where it is the + # compiler, and the Xcode on macOS, where it is the linker and the SDK. # `rust-cache` keys on the runner's rustc, which is no longer the one that # builds here, and it declines to save when an exact key hit came back: a # `target/` filled by the old runner build therefore survives under this @@ -586,7 +598,7 @@ jobs: # its own entry, and bumping the pin rotates the cache with it. - uses: Swatinem/rust-cache@v2 with: - key: release-${{ matrix.target }}${{ matrix.image && format('-{0}', matrix.image) || '' }} + key: release-${{ matrix.target }}${{ matrix.image && format('-{0}', matrix.image) || '' }}${{ matrix.xcode && format('-xcode{0}', matrix.xcode) || '' }} # The same entry the check job saves, on the same key. Without it all # three platform legs re-download 24 MB that one job already fetched and # stored on this commit, and the fetch script no-ops on a hit. @@ -657,7 +669,19 @@ jobs: shell: bash run: | binary=target/${{ matrix.target }}/release/codetrial - # Collect the output so grep cannot give otool SIGPIPE under pipefail. + # Prove the build used the Xcode DEVELOPER_DIR names, not the default. + [[ $(xcrun --find ld) == "$DEVELOPER_DIR"/* ]] \ + || { echo "ld is not from $DEVELOPER_DIR" >&2; exit 1; } + # Collect the output so awk and grep cannot give otool SIGPIPE under + # pipefail. + load=$(otool -l "$binary") + minos=$(awk '$1 == "minos" { print $2 }' <<< "$load") + [[ $minos == "$MACOSX_DEPLOYMENT_TARGET" ]] \ + || { echo "minos is $minos, want $MACOSX_DEPLOYMENT_TARGET" >&2; exit 1; } + # Launching proves dyld accepts the binary on this macOS 15 runner. + # --help returns before any WebRTC code runs, so it says nothing + # about the categories; the otool match below does. + "$binary" --help >/dev/null objc=$(otool -oV "$binary") grep -Eq '^[[:space:]]*name[[:space:]].*[[:space:]]stringForAbslStringView:$' <<< "$objc" \ || { echo 'WebRTC NSString category is missing' >&2; exit 1; } @@ -913,4 +937,3 @@ jobs: # deletes above are for. gh release edit "$staged" --tag "$tag" --target "$GITHUB_SHA" --draft=false published=true -