diff --git a/.github/workflows/package-resolved.yml b/.github/workflows/package-resolved.yml new file mode 100644 index 000000000..0c19be169 --- /dev/null +++ b/.github/workflows/package-resolved.yml @@ -0,0 +1,89 @@ +name: Package.resolved check + +# Xcode Cloud resolves packages from the committed workspace Package.resolved and +# refuses to update it, so a Package.swift requirement the file no longer satisfies +# fails every deploy at resolution, while local Xcode re-resolves silently. That is +# how the flipcash2 0.14.x bump broke Deploy Flipcash. This resolves with the same +# restriction, so the mismatch fails the PR instead. A stale file fails in under a +# minute, before anything is cloned. +# +# "Package.resolved is current" is a required check on main, so the workflow runs +# on every PR: a workflow-level `paths:` filter would leave PRs that skip it +# waiting on a check that never reports. The cheap `changes` job decides instead, +# and a job skipped by its `if:` counts as passing. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: package-resolved-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + changes: + name: Package files changed + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.diff.outputs.packages }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Diff against the base + id: diff + env: + BASE: ${{ github.event.pull_request.base.sha || github.event.before }} + run: | + set -euo pipefail + # A new branch push has an all-zero `before`, and a base that is not + # reachable can't be diffed; check rather than guess in both cases. + if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then + echo "packages=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git diff --name-only "$BASE" HEAD | grep -qE '(^|/)Package\.(swift|resolved)$|^Code\.xcodeproj/project\.pbxproj$|^\.github/workflows/package-resolved\.yml$'; then + echo "packages=true" >> "$GITHUB_OUTPUT" + else + echo "packages=false" >> "$GITHUB_OUTPUT" + fi + + resolved: + name: Package.resolved is current + needs: changes + if: needs.changes.outputs.packages == 'true' + runs-on: macos-15 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + # FlipcashCore is swift-tools 6.1, which the image's default Xcode may + # predate. Same selection as vectors.yml. + - name: Select the newest installed Xcode + run: | + set -euo pipefail + XCODE=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -V | tail -1 || true) + if [ -n "$XCODE" ]; then + sudo xcode-select -s "$XCODE" + fi + xcodebuild -version + + - name: Resolve without updating Package.resolved + run: | + set -euo pipefail + if ! xcodebuild -resolvePackageDependencies \ + -project Code.xcodeproj \ + -scheme Flipcash \ + -disableAutomaticPackageResolution; then + echo "::error file=Code.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved::Package.resolved does not satisfy the Package.swift requirements. Run 'xcodebuild -resolvePackageDependencies -project Code.xcodeproj -scheme Flipcash' and commit the updated file." + exit 1 + fi + + # Resolution can succeed while still rewriting a pin, which Xcode Cloud + # would see as a change it is not allowed to make. + - name: Package.resolved is unchanged + run: | + git diff --exit-code -- Code.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved