From df5097b6e70e6711f583f740a446b3b279cc092d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 13 Jul 2026 22:33:12 -0700 Subject: [PATCH] fix(native): keep platform validation warning-free (#107101) * build(android): keep Gradle warning-free * ci(native): pin XcodeGen tooling * test(macos): avoid concurrency warnings --- .github/workflows/ci.yml | 18 +++++---- apps/android/gradle.properties | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../BrowserProfileImportModelTests.swift | 17 +++++--- scripts/ci-changed-scope.mjs | 4 +- scripts/install-xcodegen.sh | 40 +++++++++++++++++++ scripts/prepush-ci.sh | 1 + src/scripts/ci-changed-scope.test.ts | 1 + test/scripts/ci-workflow-guards.test.ts | 15 ++++++- 9 files changed, 82 insertions(+), 20 deletions(-) create mode 100755 scripts/install-xcodegen.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abd66d52e64b..c546582e078d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2364,17 +2364,18 @@ jobs: - name: Install XcodeGen / SwiftLint / SwiftFormat run: | - brew update - if [[ -x ./scripts/install-swift-tools.sh ]]; then - brew install xcodegen + if [[ -x ./scripts/install-xcodegen.sh && -x ./scripts/install-swift-tools.sh ]]; then swift_tools_dir="$RUNNER_TEMP/openclaw-swift-tools" + ./scripts/install-xcodegen.sh "$swift_tools_dir" ./scripts/install-swift-tools.sh "$swift_tools_dir" echo "$swift_tools_dir" >> "$GITHUB_PATH" + "$swift_tools_dir/xcodegen" --version "$swift_tools_dir/swiftformat" --version "$swift_tools_dir/swiftlint" version elif [[ "$HISTORICAL_TARGET" == "true" ]]; then # Frozen release targets before the pinned installer used one of these # reviewed formatter contracts. Fail closed for any unknown minimum. + brew update brew install xcodegen swiftlint swiftformat_min_version="$(awk '$1 == "--min-version" { print $2; exit }' config/swiftformat)" case "$swiftformat_min_version" in @@ -2406,7 +2407,7 @@ jobs: echo "$swift_tools_dir" >> "$GITHUB_PATH" [[ "$("$swift_tools_dir/swiftformat" --version)" == "$swiftformat_version" ]] else - echo "Current CI targets must provide scripts/install-swift-tools.sh." >&2 + echo "Current CI targets must provide scripts/install-xcodegen.sh and scripts/install-swift-tools.sh." >&2 exit 1 fi @@ -2568,17 +2569,18 @@ jobs: - name: Install iOS Swift tooling run: | - brew update - if [[ -x ./scripts/install-swift-tools.sh ]]; then - brew install xcodegen + if [[ -x ./scripts/install-xcodegen.sh && -x ./scripts/install-swift-tools.sh ]]; then swift_tools_dir="$RUNNER_TEMP/openclaw-swift-tools" + ./scripts/install-xcodegen.sh "$swift_tools_dir" ./scripts/install-swift-tools.sh "$swift_tools_dir" echo "$swift_tools_dir" >> "$GITHUB_PATH" + "$swift_tools_dir/xcodegen" --version "$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 update brew install xcodegen swiftlint swiftformat_min_version="$(awk '$1 == "--min-version" { print $2; exit }' config/swiftformat)" case "$swiftformat_min_version" in @@ -2615,7 +2617,7 @@ jobs: ln -sfn "$swift_tools_dir/swiftformat" "$swiftformat_link" [[ "$("$swiftformat_link" --version)" == "$swiftformat_version" ]] else - echo "Current CI targets must provide scripts/install-swift-tools.sh." >&2 + echo "Current CI targets must provide scripts/install-xcodegen.sh and scripts/install-swift-tools.sh." >&2 exit 1 fi diff --git a/apps/android/gradle.properties b/apps/android/gradle.properties index e1f8c4113a2d..83099167dbba 100644 --- a/apps/android/gradle.properties +++ b/apps/android/gradle.properties @@ -1,5 +1,6 @@ org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 --enable-native-access=ALL-UNNAMED -org.gradle.warning.mode=all +# Keep toolchain and plugin deprecations from returning as successful CI noise. +org.gradle.warning.mode=fail android.useAndroidX=true android.nonTransitiveRClass=true android.enableR8.fullMode=true diff --git a/apps/android/gradle/wrapper/gradle-wrapper.properties b/apps/android/gradle/wrapper/gradle-wrapper.properties index a9db11550c62..5e263cbb5f4b 100644 --- a/apps/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +# AGP 9.2 emits deprecated project-dependency notation on Gradle 9.5+. +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/apps/macos/Tests/OpenClawIPCTests/BrowserProfileImportModelTests.swift b/apps/macos/Tests/OpenClawIPCTests/BrowserProfileImportModelTests.swift index 7d9c57febac8..c6f44ac1270e 100644 --- a/apps/macos/Tests/OpenClawIPCTests/BrowserProfileImportModelTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/BrowserProfileImportModelTests.swift @@ -7,6 +7,12 @@ private final class ContinuationBox { var continuation: CheckedContinuation? } +@MainActor +private final class BrowserImportEligibilityGate { + var isOnboarded = false + var isLocalMode = false +} + @MainActor private final class BrowserImportTransportStub { struct StubError: Error, LocalizedError { @@ -130,16 +136,15 @@ struct BrowserProfileImportModelTests { @Test func `automatic offer request waits for onboarding and local mode`() async { let stub = BrowserImportTransportStub() - var isOnboarded = false - var isLocalMode = false + let eligibility = BrowserImportEligibilityGate() let model = stub.makeModel( - isOnboarded: { isOnboarded }, - isLocalMode: { isLocalMode }) + isOnboarded: { eligibility.isOnboarded }, + isLocalMode: { eligibility.isLocalMode }) #expect(await !model.requestAutomaticOfferIfEligible()) - isOnboarded = true + eligibility.isOnboarded = true #expect(await !model.requestAutomaticOfferIfEligible()) - isLocalMode = true + eligibility.isLocalMode = true #expect(await model.requestAutomaticOfferIfEligible()) #expect(stub.requests(for: "/system-profile-import/status").count == 1) } diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index 80b17e20c9ac..0323a92e161a 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -43,9 +43,9 @@ const APPLE_SWIFT_CONFIG_RE = /^config\/(?:swiftformat|swiftlint\.yml)$/; const MACOS_NATIVE_RE = /^(apps\/macos\/|apps\/macos-mlx-tts\/|apps\/ios\/|apps\/shared\/|apps\/swabble\/|Swabble\/)/; const MACOS_SCRIPT_SCOPE_RE = - /^(?:scripts\/(?:check-swift-tools|codesign-mac-app|create-dmg|format-swift|install-swift-tools|lint-swift|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh|scripts\/lib\/(?:plistbuddy|swift-toolchain)\.sh|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts)$/; + /^(?:scripts\/(?:check-swift-tools|codesign-mac-app|create-dmg|format-swift|install-swift-tools|install-xcodegen|lint-swift|notarize-mac-artifact|package-mac-app|package-mac-dist)\.sh|scripts\/lib\/(?:plistbuddy|swift-toolchain)\.sh|test\/scripts\/(?:codesign-mac-app|create-dmg|notarize-mac-artifact|package-mac-app|package-mac-dist)\.test\.ts)$/; const IOS_BUILD_RE = - /^(apps\/ios\/|apps\/shared\/|apps\/swabble\/|Swabble\/|scripts\/(?:check-swift-tools|format-swift|install-swift-tools|lint-swift)\.sh$|scripts\/(?:ios-(?:configure-signing|team-id|write-version-xcconfig)\.sh|ios-write-swift-filelist\.mjs|ios-version\.ts)$|scripts\/lib\/(?:ios-version\.ts|npm-publish-plan\.mjs|version-script-args\.ts)$)/; + /^(apps\/ios\/|apps\/shared\/|apps\/swabble\/|Swabble\/|scripts\/(?:check-swift-tools|format-swift|install-swift-tools|install-xcodegen|lint-swift)\.sh$|scripts\/(?:ios-(?:configure-signing|team-id|write-version-xcconfig)\.sh|ios-write-swift-filelist\.mjs|ios-version\.ts)$|scripts\/lib\/(?:ios-version\.ts|npm-publish-plan\.mjs|version-script-args\.ts)$)/; const ANDROID_NATIVE_RE = /^(apps\/android\/|apps\/shared\/)/; const NODE_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/; diff --git a/scripts/install-xcodegen.sh b/scripts/install-xcodegen.sh new file mode 100755 index 000000000000..96229253955a --- /dev/null +++ b/scripts/install-xcodegen.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ "$#" -ne 1 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +readonly xcodegen_version="2.45.4" +readonly xcodegen_checksum="090ec29491aad50aec10631bf6e62253fed733c50f3aab0f5ffc86bc170bdbef" + +install_dir="$1" +temp_dir="$(mktemp -d)" +trap 'rm -rf "$temp_dir"' EXIT + +archive="$temp_dir/xcodegen.zip" +extract_dir="$temp_dir/extract" + +curl --fail --location --silent --show-error --retry 3 \ + --output "$archive" \ + "https://github.com/yonaskolb/XcodeGen/releases/download/$xcodegen_version/xcodegen.zip" +if [[ "$(shasum -a 256 "$archive" | awk '{print $1}')" != "$xcodegen_checksum" ]]; then + echo "xcodegen archive checksum mismatch" >&2 + exit 1 +fi + +mkdir -p "$install_dir" "$extract_dir" +unzip -q "$archive" -d "$extract_dir" + +prefix_dir="$(cd "$(dirname "$install_dir")" && pwd)" +share_dir="$prefix_dir/share/xcodegen" +rm -rf "$share_dir" +mkdir -p "$(dirname "$share_dir")" +cp -R "$extract_dir/xcodegen/share/xcodegen" "$share_dir" +install -m 0755 "$extract_dir/xcodegen/bin/xcodegen" "$install_dir/xcodegen" + +# XcodeGen resolves SettingPresets relative to its executable, so keep the +# release archive's bin/../share layout intact or project generation will fail. +[[ "$("$install_dir/xcodegen" --version)" == "Version: $xcodegen_version" ]] diff --git a/scripts/prepush-ci.sh b/scripts/prepush-ci.sh index 622af1b3a6c3..7619ded5d8f4 100644 --- a/scripts/prepush-ci.sh +++ b/scripts/prepush-ci.sh @@ -46,6 +46,7 @@ has_native_swift_changes() { scripts/check-swift-tools.sh scripts/format-swift.sh scripts/install-swift-tools.sh + scripts/install-xcodegen.sh scripts/ios-write-swift-filelist.mjs scripts/lint-swift.sh ) diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index b5d09c75dd92..e92c7891a032 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -248,6 +248,7 @@ describe("detectChangedScope", () => { "scripts/check-swift-tools.sh", "scripts/format-swift.sh", "scripts/install-swift-tools.sh", + "scripts/install-xcodegen.sh", "scripts/lint-swift.sh", ]) { expect(detectChangedScope([toolingPath])).toEqual({ diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 721ad73d691b..3c61c11a626c 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -161,7 +161,12 @@ function runCiManifestFixture(options: { writeFileSync(smokePlan, "export {};\n"); } if (iosCapabilities) { - for (const name of ["install-swift-tools.sh", "lint-swift.sh", "format-swift.sh"]) { + for (const name of [ + "install-swift-tools.sh", + "install-xcodegen.sh", + "lint-swift.sh", + "format-swift.sh", + ]) { writeFileSync(path.join(root, "scripts", name), "#!/bin/sh\n"); } } @@ -1793,7 +1798,13 @@ describe("ci workflow guards", () => { ); for (const installStep of [macosInstallStep, iosInstallStep]) { - expect(installStep.run).toContain("if [[ -x ./scripts/install-swift-tools.sh ]]; then"); + const currentTargetBranch = installStep.run.split('elif [[ "$HISTORICAL_TARGET"')[0]; + expect(currentTargetBranch).toContain( + "if [[ -x ./scripts/install-xcodegen.sh && -x ./scripts/install-swift-tools.sh ]]; then", + ); + expect(currentTargetBranch).toContain('./scripts/install-xcodegen.sh "$swift_tools_dir"'); + expect(currentTargetBranch).toContain('"$swift_tools_dir/xcodegen" --version'); + expect(currentTargetBranch).not.toContain("brew "); expect(installStep.run).toContain("brew install xcodegen swiftlint"); expect(installStep.run).not.toContain("brew install xcodegen swiftlint swiftformat"); expect(installStep.run).toContain(