CI #2275
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: | |
| - main | |
| schedule: | |
| # Runs at 00:00 UTC every Monday to ensure that CI does not bitrot. | |
| - cron: '0 0 * * 1' | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| update-version-check: | |
| name: Update Version Script Check | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Run update-version script | |
| run: ./Scripts/update-version.sh 99.99.99-test abc123testchecksum456 | |
| - name: Verify URL was updated | |
| run: grep -q 'releases/download/99.99.99-test/SafeDITool.artifactbundle.zip' Package.swift | |
| - name: Verify checksum was updated | |
| run: grep -q 'checksum:.*abc123testchecksum456' Package.swift | |
| - name: Verify Plugins/Shared.swift safeDIVersion was updated | |
| # Match the literal within 3 lines after the `safeDIVersion` | |
| # getter declaration so a future test-string in an unrelated | |
| # spot can't satisfy a bare file-wide grep. Block scalar because | |
| # an unquoted inline `{` trips the YAML parser. | |
| run: | | |
| grep -A 2 'var safeDIVersion: String {' Plugins/Shared.swift | grep -q '"99.99.99-test"' | |
| - name: Verify MODULE.bazel version was updated | |
| # Scope to the top-level module() block so bazel_dep lines that | |
| # also carry `version = "X"` can't false-positive. | |
| run: | | |
| awk '/^module\(/,/^\)/' MODULE.bazel | grep -q 'version = "99.99.99-test"' | |
| xcodebuild: | |
| name: Build with xcodebuild on Xcode 26 | |
| runs-on: macos-26 | |
| strategy: | |
| matrix: | |
| platforms: [ | |
| 'generic/platform=ios', | |
| 'platform=macOS', | |
| 'generic/platform=tvos', | |
| 'generic/platform=watchos', | |
| 'generic/platform=visionos' | |
| ] | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| - name: Download Platform | |
| if: matrix.platforms != 'platform=macOS' | |
| run: | | |
| sudo xcodebuild -runFirstLaunch | |
| sudo xcrun simctl list | |
| PLATFORM=$(echo "${{ matrix.platforms }}" | sed 's|generic/platform=||') | |
| sudo xcodebuild -downloadPlatform "$PLATFORM" | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: xcrun xcodebuild -resolvePackageDependencies -skipPackagePluginValidation -skipMacroValidation -scheme SafeDI-Package | |
| - name: Build Framework | |
| run: xcrun xcodebuild -skipMacroValidation -skipPackagePluginValidation build -scheme SafeDI-Package -destination ${{ matrix.platforms }} | |
| docc: | |
| name: Build DocC on Xcode 26 | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| # `swift-docc-plugin`'s `--warnings-as-errors` scopes to a single | |
| # target, so swift-syntax's internal DocC noise stays out of our | |
| # strict build. That scoping isn't available via `xcodebuild | |
| # docbuild`, which analyzes the whole target graph. | |
| - name: Build DocC | |
| uses: ./.github/actions/retry | |
| with: | |
| command: swift package generate-documentation --target SafeDI --warnings-as-errors | |
| # Example jobs that declare package traits in their .xcodeproj | |
| # (`traits = (…)`) need Xcode 26.4 — the first Xcode that | |
| # surfaces traits in the project file. Other examples run on the | |
| # same baseline as the SafeDI-core jobs above. | |
| spm-package-integration: | |
| name: Build Package Integration on Xcode 26 | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: xcrun swift package resolve --package-path "Examples/Example Package Integration" | |
| - name: Build Package Integration (swift build) | |
| run: xcrun swift build --package-path "Examples/Example Package Integration" | |
| # Also build via xcodebuild — it exercises the plugin's in-process | |
| # PluginScanner fallback, which `swift build` doesn't. A bug in that | |
| # path would make Xcode IDE builds fail while the swift build above | |
| # still passes. | |
| - name: Build Package Integration (xcodebuild) | |
| run: pushd "Examples/Example Package Integration"; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -scheme ExamplePackageIntegration -destination "platform=macOS"; popd | |
| spm-project-integration: | |
| name: Build Project Integration on Xcode 26 | |
| # Pinned to 26.4 — project file declares package traits. | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.4.app/Contents/Developer | |
| - name: Replace 'main' branch with the current branch | |
| if: github.event.pull_request.head.repo.full_name == github.repository # Only do this if the branch is from our repo. | |
| run: sed -i '' "s#branch = main;#branch = ${{ github.head_ref || github.ref_name }};#" "Examples/ExampleProjectIntegration/ExampleProjectIntegration.xcodeproj/project.pbxproj" | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: pushd Examples/ExampleProjectIntegration && xcrun xcodebuild -resolvePackageDependencies -skipPackagePluginValidation -skipMacroValidation -scheme ExampleProjectIntegration && popd | |
| - name: Build Project Integration | |
| run: pushd Examples/ExampleProjectIntegration; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -scheme ExampleProjectIntegration; popd | |
| spm-multi-project-integration: | |
| name: Build Multi Project Integration on Xcode 26 | |
| # Pinned to 26.4 — project file declares package traits. | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.4.app/Contents/Developer | |
| - name: Replace 'main' branch with the current branch | |
| if: github.event.pull_request.head.repo.full_name == github.repository # Only do this if the branch is from our repo. | |
| run: sed -i '' "s#branch = main;#branch = ${{ github.head_ref || github.ref_name }};#" "Examples/ExampleMultiProjectIntegration/ExampleMultiProjectIntegration.xcodeproj/project.pbxproj" | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: pushd Examples/ExampleMultiProjectIntegration && xcrun xcodebuild -resolvePackageDependencies -skipPackagePluginValidation -skipMacroValidation -scheme ExampleMultiProjectIntegration && popd | |
| - name: Build Project Integration | |
| run: pushd Examples/ExampleMultiProjectIntegration; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -scheme ExampleMultiProjectIntegration; popd | |
| spm-tuist-integration: | |
| name: Build Tuist Integration on Xcode 26 | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| # Required so mise / tuist install don't hit anonymous GitHub API | |
| # rate limits when resolving release tags and artifact bundles. | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| - name: Install Tuist via mise | |
| # Keep the pinned Tuist version here in sync with the README. | |
| run: | | |
| curl -fsSL https://mise.run | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" | |
| "$HOME/.local/bin/mise" install -y tuist@4.183.0 | |
| "$HOME/.local/bin/mise" use -g tuist@4.183.0 | |
| - name: Tuist Install | |
| run: pushd Examples/ExampleTuistIntegration && tuist install && popd | |
| - name: Tuist Generate | |
| run: pushd Examples/ExampleTuistIntegration && tuist generate --no-open && popd | |
| - name: Build Tuist Integration | |
| run: pushd Examples/ExampleTuistIntegration; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -workspace ExampleTuistIntegration.xcworkspace -scheme ExampleTuistIntegration; popd | |
| spm: | |
| name: Build and Test on Xcode 26 | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: xcrun swift package resolve | |
| - name: Build and Test Framework | |
| run: | | |
| for i in {1..5}; do # Run tests a few times to ensure code-gen is stable. | |
| xcrun swift test -c release --enable-code-coverage -Xswiftc -enable-testing --traits sourceBuild | |
| done | |
| - name: Prepare Coverage Reports | |
| run: ./Scripts/prepare-coverage-reports.sh | |
| - name: Upload Coverage Reports | |
| if: success() | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| fail_ci_if_error: true | |
| verbose: true | |
| os: macos | |
| linux: | |
| name: Build and Test on Linux | |
| runs-on: ubuntu-latest | |
| container: swift:6.2 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: swift package resolve | |
| - name: Build and Test Framework | |
| run: swift test -c release --enable-code-coverage -Xswiftc -enable-testing --traits sourceBuild | |
| - name: Install curl for Codecov | |
| run: | | |
| apt-get update | |
| apt-get install -y --no-install-recommends curl ca-certificates | |
| - name: Prepare Coverage Reports | |
| run: | | |
| llvm-cov export -format="lcov" .build/x86_64-unknown-linux-gnu/release/SafeDIPackageTests.xctest -instr-profile .build/x86_64-unknown-linux-gnu/release/codecov/default.profdata > coverage.lcov | |
| - name: Upload Coverage Reports | |
| if: success() | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| fail_ci_if_error: true | |
| verbose: true | |
| os: linux | |
| bazel: | |
| name: Bazel Build on macOS | |
| # General bazel sanity check on every PR. We can't perfectly | |
| # mirror BCR's presubmit (https://registry.bazel.build/modules/safedi): | |
| # BazelCI's BuildKite infrastructure is closed to non-`bazelbuild` | |
| # projects (https://github.com/bazelbuild/continuous-integration/blob/main/.github/ISSUE_TEMPLATE/adding-your-project-to-bazel-ci.md), | |
| # and any GH Actions runner we pick differs from BCR's image in | |
| # ways we can't fully control (CC toolchain auto-detection, | |
| # SDK-vs-runner-OS gap, exact Bazel point release). So treat BCR | |
| # as the canonical macOS-on-older-OS check; this job catches | |
| # general regressions before we publish. | |
| # | |
| # Keep `build_targets` aligned with `.bcr/presubmit.yml` so a | |
| # divergence is visible in review. If you change BCR's task list, | |
| # update this job too. | |
| # | |
| # Pinned to Xcode 26.0 — matches BCR's `macos_arm64` runner | |
| # platform defined at https://github.com/bazelbuild/continuous-integration/blob/master/buildkite/bazelci.py | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Select Xcode Version | |
| run: sudo xcode-select --switch /Applications/Xcode_26.0.app/Contents/Developer | |
| # Wrapping `bazelisk build` in retry — not just dependency | |
| # resolution, like the SPM jobs above — because Bazel resolves | |
| # external repos lazily during the build itself. There's no | |
| # equivalent to `swift package resolve` we could isolate; a | |
| # transient HTTP failure (e.g. on a `rules_swift` transitive | |
| # like `MobileNativeFoundation/index-import`) surfaces as a | |
| # build failure. Bazel caches successful downloads, so a retry | |
| # after a flake is cheap. | |
| # | |
| # Each command runs in a subshell so a `cd` for the example | |
| # build doesn't leak its cwd change into subsequent retries. | |
| - name: Build SafeDI targets (mirrors BCR `verify_safedi_build`) | |
| uses: ./.github/actions/retry | |
| with: | |
| # Kept on one line — the retry composite action splices | |
| # `${{ inputs.command }}` directly into a bash `for` loop | |
| # and tacks ` && break` onto the same source line, so a | |
| # multi-line YAML command leaves `&& break` orphaned. | |
| command: bazelisk build //Sources/SafeDI:SafeDI //Sources/SafeDICore:SafeDICore //Sources/SafeDIMacros:SafeDIMacros //Sources/SafeDITool:SafeDITool | |
| - name: Build downstream example (mirrors BCR `bcr_test_module.build_example`) | |
| uses: ./.github/actions/retry | |
| with: | |
| command: (cd Examples/ExampleBazelIntegration && bazelisk build //Subproject:Subproject //ExampleBazelIntegration:ExampleBazelIntegration) | |
| readme-validation: | |
| name: Check Markdown links | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Link Checker | |
| uses: AlexanderDokuchaev/md-dead-link-check@d5a37e0b14e5918605d22b34562532762ccb2e47 # v1.2.0 | |
| lint-swift: | |
| name: Lint Swift | |
| runs-on: ubuntu-latest | |
| container: swift:6.3 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v6 | |
| - name: Resolve Package Dependencies | |
| uses: ./.github/actions/retry | |
| with: | |
| command: swift package resolve --package-path CLI | |
| - name: Lint Swift | |
| run: swift run --package-path CLI swiftformat . --lint |