diff --git a/.github/workflows/cli-release-build.yml b/.github/workflows/cli-release-build.yml index a7ea66bfef..2095173d1b 100644 --- a/.github/workflows/cli-release-build.yml +++ b/.github/workflows/cli-release-build.yml @@ -219,6 +219,35 @@ jobs: mkdir -p "$LEGACY_DIR/ripgrep" tar -xzf "$LEGACY_DIR/ripgrep.tar.gz" \ -C "$LEGACY_DIR/ripgrep" --strip-components=1 + + # Select a usable Xcode with clang before cargo needs a C linker. + # Some runner images leave a broken default (e.g. Xcode_15.4 without clang). + DEVELOPER_DIR="" + CANDIDATES=() + while IFS= read -r app; do + CANDIDATES+=("$app") + done < <(ls -1d /Applications/Xcode_*.app 2>/dev/null | sort -V -r || true) + if [[ -d /Applications/Xcode.app ]]; then + CANDIDATES+=("/Applications/Xcode.app") + fi + for app in "${CANDIDATES[@]}"; do + if [[ -x "$app/Contents/Developer/usr/bin/clang" ]]; then + DEVELOPER_DIR="$app/Contents/Developer" + break + fi + done + if [[ -z "$DEVELOPER_DIR" ]]; then + echo "error: no Xcode app with an executable clang under /Applications" >&2 + echo "Available apps:" >&2 + ls -la /Applications/Xcode*.app 2>/dev/null || echo "(none)" >&2 + exit 1 + fi + sudo xcode-select -s "$DEVELOPER_DIR" + export DEVELOPER_DIR + echo "Selected DEVELOPER_DIR=$DEVELOPER_DIR" + xcodebuild -version + clang --version + pushd "$LEGACY_DIR/ripgrep" MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --locked popd @@ -239,6 +268,35 @@ jobs: https://github.com/BurntSushi/ripgrep/archive/refs/tags/14.1.1.tar.gz tar -xzf "$RG_DIR/ripgrep.tar.gz" \ -C "$RG_DIR/source" --strip-components=1 + + # Select a usable Xcode with clang before cargo needs a C linker. + # Some runner images leave a broken default (e.g. Xcode_15.4 without clang). + DEVELOPER_DIR="" + CANDIDATES=() + while IFS= read -r app; do + CANDIDATES+=("$app") + done < <(ls -1d /Applications/Xcode_*.app 2>/dev/null | sort -V -r || true) + if [[ -d /Applications/Xcode.app ]]; then + CANDIDATES+=("/Applications/Xcode.app") + fi + for app in "${CANDIDATES[@]}"; do + if [[ -x "$app/Contents/Developer/usr/bin/clang" ]]; then + DEVELOPER_DIR="$app/Contents/Developer" + break + fi + done + if [[ -z "$DEVELOPER_DIR" ]]; then + echo "error: no Xcode app with an executable clang under /Applications" >&2 + echo "Available apps:" >&2 + ls -la /Applications/Xcode*.app 2>/dev/null || echo "(none)" >&2 + exit 1 + fi + sudo xcode-select -s "$DEVELOPER_DIR" + export DEVELOPER_DIR + echo "Selected DEVELOPER_DIR=$DEVELOPER_DIR" + xcodebuild -version + clang --version + pushd "$RG_DIR/source" MACOSX_DEPLOYMENT_TARGET=13.0 cargo build --release --locked popd diff --git a/scripts/__tests__/release-workflow.test.ts b/scripts/__tests__/release-workflow.test.ts index f924d4ec49..1a93290fa4 100644 --- a/scripts/__tests__/release-workflow.test.ts +++ b/scripts/__tests__/release-workflow.test.ts @@ -7,6 +7,11 @@ const workflow = readFileSync( 'utf8', ) +const buildWorkflow = readFileSync( + resolve(import.meta.dir, '../../.github/workflows/cli-release-build.yml'), + 'utf8', +) + describe('production release workflow', () => { test('builds the committed release source before creating its tag', () => { const prepareStart = workflow.indexOf(' prepare-and-commit-prod:') @@ -42,3 +47,32 @@ describe('production release workflow', () => { expect(workflow).toContain('test "$existing_commit" = "$RELEASE_COMMIT"') }) }) + +describe('CLI release build workflow', () => { + test('selects a usable Xcode before macOS cargo builds that need a C linker', () => { + const legacyStep = 'Prepare macOS 11 legacy toolchain' + const intelRipgrepStep = 'Rebuild Intel ripgrep for macOS 13' + const legacyStart = buildWorkflow.indexOf(legacyStep) + const intelStart = buildWorkflow.indexOf(intelRipgrepStep) + const legacyCargo = buildWorkflow.indexOf( + 'MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --locked', + ) + const intelCargo = buildWorkflow.indexOf( + 'MACOSX_DEPLOYMENT_TARGET=13.0 cargo build --release --locked', + ) + + expect(legacyStart).toBeGreaterThan(-1) + expect(intelStart).toBeGreaterThan(-1) + expect(legacyCargo).toBeGreaterThan(legacyStart) + expect(intelCargo).toBeGreaterThan(intelStart) + + const legacyBlock = buildWorkflow.slice(legacyStart, legacyCargo) + const intelBlock = buildWorkflow.slice(intelStart, intelCargo) + + for (const block of [legacyBlock, intelBlock]) { + expect(block).toContain('xcode-select -s') + expect(block).toContain('export DEVELOPER_DIR') + expect(block).toContain('/Contents/Developer/usr/bin/clang') + } + }) +})