diff --git a/.github/workflows/verify-api.yml b/.github/workflows/verify-api.yml new file mode 100644 index 0000000000..57d3863883 --- /dev/null +++ b/.github/workflows/verify-api.yml @@ -0,0 +1,172 @@ +name: verify api +on: + pull_request: + paths: + - 'src/**' + - 'test/**/ApiApprovalTests*' + - 'test/Sentry.Testing/ApiExtensions.cs' + - '.github/workflows/verify-api.yml' + +jobs: + run-api-tests: + name: Run API Approval Tests (${{ matrix.rid }}) + runs-on: ${{ matrix.os }} + # This job builds and runs untrusted PR code — keep the token read-only. + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + include: + # macOS covers all non-Windows TFMs (net9.0, net10.0, netstandard, iOS, MacCatalyst, Android) + - os: macos-15 + rid: macos + slnf: Sentry-CI-Build-macOS.slnf + # Windows is required to produce the .NET Framework (net48 / Net4_8) verified files + - os: windows-latest + rid: win-x64 + slnf: Sentry-CI-Build-Windows.slnf + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: recursive + + - name: Remove unused applications + uses: ./.github/actions/freediskspace + + - name: Setup Environment + uses: ./.github/actions/environment + + - name: Restore sentry-native cache + id: cache-native + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: src/Sentry/Platforms/Native/sentry-native + key: sentry-native-${{ matrix.rid }}-${{ hashFiles('scripts/build-sentry-native.ps1') }}-${{ hashFiles('.git/modules/modules/sentry-native/HEAD') }} + enableCrossOsArchive: true + + - name: Build sentry-native (cache miss) + if: steps.cache-native.outputs.cache-hit != 'true' + shell: pwsh + run: scripts/build-sentry-native.ps1 + + - name: Build Native Dependencies + uses: ./.github/actions/buildnative + + - name: Restore .NET Dependencies + run: | + dotnet workload restore + dotnet restore ${{ matrix.slnf }} --nologo + + - name: Build + run: dotnet build ${{ matrix.slnf }} -c Release --no-restore --nologo -v:minimal + + # API approval tests fail when the public API surface changes. We swallow the failure + # here and rely on the produced *.received.txt files to detect and accept the change. + - name: Run API Approval Tests + continue-on-error: true + run: dotnet test ${{ matrix.slnf }} -c Release --no-build --nologo --filter "FullyQualifiedName~ApiApprovalTests" + + - name: Upload Received API Files + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: api-verify-received-${{ matrix.rid }} + path: "**/*.received.txt" + if-no-files-found: ignore + + accept-api-changes: + name: Accept and Commit API Changes + needs: run-api-tests + runs-on: ubuntu-22.04 + if: github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.pull_request.head.ref }} + + # When the matrix produces no received files (clean PR), no artifact is uploaded. + # download-artifact's pattern branch tolerates zero matches without erroring, so + # we don't need `continue-on-error` here — that would mask genuine download failures. + - name: Download Received API Files + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: api-verify-received-* + merge-multiple: true + + - name: Accept Verifier Changes + shell: pwsh + run: pwsh ./scripts/accept-verifier-changes.ps1 + + - name: Detect API Changes + id: detect + shell: bash + run: | + if [[ -z "$(git status --porcelain)" ]]; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + echo "No API verifier changes detected." + else + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "API verifier changes detected:" + git status --short + fi + + - name: Commit Accepted API Changes + if: steps.detect.outputs.has_changes == 'true' + shell: bash + run: | + git config --global user.name 'Sentry Github Bot' + git config --global user.email 'bot+github-bot@sentry.io' + git add -A + git commit -m "Accept API verifier changes" + git push + + - name: Label Public API PR + if: steps.detect.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh label create "public API" --color "0075ca" --description "Modifies the public API surface" --repo "${{ github.repository }}" 2>/dev/null || true + gh pr edit "${{ github.event.pull_request.number }}" --add-label "public API" --repo "${{ github.repository }}" + + # Fork PRs can't be auto-committed since the bot can't push to a contributor's repo. + # Fail the check with guidance so the contributor accepts the changes locally. + report-fork-api-changes: + name: Report API Changes (Fork PR) + needs: run-api-tests + runs-on: ubuntu-22.04 + if: github.event.pull_request.head.repo.full_name != github.repository + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download Received API Files + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: api-verify-received-* + merge-multiple: true + + - name: Accept Verifier Changes + shell: pwsh + run: pwsh ./scripts/accept-verifier-changes.ps1 + + - name: Fail If API Changes Detected + shell: bash + run: | + if [[ -n "$(git status --porcelain)" ]]; then + echo "::error::Public API changes detected. Please run the following locally and push the result:" + echo "::error:: dotnet test && pwsh ./scripts/accept-verifier-changes.ps1" + exit 1 + fi + echo "No API verifier changes detected."