From 265ca345a1ea60a782e3e30e4b0436fda7a9a679 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Jul 2026 10:49:26 +0100 Subject: [PATCH] ci: pin frozen iOS formatter (#105242) * ci: pin frozen iOS formatter * docs: clarify release CI dispatch * ci: preserve frozen iOS formatter path * style: format CI workflow guard --- .github/workflows/ci.yml | 44 +++++++++++++++++++++++-- AGENTS.md | 1 + test/scripts/ci-workflow-guards.test.ts | 29 ++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 539b2c258e83..e67c461190af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2322,6 +2322,8 @@ jobs: if: needs.preflight.outputs.run_ios_build == 'true' runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-26' || (github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-26' || 'macos-26') }} timeout-minutes: 45 + env: + HISTORICAL_TARGET: ${{ needs.preflight.outputs.compatibility_target }} steps: - *platform_checkout_step @@ -2357,9 +2359,47 @@ jobs: echo "$swift_tools_dir" >> "$GITHUB_PATH" "$swift_tools_dir/swiftformat" --version "$swift_tools_dir/swiftlint" version + elif [[ "$HISTORICAL_TARGET" == "true" ]]; then + # The generated Xcode project runs SwiftFormat during the build, so + # frozen targets must keep the formatter contract they were authored for. + brew install xcodegen swiftlint + swiftformat_min_version="$(awk '$1 == "--min-version" { print $2; exit }' config/swiftformat)" + case "$swiftformat_min_version" in + ""|0.61.1) + swiftformat_version="0.61.1" + swiftformat_checksum="b990400779aceb7d7020796eb9ba814d4480543f671d38fc0ff48cb72f04c584" + ;; + 0.62.1) + swiftformat_version="0.62.1" + swiftformat_checksum="7cb1cb1fae04932047c7015441c543848e8e60e1572d808d080e0a1f1661114a" + ;; + *) + echo "Unsupported frozen-target SwiftFormat minimum: $swiftformat_min_version" >&2 + exit 1 + ;; + esac + swiftformat_archive="$RUNNER_TEMP/swiftformat-$swiftformat_version.zip" + swift_tools_dir="$RUNNER_TEMP/openclaw-legacy-swift-tools" + curl --fail --location --silent --show-error --retry 3 \ + --output "$swiftformat_archive" \ + "https://github.com/nicklockwood/SwiftFormat/releases/download/$swiftformat_version/swiftformat.zip" + if [[ "$(shasum -a 256 "$swiftformat_archive" | awk '{print $1}')" != "$swiftformat_checksum" ]]; then + echo "SwiftFormat $swiftformat_version archive checksum mismatch" >&2 + exit 1 + fi + mkdir -p "$swift_tools_dir" + unzip -q "$swiftformat_archive" -d "$swift_tools_dir" + chmod +x "$swift_tools_dir/swiftformat" + echo "$swift_tools_dir" >> "$GITHUB_PATH" + [[ "$("$swift_tools_dir/swiftformat" --version)" == "$swiftformat_version" ]] + # Legacy generated Xcode phases prepend Homebrew ahead of GITHUB_PATH. + # Point that lookup at the verified binary or the build can bypass the pin. + swiftformat_link="$(brew --prefix)/bin/swiftformat" + ln -sfn "$swift_tools_dir/swiftformat" "$swiftformat_link" + [[ "$("$swiftformat_link" --version)" == "$swiftformat_version" ]] else - # Frozen release targets before the pinned installer used Homebrew directly. - brew install xcodegen swiftlint swiftformat + echo "Current CI targets must provide scripts/install-swift-tools.sh." >&2 + exit 1 fi - name: Swift lint diff --git a/AGENTS.md b/AGENTS.md index 6cec74953147..61444bf70918 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -186,6 +186,7 @@ Skills own workflows; root owns hard policy and routing. - Contributor PRs: parsed context requires authored `What Problem This Solves` and `Evidence` sections. Do not require field-level proof forms; reviewers inspect code, tests, and CI for correctness. - PR artifacts/screenshots: attach to PR/comment/external artifact store. Never push screenshots, videos, proof images, or proof assets to OpenClaw or any product repo branch, including temp artifact branches. Use Crabbox artifact publishing plus the manifest URL. Do not commit `.github/pr-assets`. - CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need. +- Trusted-workflow release-branch CI: pass `target_ref` + `release_candidate_ref`; never `release_gate` (requires workflow head == target). - Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init `, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init `, validate them with `scripts/pr review-validate-artifacts `, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run ` and `scripts/pr merge-run `. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`. ## Code diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 4980be6c7315..1be2028f0539 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -1567,10 +1567,33 @@ describe("ci workflow guards", () => { for (const installStep of [macosInstallStep, iosInstallStep]) { expect(installStep.run).toContain("if [[ -x ./scripts/install-swift-tools.sh ]]; then"); + expect(installStep.run).toContain("brew install xcodegen swiftlint"); + expect(installStep.run).not.toContain("brew install xcodegen swiftlint swiftformat"); + expect(installStep.run).toContain( + "https://github.com/nicklockwood/SwiftFormat/releases/download/$swiftformat_version/swiftformat.zip", + ); + expect(installStep.run).toContain( + 'swiftformat_checksum="b990400779aceb7d7020796eb9ba814d4480543f671d38fc0ff48cb72f04c584"', + ); + expect(installStep.run).toContain( + 'swiftformat_checksum="7cb1cb1fae04932047c7015441c543848e8e60e1572d808d080e0a1f1661114a"', + ); + expect(installStep.run).toContain( + '[[ "$("$swift_tools_dir/swiftformat" --version)" == "$swiftformat_version" ]]', + ); } - expect(macosInstallStep.run).toContain("brew install xcodegen swiftlint"); - expect(macosInstallStep.run).not.toContain("brew install xcodegen swiftlint swiftformat"); - expect(iosInstallStep.run).toContain("brew install xcodegen swiftlint swiftformat"); + for (const jobName of ["macos-swift", "ios-build"]) { + expect(workflow.jobs[jobName].env.HISTORICAL_TARGET).toBe( + "${{ needs.preflight.outputs.compatibility_target }}", + ); + } + expect(iosInstallStep.run).toContain('swiftformat_link="$(brew --prefix)/bin/swiftformat"'); + expect(iosInstallStep.run).toContain( + 'ln -sfn "$swift_tools_dir/swiftformat" "$swiftformat_link"', + ); + expect(iosInstallStep.run).toContain( + '[[ "$("$swiftformat_link" --version)" == "$swiftformat_version" ]]', + ); for (const lintStep of [macosLintStep, iosLintStep]) { expect(lintStep.run).toContain( "if [[ -x ./scripts/lint-swift.sh && -x ./scripts/format-swift.sh ]]; then",