ci: add upstream sync workflow on major releases #1
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: Build & Test | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| jobs: | |
| test: | |
| name: Run Swift Tests (${{ matrix.name }}) | |
| runs-on: macos-latest | |
| # A hung test must fail fast, not hold a runner for GitHub's 360-minute | |
| # default (observed: intermittent app-suite hangs starving the queue). | |
| # The long steps carry tighter individual bounds (5-minute test watchdog, | |
| # 6-minute benchmark step, 10-minute floor gate that may re-run the | |
| # benchmarks up to twice on a noisy runner); this is the backstop. | |
| timeout-minutes: 25 | |
| strategy: | |
| fail-fast: false # Don't cancel other matrix jobs when one fails | |
| matrix: | |
| include: | |
| - name: app | |
| path: . | |
| - name: BitLogger | |
| path: localPackages/BitLogger | |
| - name: BitFoundation | |
| path: localPackages/BitFoundation | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| # Use the Xcode-bundled Swift toolchain: it always matches the SDK on | |
| # the runner image. A standalone swift.org toolchain (setup-swift) broke | |
| # whenever the image's Xcode moved ahead of it ("this SDK is not | |
| # supported by the compiler"). | |
| - name: Note toolchain version (cache key) | |
| id: swift-version | |
| run: echo "version=$(swift --version 2>/dev/null | head -1 | shasum | cut -c1-12)" >> "$GITHUB_OUTPUT" | |
| - name: Cache build artifacts | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ matrix.path }}/.build | |
| key: ${{ runner.os }}-${{ steps.swift-version.outputs.version }}-${{ matrix.name }}-${{ hashFiles(format('{0}/**/*.swift', matrix.path), format('{0}/**/Package.resolved', matrix.path)) }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ steps.swift-version.outputs.version }}-${{ matrix.name }}-${{ hashFiles(format('{0}/**/Package.resolved', matrix.path)) }} | |
| ${{ runner.os }}-${{ steps.swift-version.outputs.version }}-${{ matrix.name }}- | |
| - name: Build tests | |
| # Built separately so the hang watchdog below times only test | |
| # execution: a cold-cache coverage build on a slow runner can | |
| # legitimately take several minutes, and is already bounded by the | |
| # 15-minute job timeout. | |
| run: swift build --build-tests --enable-code-coverage --package-path ${{ matrix.path }} | |
| - name: Run Tests | |
| # Perf benchmarks are excluded here and run in their own serial step | |
| # below: measuring while parallel test processes contend for cores | |
| # produces noisy numbers, and the XCTest measure machinery has hung | |
| # intermittently under parallel workers on loaded runners. Excluded | |
| # via --skip (not just the env guard): every app run since the | |
| # baselines landed timed out at the 15-minute job limit with the | |
| # baseline tests dispatched into the parallel phase. | |
| # | |
| # The watchdog samples any still-running test processes after 5 | |
| # minutes (the suite passes in seconds when healthy; the build is | |
| # done by this step) and kills the run, so a hang fails fast with | |
| # stacks in the log instead of a silent timeout. | |
| env: | |
| BITCHAT_SKIP_PERF_BASELINES: "1" | |
| run: | | |
| swift test --skip-build --parallel --quiet --enable-code-coverage \ | |
| --skip PerformanceBaselineTests \ | |
| --package-path ${{ matrix.path }} & | |
| test_pid=$! | |
| ( | |
| sleep 300 | |
| if kill -0 "$test_pid" 2>/dev/null; then | |
| echo "::group::Tests still running after 5 minutes — sampling before kill" | |
| for pid in $(pgrep -if 'swiftpm-testing|xctest|PackageTests' || true); do | |
| echo "--- sample of pid $pid ---" | |
| sample "$pid" 5 2>/dev/null || true | |
| done | |
| echo "::endgroup::" | |
| pkill -KILL -P "$test_pid" 2>/dev/null || true | |
| kill -KILL "$test_pid" 2>/dev/null || true | |
| fi | |
| ) & | |
| watchdog_pid=$! | |
| wait "$test_pid" && status=0 || status=$? | |
| kill "$watchdog_pid" 2>/dev/null || true | |
| exit "$status" | |
| # Benchmarks run serially on an otherwise idle runner for stable | |
| # numbers; BITCHAT_PERF_LOG captures the PERF[...] lines for the gate. | |
| - name: Run performance benchmarks (serial) | |
| if: matrix.name == 'app' | |
| timeout-minutes: 6 | |
| env: | |
| BITCHAT_PERF_LOG: ${{ github.workspace }}/perf-output.log | |
| run: swift test --quiet --filter PerformanceBaselineTests | |
| # Order-of-magnitude performance regression gate. Floors are deliberately | |
| # generous (see bitchatTests/Performance/perf-floors.json) so this | |
| # catches algorithmic regressions, never runner variance. If a metric | |
| # still lands below floor (a saturated runner can dip one), the script | |
| # re-runs the benchmarks — appending to the same log and keeping each | |
| # benchmark's best value per metric — so noise clears on retry while a | |
| # real regression fails every attempt. Floors are never lowered by this. | |
| - name: Performance floor gate | |
| if: matrix.name == 'app' | |
| timeout-minutes: 10 | |
| run: ./scripts/check-perf-floors.sh perf-output.log | |
| # Informational only: surfaces per-file and total line coverage in the | |
| # job log so coverage trends are visible on every PR. No thresholds — | |
| # this must never be the reason a build goes red. | |
| - name: Coverage summary | |
| run: | | |
| BIN_PATH=$(swift build --show-bin-path --package-path ${{ matrix.path }}) | |
| PROF="$BIN_PATH/codecov/default.profdata" | |
| XCTEST=$(find "$BIN_PATH" -maxdepth 1 -name '*.xctest' | head -1) | |
| BINARY="$XCTEST/Contents/MacOS/$(basename "$XCTEST" .xctest)" | |
| if [ -f "$PROF" ] && [ -f "$BINARY" ]; then | |
| xcrun llvm-cov report "$BINARY" -instr-profile "$PROF" \ | |
| -ignore-filename-regex='(Tests|\.build|checkouts|Mocks|_PreviewHelpers)' || true | |
| else | |
| echo "No coverage data found; skipping summary." | |
| fi | |
| # SPM tests do not link the shipping app targets. This job covers the | |
| # iOS-conditional paths and both universal Release link configurations. | |
| ios-build: | |
| name: Build Release apps (universal) | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Build iOS (simulator, no signing) | |
| # Build both simulator architectures so CI validates every vendored | |
| # Arti simulator slice and the configuration that ships. | |
| run: | | |
| set -o pipefail | |
| xcodebuild -project bitchat.xcodeproj \ | |
| -scheme "bitchat (iOS)" \ | |
| -configuration Release \ | |
| -sdk iphonesimulator \ | |
| -destination 'generic/platform=iOS Simulator' \ | |
| ARCHS='arm64 x86_64' \ | |
| ONLY_ACTIVE_ARCH=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| build | |
| - name: Build macOS (universal, no signing) | |
| run: | | |
| set -o pipefail | |
| xcodebuild -project bitchat.xcodeproj \ | |
| -scheme "bitchat (macOS)" \ | |
| -configuration Release \ | |
| -destination 'generic/platform=macOS' \ | |
| ARCHS='arm64 x86_64' \ | |
| ONLY_ACTIVE_ARCH=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| build | |
| # Advisory only: SwiftLint reports style violations without ever failing the | |
| # build. Runs in a pinned container (no Xcode plugin, no pbxproj changes) so | |
| # it can never break the documented xcodebuild path or block a merge. | |
| lint: | |
| name: SwiftLint (advisory) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| # This job runs a third-party container image, so give it the least | |
| # privilege we can: a read-only token, and no credentials left in the | |
| # checkout for the container to find. | |
| permissions: | |
| contents: read | |
| container: | |
| # Tag for readability, digest for immutability (tags can be repointed). | |
| # Bump both together, deliberately — never a floating tag. | |
| image: ghcr.io/realm/swiftlint:0.65.0@sha256:a482729f4b58741875af1566f23397f3f6db300372756fc31606d0a4527fab9e | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| persist-credentials: false | |
| - name: Run SwiftLint | |
| run: swiftlint lint --reporter github-actions-logging |