diff --git a/.github/workflows/api-diff.yml b/.github/workflows/api-diff.yml index 925263e8f..1c1150f9c 100644 --- a/.github/workflows/api-diff.yml +++ b/.github/workflows/api-diff.yml @@ -7,8 +7,6 @@ on: - opened - synchronize - reopened - - labeled - - unlabeled workflow_dispatch: inputs: baseline_version: @@ -29,8 +27,6 @@ jobs: # Empty unless overridden via workflow_dispatch; the api-diff task then # falls back to in pom.xml. API_DIFF_BASELINE_VERSION: ${{ inputs.baseline_version }} - BREAKING_API_CHANGE_ACCEPTED: >- - ${{ contains(github.event.pull_request.labels.*.name, 'breaking-api-change-accepted') }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 @@ -54,38 +50,3 @@ jobs: echo "Run 'mise run api-diff' locally and commit the updated docs/apidiffs." exit 1 fi - - name: Fail on incompatible published API changes - run: | - python3 - <<'PY' - import os - from pathlib import Path - import sys - import xml.etree.ElementTree as ET - - failures = [] - for report in sorted(Path(".").glob("**/target/japicmp/api-diff.xml")): - parts = report.parts - module = "/".join(parts[: parts.index("target")]) - tree = ET.parse(report) - for change in tree.findall(".//compatibilityChange"): - binary = change.get("binaryCompatible") == "false" - source = change.get("sourceCompatible") == "false" - if binary or source: - failures.append((module, change.get("type", "unknown"))) - - if not failures: - print("No incompatible published API changes detected.") - sys.exit(0) - - print("Incompatible published API changes detected:") - for module, change_type in failures[:100]: - print(f"- {module}: {change_type}") - if len(failures) > 100: - print(f"... and {len(failures) - 100} more") - if os.environ.get("BREAKING_API_CHANGE_ACCEPTED") == "true": - print("Accepted by PR label `breaking-api-change-accepted`.") - sys.exit(0) - print("Run `mise run api-diff` locally for full japicmp output.") - print("Reports are written to `**/target/japicmp/*`.") - sys.exit(1) - PY diff --git a/.github/workflows/detect-api-changes.yml b/.github/workflows/detect-api-changes.yml new file mode 100644 index 000000000..e529e2ec7 --- /dev/null +++ b/.github/workflows/detect-api-changes.yml @@ -0,0 +1,77 @@ +--- +name: Detect API Changes + +on: + # pull_request_target is used so the workflow can update labels and comments + # without checking out or executing code from the PR branch. + # zizmor: ignore[dangerous-triggers] -- this workflow only reads PR metadata + # via the GitHub API and never checks out or executes code from the PR branch. + pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: {} + +jobs: + detect-api-changes: + if: github.repository == 'prometheus/client_java' + runs-on: ubuntu-24.04 + permissions: + issues: write + pull-requests: write + steps: + - name: Check for API changes and update PR + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + MARKER="" + + api_files=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files" --paginate \ + --jq '.[] | select(.filename | startswith("docs/apidiffs/current_vs_latest/")) | .filename') + + comment_id=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --paginate \ + --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1) + + if [[ -z "$api_files" ]]; then + echo "No API diff files changed." + gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "api-change" 2>/dev/null || true + if [[ -n "$comment_id" ]]; then + gh api --method DELETE "repos/${REPO}/issues/comments/${comment_id}" + fi + exit 0 + fi + + echo "API diff files changed:" + echo "$api_files" + + gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "api-change" + + modules=$(echo "$api_files" \ + | sed 's|docs/apidiffs/current_vs_latest/||' \ + | sed 's|\.txt$||' \ + | sort \ + | sed 's/^/- /') + + body=$(cat <