forked from googleapis/google-cloud-go
-
Notifications
You must be signed in to change notification settings - Fork 0
181 lines (178 loc) · 7.75 KB
/
apidiff.yml
File metadata and controls
181 lines (178 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
---
name: apidiff
on:
pull_request:
permissions:
contents: read
env:
GOTOOLCHAIN: local
jobs:
scan_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: main
- name: Get main commit
id: main
run: echo "hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '1.26.x'
- name: Get changed directories
id: changed_dirs
# Ignore changes to the internal and root directories.
# Ignore added files with --diff-filter=a.
run: |
dirs=$(cd internal/actions; go run ./cmd/changefinder -q --diff-filter=a --dir=${{ github.workspace }})
if [ -z "$dirs" ]
then
echo "skip=1" >> "$GITHUB_OUTPUT"
echo "No changes worth diffing!"
else
for d in $dirs; do list=${list},\"${d}\"; done
echo "changed={\"changed\":[${list#,}]}" >> "$GITHUB_OUTPUT"
echo "skip=" >> "$GITHUB_OUTPUT"
fi
outputs:
changed_dirs: ${{ steps.changed_dirs.outputs.changed }}
skip: ${{ steps.changed_dirs.outputs.skip }}
apidiff:
needs: scan_changes
runs-on: ubuntu-latest
# This job runs if there are changed directories and the PR is not explicitly
# marked as allowing breaking changes.
if: ${{ !needs.scan_changes.outputs.skip && !contains(github.event.pull_request.labels.*.name, 'breaking change allowed') }}
permissions:
pull-requests: write
strategy:
matrix: ${{ fromJson(needs.scan_changes.outputs.changed_dirs) }}
steps:
- uses: actions/setup-go@v6
with:
go-version: '1.26.x'
- name: Install latest apidiff
run: go install golang.org/x/exp/cmd/apidiff@latest
- uses: actions/checkout@v6
with:
ref: main
- name: Create baseline
id: baseline
run: |
export CHANGED=${MATRIX_CHANGED}
echo pkg="${CHANGED//\//_}_pkg.main" >> "$GITHUB_OUTPUT"
env:
MATRIX_CHANGED: ${{ matrix.changed }}
- name: Create Go package baseline
run: cd ${MATRIX_CHANGED} && apidiff -m -w ${STEPS_BASELINE_OUTPUTS_PKG} .
env:
MATRIX_CHANGED: ${{ matrix.changed }}
STEPS_BASELINE_OUTPUTS_PKG: ${{ steps.baseline.outputs.pkg }}
- name: Upload baseline package data
uses: actions/upload-artifact@v6
with:
name: ${{ steps.baseline.outputs.pkg }}
path: ${{ matrix.changed }}/${{ steps.baseline.outputs.pkg }}
retention-days: 1
- uses: actions/checkout@v6
- name: Download baseline package data
uses: actions/download-artifact@v7
with:
name: ${{ steps.baseline.outputs.pkg }}
path: ${{ matrix.changed }}
# Step 1: Run apidiff to detect breaking changes.
# Instead of failing the job immediately, we capture the output and set a
# flag (`breaking_change_found`) to be used in subsequent steps.
- name: Detect breaking changes
id: detect
# Only ignore Go interface additions when the PR is from Librarian (e.g., librarian-20251016T095208Z)
# as it is likely a new method added to the gRPC client stub interface, which is non-breaking.
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
MATRIX_CHANGED: ${{ matrix.changed }}
STEPS_BASELINE_OUTPUTS_PKG: ${{ steps.baseline.outputs.pkg }}
run: |
cd ${MATRIX_CHANGED}
apidiff -m -incompatible ${STEPS_BASELINE_OUTPUTS_PKG} . > diff.txt
if [[ "$PR_HEAD_REF" == librarian-* ]]; then
sed -i '/: added/d' ./diff.txt
fi
cat diff.txt
if [ -s diff.txt ]; then
echo "breaking_change_found=true" >> "$GITHUB_OUTPUT"
else
echo "breaking_change_found=false" >> "$GITHUB_OUTPUT"
fi
# Step 2: Conditionally fail the job for GA APIs.
# This step runs only if a breaking change was found in the previous step.
# It checks if the changed module is a GA version (e.g., "apiv1", "apiv2").
# If it is, the `exit 1` command fails the job, blocking the PR.
# For non-GA versions (e.g., "apiv1beta1"), this step is skipped, and the
# job continues.
- name: Conditionally fail for GA APIs
id: ga_check # Add an ID to this step to check its outcome later.
if: steps.detect.outputs.breaking_change_found == 'true'
run: |
# Fail if the module path itself indicates that it is GA (e.g. "compute/apiv1").
# Some modules (e.g. "shopping") contain multiple API versions below the module root.
# For these, we also check diff.txt for GA package paths (e.g. apiv1/) to
# catch breaking changes in GA packages nested within a non-versioned module.
if [[ "${MATRIX_CHANGED}" =~ apiv[0-9]+$ ]] || grep -q -E "(/|^)apiv[0-9]+(/|:)" "${MATRIX_CHANGED}/diff.txt"; then
echo "Error: Breaking change detected in GA API: ${MATRIX_CHANGED}"
exit 1
fi
echo "Breaking change in non-GA API, proceeding."
env:
MATRIX_CHANGED: ${{ matrix.changed }}
# Step 3: Add a "breaking change" label.
# This step runs if a breaking change was found, regardless of whether the
# API is GA or not. This ensures that all breaking changes are labeled for
# review, even if they don't block the PR.
- name: Add breaking change label
if: ${{ steps.detect.outputs.breaking_change_found == 'true' && !github.event.pull_request.head.repo.fork }}
uses: actions/github-script@v8
with:
script: |
github.rest.issues.addLabels({
issue_number: ${{ github.event.number }},
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['breaking change']
})
# Step 4: For non-GA APIs, post the breaking change details as a comment.
# This step only runs if the ga_check step succeeded, meaning a breaking
# change was found in a non-GA API. This provides high-visibility feedback
# without blocking the PR, and avoids redundant comments for GA APIs where
# the failed presubmit check is the primary signal.
- name: Post breaking change details as a comment
if: ${{ steps.ga_check.outcome == 'success' && !github.event.pull_request.head.repo.fork }}
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const diff = fs.readFileSync('${{ matrix.changed }}/diff.txt', 'utf8');
const moduleName = '${{ matrix.changed }}';
const commentHeader = `### ⚠️ Breaking change detected in pre-GA module: \`${moduleName}\``;
const body = `${commentHeader}\n\nThe apidiff check has detected one or more breaking changes in this module for pre-GA APIs.\n\nPre-GA breaking changes do not block the pull request, but are flagged here for review.\n\nFor any pre-GA breaking change that needs investigation, open a new issue containing a link to this comment, then you may proceed with reviewing and merging this PR.\n\n\`\`\`\n${diff}\n\`\`\``;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existingComment = comments.find(comment => comment.body.startsWith(commentHeader));
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});
}