Skip to content

Commit d08c600

Browse files
committed
[CI] Select latest stable Xcode on macOS
Use a local composite action to pick the newest non-beta Xcode and log the selected toolchain so macOS CI follows runner image updates. Include the selected Xcode in the dev environment cache key and let set_env_defaults.sh choose LLVM projects. Signed-off-by: Thomas Alexander <talexander@nvidia.com>
1 parent 74632a4 commit d08c600

3 files changed

Lines changed: 166 additions & 6 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Set up latest stable Xcode
2+
description: Select the newest non-beta Xcode installed on the macOS runner.
3+
4+
outputs:
5+
version:
6+
description: Selected Xcode version.
7+
value: ${{ steps.select.outputs.version }}
8+
build:
9+
description: Selected Xcode build number.
10+
value: ${{ steps.select.outputs.build }}
11+
path:
12+
description: Selected Xcode application path.
13+
value: ${{ steps.select.outputs.path }}
14+
developer-dir:
15+
description: Selected Xcode developer directory.
16+
value: ${{ steps.select.outputs.developer-dir }}
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- id: select
22+
shell: bash
23+
run: |
24+
set -euo pipefail
25+
26+
# Mirrors the selection strategy from maxim-lobanov/setup-xcode
27+
# (scan installed Xcode apps, ignore beta releases, choose newest
28+
# stable), but keeps it local to avoid external-action allowlist issues.
29+
selection_file=$(mktemp)
30+
python3 > "$selection_file" <<'PY'
31+
from pathlib import Path
32+
import plistlib
33+
import re
34+
import sys
35+
36+
def semver_key(version):
37+
return tuple(int(part) for part in re.findall(r"\d+", version))
38+
39+
versions = []
40+
for app in Path("/Applications").iterdir():
41+
if not app.is_dir() or app.is_symlink():
42+
continue
43+
if not re.match(r"^Xcode.*\.app$", app.name):
44+
continue
45+
46+
version_plist = app / "Contents" / "version.plist"
47+
license_plist = app / "Contents" / "Resources" / "LicenseInfo.plist"
48+
if not version_plist.exists() or not license_plist.exists():
49+
continue
50+
51+
with version_plist.open("rb") as handle:
52+
version_info = plistlib.load(handle)
53+
with license_plist.open("rb") as handle:
54+
license_info = plistlib.load(handle)
55+
56+
version = str(version_info.get("CFBundleShortVersionString", ""))
57+
build = str(version_info.get("ProductBuildVersion", ""))
58+
license_type = str(license_info.get("licenseType", "")).lower()
59+
if not version or not license_type or "beta" in license_type:
60+
continue
61+
62+
versions.append((semver_key(version), version, build, str(app)))
63+
64+
if not versions:
65+
print("No stable Xcode installation found in /Applications.", file=sys.stderr)
66+
sys.exit(1)
67+
68+
_, version, build, path = sorted(versions, reverse=True)[0]
69+
print(path)
70+
print(version)
71+
print(build)
72+
PY
73+
74+
xcode_path=$(sed -n '1p' "$selection_file")
75+
xcode_version=$(sed -n '2p' "$selection_file")
76+
xcode_build=$(sed -n '3p' "$selection_file")
77+
rm -f "$selection_file"
78+
79+
developer_dir="$xcode_path/Contents/Developer"
80+
if [ ! -d "$developer_dir" ]; then
81+
echo "Could not find Xcode developer directory: $developer_dir"
82+
exit 1
83+
fi
84+
85+
sudo xcode-select -s "$developer_dir"
86+
echo "DEVELOPER_DIR=$developer_dir" >> "$GITHUB_ENV"
87+
echo "MD_APPLE_SDK_ROOT=$xcode_path" >> "$GITHUB_ENV"
88+
89+
echo "path=$xcode_path" >> "$GITHUB_OUTPUT"
90+
echo "version=$xcode_version" >> "$GITHUB_OUTPUT"
91+
echo "build=$xcode_build" >> "$GITHUB_OUTPUT"
92+
echo "developer-dir=$developer_dir" >> "$GITHUB_OUTPUT"
93+
94+
echo "Selected Xcode $xcode_version ($xcode_build) at $xcode_path"

.github/workflows/ci_macos.yml

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ jobs:
128128
with:
129129
python-version: '3.12'
130130

131+
- name: Select latest stable Xcode
132+
uses: ./.github/actions/setup-xcode-stable
133+
134+
- name: Log macOS toolchain
135+
run: |
136+
sw_vers
137+
xcodebuild -version
138+
xcrun --show-sdk-path
139+
xcrun --show-sdk-version
140+
clang --version
141+
131142
- name: Set up ccache and ORAS
132143
uses: ./.github/actions/ccache-setup
133144

@@ -158,7 +169,6 @@ jobs:
158169
run: |
159170
source scripts/set_env_defaults.sh
160171
Python3_EXECUTABLE="$(which python3)" \
161-
LLVM_PROJECTS='clang;lld;mlir;openmp;python-bindings' \
162172
LLVM_SOURCE="$HOME/.llvm-project" \
163173
bash scripts/build_llvm.sh -c Release -v -j $(sysctl -n hw.ncpu)
164174
@@ -209,6 +219,17 @@ jobs:
209219
with:
210220
python-version: ${{ matrix.python_version }}
211221

222+
- name: Select latest stable Xcode
223+
uses: ./.github/actions/setup-xcode-stable
224+
225+
- name: Log macOS toolchain
226+
run: |
227+
sw_vers
228+
xcodebuild -version
229+
xcrun --show-sdk-path
230+
xcrun --show-sdk-version
231+
clang --version
232+
212233
- name: Set up ccache and ORAS
213234
uses: ./.github/actions/ccache-setup
214235

@@ -235,7 +256,6 @@ jobs:
235256
run: |
236257
source scripts/set_env_defaults.sh
237258
Python3_EXECUTABLE="$(which python3)" \
238-
LLVM_PROJECTS='clang;lld;mlir;openmp;python-bindings' \
239259
LLVM_SOURCE="$HOME/.llvm-project" \
240260
bash scripts/build_llvm.sh -c Release -v -j $(sysctl -n hw.ncpu)
241261
@@ -291,6 +311,17 @@ jobs:
291311
with:
292312
python-version: ${{ matrix.python_version }}
293313

314+
- name: Select latest stable Xcode
315+
uses: ./.github/actions/setup-xcode-stable
316+
317+
- name: Log macOS toolchain
318+
run: |
319+
sw_vers
320+
xcodebuild -version
321+
xcrun --show-sdk-path
322+
xcrun --show-sdk-version
323+
clang --version
324+
294325
- name: Download wheel artifact
295326
uses: actions/download-artifact@v4
296327
with:
@@ -339,6 +370,17 @@ jobs:
339370
with:
340371
python-version: '3.12'
341372

373+
- name: Select latest stable Xcode
374+
uses: ./.github/actions/setup-xcode-stable
375+
376+
- name: Log macOS toolchain
377+
run: |
378+
sw_vers
379+
xcodebuild -version
380+
xcrun --show-sdk-path
381+
xcrun --show-sdk-version
382+
clang --version
383+
342384
- name: Set up ccache and ORAS
343385
uses: ./.github/actions/ccache-setup
344386

@@ -367,7 +409,6 @@ jobs:
367409
run: |
368410
source scripts/set_env_defaults.sh
369411
Python3_EXECUTABLE="$(which python3)" \
370-
LLVM_PROJECTS='clang;lld;mlir;openmp;python-bindings' \
371412
LLVM_SOURCE="$HOME/.llvm-project" \
372413
bash scripts/build_llvm.sh -c Release -v -j $(sysctl -n hw.ncpu)
373414
@@ -418,6 +459,17 @@ jobs:
418459
with:
419460
python-version: '3.12'
420461

462+
- name: Select latest stable Xcode
463+
uses: ./.github/actions/setup-xcode-stable
464+
465+
- name: Log macOS toolchain
466+
run: |
467+
sw_vers
468+
xcodebuild -version
469+
xcrun --show-sdk-path
470+
xcrun --show-sdk-version
471+
clang --version
472+
421473
- name: Download installer artifact
422474
uses: actions/download-artifact@v4
423475
with:

.github/workflows/dev_environment_macos.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,32 @@ jobs:
141141
- name: Log in to GitHub CR
142142
run: echo "${{ github.token }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
143143

144+
- name: Select latest stable Xcode
145+
uses: ./.github/actions/setup-xcode-stable
146+
147+
- name: Log macOS toolchain
148+
run: |
149+
sw_vers
150+
xcodebuild -version
151+
xcrun --show-sdk-path
152+
xcrun --show-sdk-version
153+
clang --version
154+
144155
- name: Compute cache key
145156
id: cache_key
146157
run: |
147158
# Combine build scripts hash with the runner image version to detect
148159
# tool updates (cmake, compilers, etc.) that would invalidate the cache.
160+
# Include the selected Xcode as well because the same runner image can
161+
# contain multiple SDK/compiler releases.
149162
scripts_hash=${{ needs.metadata.outputs.scripts_hash }}
150163
runner_image=${ImageVersion:-unknown}
151-
deps_hash=$(echo "${scripts_hash}-${runner_image}" | shasum -a 256 | cut -c1-8)
164+
developer_dir=${DEVELOPER_DIR:-$(xcode-select -p 2>/dev/null || echo unknown)}
165+
xcode_version=$(xcodebuild -version 2>/dev/null | shasum -a 256 | cut -c1-8 || echo unknown)
166+
deps_hash=$(echo "${scripts_hash}-${runner_image}-${developer_dir}-${xcode_version}" | shasum -a 256 | cut -c1-8)
152167
image_name="${{ needs.metadata.outputs.image_name_base }}${deps_hash}"
153168
echo "image_name=$image_name" >> $GITHUB_OUTPUT
154-
echo "Cache key: scripts_hash=$scripts_hash runner_image=$runner_image deps_hash=$deps_hash"
169+
echo "Cache key: scripts_hash=$scripts_hash runner_image=$runner_image developer_dir=$developer_dir xcode_version=$xcode_version deps_hash=$deps_hash"
155170
echo "Image name: $image_name"
156171
157172
- name: Check for cached image
@@ -182,7 +197,6 @@ jobs:
182197
# wheel jobs only change Python3_EXECUTABLE, which keeps ninja's
183198
# incremental rebuild scoped to the binding targets.
184199
source scripts/set_env_defaults.sh
185-
export LLVM_PROJECTS='clang;lld;mlir;openmp;python-bindings'
186200
187201
echo "Building prerequisites..."
188202
echo "LLVM_INSTALL_PREFIX=$LLVM_INSTALL_PREFIX"

0 commit comments

Comments
 (0)