-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Add API Breakage Checking GitHub Action (#3753)
* Add API Checkers * Add Test API Dumps Files * Update apiBreakTest.yml * Add Test * Revert "Add Test" This reverts commit 0e45bdb. * Update apiBreakTest.yml * Update check_api_breakage.sh * Update apiBreakTest.yml * Update apiBreakTest.yml * Update apiBreakTest.yml * Update apiBreakTest.yml * Update apiBreakTest.yml * Update apiBreakTest.yml * Update apiBreakTest.yml * Update API dumps for new version * Update APIDigesterCheck.yml * Update apiBreakTest.yml * Update APIDigesterCheck.yml * Update API dumps for new version * Update APIDigesterCheck.yml * Update API dumps for new version * Auto-send Slack Message when Check Failed * Update API dumps for new version * Use aws-amplify-ops for committing * Update API dumps for new version * Update apiBreakTest.yml * Update CODEOWNERS * Update apiBreakTest.yml to remove review adding function * Enabling Configurable Exceptions * Update CODEOWNERS * Rename APIDigesterCheck.yml to api_digester_check.yml * Update api_digester_check.yml * Update api_digester_check.yml * Update and rename apiBreakTest.yml to api-breaking-changes-detection.yml * Rename check_api_breakage.sh to CircleciScripts/check_api_breakage.sh * Update api-breaking-changes-detection.yml * Update API dumps for new version --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: aws-amplify-ops <[email protected]>
- Loading branch information
1 parent
151fac5
commit a8beec8
Showing
11 changed files
with
213,537 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
name: Public Interface Breakage Detection | ||
|
||
on: | ||
pull_request: | ||
|
||
permissions: | ||
contents: write | ||
pull-requests: write | ||
|
||
jobs: | ||
build-and-check-api-breakage: | ||
name: Build and Check API Breakage | ||
runs-on: macos-latest | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 | ||
with: | ||
ref: ${{ github.head_ref }} # Checkout the PR branch | ||
fetch-depth: 1 | ||
|
||
- name: Fetch the branchs | ||
run: | | ||
git fetch origin ${{ github.sha }} | ||
- name: Setup and Run Swift API Diff | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
run: | | ||
# Define the list of exceptions to filter out | ||
exceptions=( | ||
'has been added as a new enum case$' | ||
'is now with @_spi$' | ||
) | ||
# Define the mandatory patterns to filter out | ||
mandatory_patterns=( | ||
'^/\*' | ||
'^$' | ||
) | ||
# Function to apply patterns with grep | ||
apply_patterns() { | ||
local input="$1" | ||
local output="$input" | ||
# Apply mandatory patterns | ||
for pattern in "${mandatory_patterns[@]}"; do | ||
output=$(echo "$output" | grep -v "$pattern") | ||
done | ||
# Apply exceptions | ||
for exception in "${exceptions[@]}"; do | ||
output=$(echo "$output" | grep -v "$exception") | ||
done | ||
echo "$output" | ||
} | ||
echo "Swift version: $(swift --version)" | ||
echo "Swift package manager version: $(swift package --version)" | ||
swift package resolve | ||
# Ensure we are in the correct directory | ||
cd $GITHUB_WORKSPACE | ||
# Run swift-api-diff commands here directly | ||
NEW_API_DIR=$(mktemp -d) | ||
OLD_API_DIR=$(mktemp -d) | ||
SDK_PATH=$(xcrun --show-sdk-path) | ||
# Get all library module names | ||
# Moduels with aws-crt-swift as dependency are not listed due to swift-api-digester's issue with analyzing C dependencies | ||
modules=$(swift package dump-package | jq -r '.products | map(select(.name == "Amplify" or .name == "CoreMLPredictionsPlugin" or .name == "AWSDataStorePlugin" or .name == "AWSPluginsCore")) | map(.name) | .[]') | ||
echo "Modules: $modules" | ||
echo "Fetching old version..." | ||
git fetch origin ${{ github.event.pull_request.base.sha }} | ||
git checkout ${{ github.event.pull_request.base.sha }} | ||
built=false | ||
for module in $modules; do | ||
# If file doesn't exits in the old directory | ||
if [ ! -f api-dump/${module}.json ]; then | ||
echo "Old API file does not exist in the base branch. Generating it..." | ||
# Check if the project has been built | ||
if ! $built; then | ||
echo "Building project..." | ||
swift build > /dev/null 2>&1 || { echo "Failed to build project"; exit 1; } | ||
built=true | ||
fi | ||
# Generate the API file using api-digester | ||
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "$module" -o "$OLD_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump new SDK for module $module"; exit 1; } | ||
else | ||
# Use the api-dump/${module}.json file from the base branch directly | ||
cp "api-dump/${module}.json" "$OLD_API_DIR/${module}.json" | ||
fi | ||
done | ||
echo "Fetching new version..." | ||
git checkout ${{ github.sha }} | ||
git log -1 # Print the commit info for debugging | ||
swift build> /dev/null 2>&1 || { echo "Failed to build new version"; exit 1; } | ||
for module in $modules; do | ||
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "$module" -o "$NEW_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump new SDK for module $module"; exit 1; } | ||
done | ||
# Compare APIs for each module and capture the output | ||
api_diff_output="" | ||
for module in $modules; do | ||
swift api-digester -sdk "$SDK_PATH" -diagnose-sdk --input-paths "$OLD_API_DIR/${module}.json" --input-paths "$NEW_API_DIR/${module}.json" >> "api-diff-report-${module}.txt" 2>&1 | ||
module_diff_output=$(apply_patterns "$(cat "api-diff-report-${module}.txt")") | ||
if [ -n "$module_diff_output" ]; then | ||
api_diff_output="${api_diff_output}\n**Module: ${module}**\n${module_diff_output}\n" | ||
# Check if there are lines containing "has been renamed to Func" | ||
if echo "$module_diff_output" | grep -q 'has been renamed to Func'; then | ||
# Capture the line containing "has been renamed to Func" | ||
renamed_line=$(echo "$module_diff_output" | grep 'has been renamed to Func') | ||
# Append a message to the module_diff_output | ||
api_diff_output="${api_diff_output}👉🏻 _Note: If you're just adding optional parameters to existing methods, neglect the line:_\n_${renamed_line}_\n" | ||
fi | ||
fi | ||
done | ||
echo "API_DIFF_OUTPUT<<EOF" >> $GITHUB_ENV | ||
if [ -n "$api_diff_output" ]; then | ||
echo "### 💔 Public API Breaking Change detected:" >> $GITHUB_ENV | ||
echo -e "$api_diff_output" >> $GITHUB_ENV | ||
echo "EOF" >> $GITHUB_ENV | ||
else | ||
echo "### ✅ No Public API Breaking Change detected" >> $GITHUB_ENV | ||
echo "EOF" >> $GITHUB_ENV | ||
fi | ||
# Checkout to the branch associated with the pull request | ||
git stash --include-untracked | ||
git checkout ${{ github.head_ref }} | ||
if [ ! -d "api-dump" ]; then | ||
echo "api-dump folder does not exist. Creating it..." | ||
mkdir -p "api-dump" | ||
fi | ||
# Update the api-dump folder of the new version by making a commit if there are changes | ||
for module in $modules; do | ||
if [ ! -f api-dump/${module}.json ]; then | ||
echo "API file does not exist in api-dump folder. Creating it..." | ||
echo "{}" > "api-dump/${module}.json" | ||
fi | ||
if ! diff "$NEW_API_DIR/${module}.json" "api-dump/${module}.json" > /dev/null; then | ||
echo "Updating API Dumps..." | ||
mv "$NEW_API_DIR/${module}.json" "api-dump/${module}.json" | ||
fi | ||
done | ||
git config --global user.name "aws-amplify-ops" | ||
git config --global user.email "[email protected]" | ||
git add api-dump/*.json | ||
if ! git diff --cached --quiet --exit-code; then | ||
# Get the file names that have changes | ||
changed_files=$(git diff --cached --name-only) | ||
push_changes=false | ||
for file in $changed_files; do | ||
if [[ $file == api-dump/* ]]; then | ||
# Get the number of lines in the file | ||
total_lines=$(wc -l < "$file") | ||
# Get the line numbers of the changes | ||
changed_lines=$(git diff --cached -U0 "$file" | grep -o '@@ [^ ]* [^ ]* @@' | awk '{print $3}' | cut -d ',' -f1 | sed 's/[^0-9]//g') | ||
echo "Changed lines in $file: $changed_lines" | ||
# Check if any change is not within the last 10 lines | ||
for line in $changed_lines; do | ||
if [ "$line" -le "$((total_lines - 10))" ]; then | ||
push_changes=true | ||
break | ||
fi | ||
done | ||
# If any file should be pushed, break out of the loop | ||
if [ "$push_changes" = true ]; then | ||
break | ||
fi | ||
fi | ||
done | ||
if [ "$push_changes" = true ]; then | ||
git commit -m "Update API dumps for new version" | ||
git push origin HEAD:${{ github.head_ref }} | ||
else | ||
echo "No changes to commit in the api-dump folder." | ||
fi | ||
else | ||
echo "No changes to commit in the api-dump folder." | ||
fi | ||
git stash pop || true | ||
- name: Comment on PR with API Diff | ||
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
script: | | ||
const apiDiffOutput = process.env.API_DIFF_OUTPUT; | ||
const issueNumber = context.payload.pull_request.number; | ||
const owner = context.repo.owner; | ||
const repo = context.repo.repo; | ||
if (apiDiffOutput && apiDiffOutput.trim().length > 0) { | ||
github.rest.issues.createComment({ | ||
owner: owner, | ||
repo: repo, | ||
issue_number: issueNumber, | ||
body: `## API Breakage Report\n${apiDiffOutput}\n` | ||
}); | ||
} else { | ||
console.log("No API diff output found."); | ||
} | ||
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: Swift API Digester Functionality Check | ||
|
||
on: | ||
schedule: | ||
- cron: '0 15 * * *' # This will run the action every day at 3:00 pm UTC (8:00 am PDT) | ||
workflow_dispatch: # Allows manual triggering | ||
|
||
jobs: | ||
check-swift-api-digester: | ||
runs-on: macos-latest | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 | ||
|
||
- name: Check API Digester | ||
shell: bash | ||
env: | ||
WEBHOOK_URL: ${{ secrets.SLACK_API_CHECKER_WEBHOOK_URL }} | ||
run: | | ||
TEMP_DIR=$(mktemp -d) | ||
echo "Temporary directory created at $TEMP_DIR" | ||
SDK_PATH=$(xcrun --sdk macosx --show-sdk-path) | ||
echo "SDK Path: $SDK_PATH" | ||
# Run swift-api-digester | ||
swift api-digester -sdk "$SDK_PATH" -diagnose-sdk --input-paths api-dump-test/A.json --input-paths api-dump-test/B.json >> "$TEMP_DIR/api-digester-output.txt" 2>&1 | ||
# Display the output | ||
cat "$TEMP_DIR/api-digester-output.txt" | ||
if diff "$TEMP_DIR/api-digester-output.txt" api-dump-test/expected-result.txt; then | ||
echo "The output matches the expected result." | ||
else | ||
echo "The output does not match the expected result." | ||
WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | ||
echo "$WORKFLOW_URL" | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"WORKFLOW_URL":"{}"}' | ||
exit 1 | ||
fi |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#!/bin/bash | ||
|
||
# Script: check_api_breakage.sh | ||
|
||
# Ensure the script is run from the root of the repository | ||
if [ ! -d ".git" ]; then | ||
echo "This script must be run from the root of the repository." | ||
exit 1 | ||
fi | ||
|
||
# Check if a base branch is provided as an argument | ||
if [ -z "$1" ]; then | ||
echo "Usage: $0 <base-branch>" | ||
exit 1 | ||
fi | ||
|
||
BASE_BRANCH=$1 | ||
|
||
# Setup environment variables | ||
OLD_API_DIR=$(mktemp -d) | ||
NEW_API_DIR=$(mktemp -d) | ||
REPORT_DIR=$(mktemp -d) | ||
SDK_PATH=$(xcrun --show-sdk-path) | ||
|
||
modules=$(swift package dump-package | jq -r '.products | map(select(.name == "Amplify" or .name == "CoreMLPredictionsPlugin")) | map(.name) | .[]') | ||
|
||
# Ensure repository is up to date | ||
git fetch origin | ||
|
||
# Fetch and build the main branch | ||
echo "Fetching API from base branch ($BASE_BRANCH)..." | ||
git checkout $BASE_BRANCH | ||
git pull origin $BASE_BRANCH | ||
swift build > /dev/null 2>&1 || { echo "Failed to build base branch ($BASE_BRANCH)"; exit 1; } | ||
for module in $modules; do | ||
# If file doesn't exits in the old directory | ||
if [ ! -f api-dump/${module}.json ]; then | ||
echo "Old API file does not exist in the base branch. Generating it..." | ||
# Check if the project has been built | ||
if ! $built; then | ||
echo "Building project..." | ||
swift build > /dev/null 2>&1 || { echo "Failed to build project"; exit 1; } | ||
built=true | ||
fi | ||
|
||
# Generate the API file using api-digester | ||
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "$module" -o "$OLD_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump new SDK for module $module"; exit 1; } | ||
else | ||
# Use the api-dump/${module}.json file from the base branch directly | ||
cp "api-dump/${module}.json" "$OLD_API_DIR/${module}.json" | ||
fi | ||
done | ||
|
||
# Fetch and build the current branch | ||
echo "Fetching API from current branch..." | ||
git checkout - | ||
git pull origin "$(git rev-parse --abbrev-ref HEAD)" | ||
swift build > /dev/null 2>&1 || { echo "Failed to build current branch"; exit 1; } | ||
for module in $modules; do | ||
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "${module}" -o "$NEW_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump SDK for current branch"; exit 1; } | ||
done | ||
|
||
# Compare APIs for each module and capture the output | ||
api_diff_output="" | ||
for module in $modules; do | ||
swift api-digester -sdk "$SDK_PATH" -diagnose-sdk --input-paths "$OLD_API_DIR/${module}.json" --input-paths "$NEW_API_DIR/${module}.json" > "$REPORT_DIR/api-diff-report.txt" 2>&1 | ||
module_diff_output=$(grep -v '^/\*' "$REPORT_DIR/api-diff-report.txt" | grep -v '^$' || true) | ||
if [ -n "$module_diff_output" ]; then | ||
api_diff_output=$(printf "%s\n Module: %s\n%s\n" "$api_diff_output" "$module" "$module_diff_output") | ||
# Check if there are lines containing "has been renamed to Func" | ||
if echo "$module_diff_output" | grep -q 'has been renamed to Func'; then | ||
# Capture the line containing "has been renamed to Func" | ||
renamed_line=$(echo "$module_diff_output" | grep 'has been renamed to Func') | ||
|
||
# Append a message to the module_diff_output | ||
api_diff_output="${api_diff_output}👉🏻 _Note: If you're just adding optional parameters to existing methods, neglect the line:_\n_${renamed_line}_\n" | ||
fi | ||
fi | ||
done | ||
|
||
if [ -n "$api_diff_output" ]; | ||
then | ||
echo "💔 Public API Breaking Change detected:" | ||
echo "$api_diff_output" | ||
else | ||
echo "✅ No Public API Breaking Change detected" | ||
fi |
Oops, something went wrong.