From 5bda3a54f9f481bf1f9781a6b013e7a4de31ef94 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 6 Oct 2026 23:16:25 +0600 Subject: [PATCH 1/7] ci: build all platforms on Xcode 16 and 27, check API breaks, harden workflows - Compile the package for iOS, tvOS, watchOS and macOS on the newest (27.0) and oldest supported (16.0) Xcode; watchOS was never built in CI - Fail PRs that break public API vs. the base branch (swift package diagnose-api-breaking-changes) - Cancel superseded PR runs; add timeouts to macOS jobs - Upgrade actions/checkout v3 -> v7, pin all actions to commit SHAs, stop persisting checkout credentials, add Dependabot for actions - Call reusable workflows from the same commit instead of @master - Pipe unit test output through xcbeautify for inline PR annotations Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 6 +++ .github/workflows/integration_tests.yml | 2 +- .github/workflows/lint_markdown.yml | 6 ++- .github/workflows/source_clear_cron.yml | 5 +- .github/workflows/swift.yml | 55 ++++++++++++++++++-- .github/workflows/ticket_reference_check.yml | 2 +- .github/workflows/unit_tests.yml | 9 ++-- Scripts/run_unit_tests.sh | 2 +- 8 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ca79ca5b4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index e0401f44b..5363ce1b8 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -9,7 +9,7 @@ jobs: integration_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # You should create a personal access token and store it in your repository token: ${{ secrets.CI_USER_TOKEN }} diff --git a/.github/workflows/lint_markdown.yml b/.github/workflows/lint_markdown.yml index e3e903143..a5976991a 100644 --- a/.github/workflows/lint_markdown.yml +++ b/.github/workflows/lint_markdown.yml @@ -6,9 +6,11 @@ jobs: lint_markdown: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 with: ruby-version: '2.6' bundler-cache: true # runs 'bundle install' and caches installed gems automatically diff --git a/.github/workflows/source_clear_cron.yml b/.github/workflows/source_clear_cron.yml index 4b37ecc15..85bf3587a 100644 --- a/.github/workflows/source_clear_cron.yml +++ b/.github/workflows/source_clear_cron.yml @@ -10,8 +10,11 @@ on: jobs: source_clear: runs-on: macos-15 + timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Source clear scan env: SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 3ebcfdf06..4c420e89e 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,22 +10,29 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint_markdown_files: - uses: optimizely/swift-sdk/.github/workflows/lint_markdown.yml@master + uses: ./.github/workflows/lint_markdown.yml integration_tests: # ci-helper-tools' trigger script only understands push and pull_request events if: github.event_name != 'workflow_dispatch' - uses: optimizely/swift-sdk/.github/workflows/integration_tests.yml@master + uses: ./.github/workflows/integration_tests.yml secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} package_validation: runs-on: xcode-27 + timeout-minutes: 20 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - env: SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} run: | @@ -33,5 +40,47 @@ jobs: swift build curl -sSL https://download.sourceclear.com/ci.sh | bash + # Compile the package for every platform in Package.swift, on the newest and oldest supported Xcode + build: + name: build (${{ matrix.xcode }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - xcode: Xcode_27.0 + runner: xcode-27 + - xcode: Xcode_16.0 + runner: macos-15 + env: + DEVELOPER_DIR: /Applications/${{ matrix.xcode }}.app/Contents/Developer + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: | + swift build + for target in iphoneos:arm64-apple-ios15.0 appletvos:arm64-apple-tvos15.0 watchos:arm64_32-apple-watchos9.0; do + sdk=${target%%:*} + echo "::group::$sdk" + swift build --scratch-path ".build-$sdk" --triple "${target#*:}" --sdk "$(xcrun --sdk "$sdk" --show-sdk-path)" + echo "::endgroup::" + done + + # Fails when the PR breaks public API compared to its base branch (expected only for major releases) + api_breaking_changes: + if: github.event_name == 'pull_request' + runs-on: xcode-27 + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - env: + BASE_REF: ${{ github.base_ref }} + run: swift package diagnose-api-breaking-changes "origin/$BASE_REF" + unittests: uses: ./.github/workflows/unit_tests.yml diff --git a/.github/workflows/ticket_reference_check.yml b/.github/workflows/ticket_reference_check.yml index b7d52780f..81beaa91d 100644 --- a/.github/workflows/ticket_reference_check.yml +++ b/.github/workflows/ticket_reference_check.yml @@ -11,6 +11,6 @@ jobs: steps: - name: Check for Jira ticket reference - uses: optimizely/github-action-ticket-reference-checker-public@master + uses: optimizely/github-action-ticket-reference-checker-public@06871091b2c0e23c0354109a29406bc0e47f6af1 # master with: bodyRegex: 'FSSDK-(?\d+)' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8ea083638..d2c441962 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -11,6 +11,7 @@ env: jobs: unittests: runs-on: xcode-27 + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -40,7 +41,9 @@ jobs: platform: "tvOS Simulator" os_type: "tvOS" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: set SDK Branch if PR env: BASE_REF: ${{ github.base_ref }} @@ -77,7 +80,7 @@ jobs: exit 1 - name: Check on failures (Archive Test Results) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: steps.unit_tests.outcome != 'success' with: name: build-logs-${{ matrix.device }}-${{ matrix.os }} @@ -97,7 +100,7 @@ jobs: - name: Upload coveralls report if: steps.coveralls.outcome == 'success' continue-on-error: true - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./xccov2lcov/lcov.info diff --git a/Scripts/run_unit_tests.sh b/Scripts/run_unit_tests.sh index 9d1afb69d..d5ed1a0f1 100755 --- a/Scripts/run_unit_tests.sh +++ b/Scripts/run_unit_tests.sh @@ -7,4 +7,4 @@ set -euo pipefail # $ ./run_unit_tests.sh # -xcodebuild test -derivedDataPath "$COVERAGE_DIR" -project OptimizelySwiftSDK.xcodeproj -scheme "$SCHEME" -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk "$TEST_SDK" -destination "platform=$PLATFORM,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES | tee buildoutput +xcodebuild test -derivedDataPath "$COVERAGE_DIR" -project OptimizelySwiftSDK.xcodeproj -scheme "$SCHEME" -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk "$TEST_SDK" -destination "platform=$PLATFORM,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES | tee buildoutput | xcbeautify --renderer github-actions From d790a9895b21247b266ed1d39731faa163f793f0 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 6 Oct 2026 23:28:27 +0600 Subject: [PATCH 2/7] ci: don't persist the CI_USER_TOKEN PAT after checking out ci-helper-tools The trigger scripts only call the GitHub API with curl using the token from the environment, so no git credentials are needed on disk. Addresses Arnica's artipacked finding. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/integration_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 5363ce1b8..8d90e2f2c 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -16,6 +16,7 @@ jobs: repository: 'optimizely/ci-helper-tools' path: 'home/runner/ci-helper-tools' ref: 'master' + persist-credentials: false - name: set SDK Branch if PR env: HEAD_REF: ${{ github.head_ref }} From 9903c2a68dc7806de9b7e242241792ac51a2cea2 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 6 Oct 2026 23:32:30 +0600 Subject: [PATCH 3/7] ci: reference actions by major tag instead of SHA; drop Dependabot Major tags pick up patch and minor fixes without bot PRs. The ticket checker stays SHA-pinned since it only has a master branch and hasn't changed since 2022. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 6 ------ .github/workflows/integration_tests.yml | 2 +- .github/workflows/lint_markdown.yml | 4 ++-- .github/workflows/source_clear_cron.yml | 2 +- .github/workflows/swift.yml | 6 +++--- .github/workflows/unit_tests.yml | 6 +++--- 6 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index ca79ca5b4..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 8d90e2f2c..d898f03cb 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -9,7 +9,7 @@ jobs: integration_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: # You should create a personal access token and store it in your repository token: ${{ secrets.CI_USER_TOKEN }} diff --git a/.github/workflows/lint_markdown.yml b/.github/workflows/lint_markdown.yml index a5976991a..582ed4ddd 100644 --- a/.github/workflows/lint_markdown.yml +++ b/.github/workflows/lint_markdown.yml @@ -6,11 +6,11 @@ jobs: lint_markdown: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0 + uses: ruby/setup-ruby@v1 with: ruby-version: '2.6' bundler-cache: true # runs 'bundle install' and caches installed gems automatically diff --git a/.github/workflows/source_clear_cron.yml b/.github/workflows/source_clear_cron.yml index 85bf3587a..44105f313 100644 --- a/.github/workflows/source_clear_cron.yml +++ b/.github/workflows/source_clear_cron.yml @@ -12,7 +12,7 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: persist-credentials: false - name: Source clear scan diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 4c420e89e..d486ac335 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -30,7 +30,7 @@ jobs: runs-on: xcode-27 timeout-minutes: 20 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: persist-credentials: false - env: @@ -56,7 +56,7 @@ jobs: env: DEVELOPER_DIR: /Applications/${{ matrix.xcode }}.app/Contents/Developer steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: persist-credentials: false - run: | @@ -74,7 +74,7 @@ jobs: runs-on: xcode-27 timeout-minutes: 20 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d2c441962..2fe1ed3b9 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -41,7 +41,7 @@ jobs: platform: "tvOS Simulator" os_type: "tvOS" steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: persist-credentials: false - name: set SDK Branch if PR @@ -80,7 +80,7 @@ jobs: exit 1 - name: Check on failures (Archive Test Results) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@v4 if: steps.unit_tests.outcome != 'success' with: name: build-logs-${{ matrix.device }}-${{ matrix.os }} @@ -100,7 +100,7 @@ jobs: - name: Upload coveralls report if: steps.coveralls.outcome == 'success' continue-on-error: true - uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./xccov2lcov/lcov.info From 31e06396e59a1a58bb5abaf27cc00de4f1ded366 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 6 Oct 2026 23:34:28 +0600 Subject: [PATCH 4/7] ci: keep ticket checker on @master It's Optimizely-owned, so pinning adds little and would hide future fixes. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ticket_reference_check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ticket_reference_check.yml b/.github/workflows/ticket_reference_check.yml index 81beaa91d..b7d52780f 100644 --- a/.github/workflows/ticket_reference_check.yml +++ b/.github/workflows/ticket_reference_check.yml @@ -11,6 +11,6 @@ jobs: steps: - name: Check for Jira ticket reference - uses: optimizely/github-action-ticket-reference-checker-public@06871091b2c0e23c0354109a29406bc0e47f6af1 # master + uses: optimizely/github-action-ticket-reference-checker-public@master with: bodyRegex: 'FSSDK-(?\d+)' From 39b75579c6f11670c838068008c991fa11c563b3 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Tue, 6 Oct 2026 23:51:00 +0600 Subject: [PATCH 5/7] ci: limit run cancellation to PRs without queuing master runs A shared group with cancel-in-progress=false still lets a newer pending master run replace an older pending one; give non-PR runs a unique group. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/swift.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index d486ac335..9b2430f0b 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,9 +10,10 @@ on: permissions: contents: read +# Cancel superseded runs of the same PR; push/manual runs get a unique group so none are queued or dropped concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.run_id }} + cancel-in-progress: true jobs: From 5e1af29fb594610f5979d44f5234842fe43d8456 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Wed, 7 Oct 2026 01:05:29 +0600 Subject: [PATCH 6/7] ci: raise unit test timeout to 90 minutes A job that needs all 3 retry attempts runs 60-70 minutes; 60 cancelled a legitimate run. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2fe1ed3b9..096c1982c 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -11,7 +11,7 @@ env: jobs: unittests: runs-on: xcode-27 - timeout-minutes: 60 + timeout-minutes: 90 # up to 3 attempts of ~20-25 min each strategy: fail-fast: false matrix: From 3034ddcc6d73ec940af119ee763ebc61a3287921 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Wed, 7 Oct 2026 07:34:24 +0600 Subject: [PATCH 7/7] ci: remove redundant package_validation and stale CI leftovers - package_validation: its swift build is covered by build (Xcode_27.0) and SourceClear still scans master via source_clear_cron.yml - upload-artifact v4 -> v7 (v4 runs on deprecated Node 20; name/path inputs unchanged) - run_unit_tests.sh: drop the unused buildoutput tee - lint_markdown: Ruby 2.6 (EOL) -> 3.3; drop bundler-cache (no Gemfile) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/lint_markdown.yml | 3 +-- .github/workflows/swift.yml | 14 -------------- .github/workflows/unit_tests.yml | 2 +- Scripts/run_unit_tests.sh | 2 +- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/lint_markdown.yml b/.github/workflows/lint_markdown.yml index 582ed4ddd..e52a7731a 100644 --- a/.github/workflows/lint_markdown.yml +++ b/.github/workflows/lint_markdown.yml @@ -12,8 +12,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '2.6' - bundler-cache: true # runs 'bundle install' and caches installed gems automatically + ruby-version: '3.3' - name: Install gem and Run tests run: | cd ../../ diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 9b2430f0b..4aa5e7865 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -27,20 +27,6 @@ jobs: secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - package_validation: - runs-on: xcode-27 - timeout-minutes: 20 - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - env: - SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} - run: | - swift package resolve - swift build - curl -sSL https://download.sourceclear.com/ci.sh | bash - # Compile the package for every platform in Package.swift, on the newest and oldest supported Xcode build: name: build (${{ matrix.xcode }}) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 096c1982c..f6d2da046 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -80,7 +80,7 @@ jobs: exit 1 - name: Check on failures (Archive Test Results) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: steps.unit_tests.outcome != 'success' with: name: build-logs-${{ matrix.device }}-${{ matrix.os }} diff --git a/Scripts/run_unit_tests.sh b/Scripts/run_unit_tests.sh index d5ed1a0f1..f103d57be 100755 --- a/Scripts/run_unit_tests.sh +++ b/Scripts/run_unit_tests.sh @@ -7,4 +7,4 @@ set -euo pipefail # $ ./run_unit_tests.sh # -xcodebuild test -derivedDataPath "$COVERAGE_DIR" -project OptimizelySwiftSDK.xcodeproj -scheme "$SCHEME" -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk "$TEST_SDK" -destination "platform=$PLATFORM,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES | tee buildoutput | xcbeautify --renderer github-actions +xcodebuild test -derivedDataPath "$COVERAGE_DIR" -project OptimizelySwiftSDK.xcodeproj -scheme "$SCHEME" -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk "$TEST_SDK" -destination "platform=$PLATFORM,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES | xcbeautify --renderer github-actions