Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions .github/backwards-compatibility-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/bin/bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

# USAGE:
#
# backwards-compatibility-check.sh COMPONENT [BASE_REF]
#
# COMPONENT: The component directory name to run the backwards compatibility check for.
# BASE_REF: Optional. The baseline git ref (e.g. 'main', 'origin/main' or a release tag) to compare against. Defaults to 'main'.

if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
echo "usage: backwards-compatibility-check.sh COMPONENT [BASE_REF]"
exit 1
fi

COMPONENT=$1
BASE_REF=${2:-main}

# Exception for 'dev'
if [ "${COMPONENT}" = "dev" ]; then
echo "Skipping dev directory (not a public component)."
exit 0
fi

# Check if component directory exists
if [ ! -d "${COMPONENT}" ]; then
echo "Error: Directory ${COMPONENT} does not exist!" >&2
exit 1
fi

# Check if composer.json exists
COMP_JSON="${COMPONENT}/composer.json"
if [ ! -f "${COMP_JSON}" ]; then
echo "Error: composer.json not found in ${COMPONENT}!" >&2
exit 1
fi

echo "Checking backwards compatibility for component: ${COMPONENT} against baseline: ${BASE_REF}" >&2

# Check if the component existed in the baseline reference
if ! git rev-parse --verify "${BASE_REF}:${COMPONENT}" >/dev/null 2>&1; then
echo "Component ${COMPONENT} did not exist in baseline ${BASE_REF}. Skipping check (all additions)." >&2
exit 0
fi

# Create a temporary directory
TMP_DIR=$(mktemp -d)

# Initialize a dummy git repo inside TMP_DIR so roave-backward-compatibility-check can compare revisions
(
cd "${TMP_DIR}"
git init -q
)

# Extract baseline files from the BASE_REF, stripping the prefix folder so they land at the root of TMP_DIR
if ! git archive "${BASE_REF}" "${COMPONENT}" | tar -x --strip-components=1 -C "${TMP_DIR}" 2>/dev/null; then
echo "Error: Failed to archive and extract files for ${COMPONENT} from git ref ${BASE_REF}." >&2
rm -rf "${TMP_DIR}"
exit 1
fi

(
cd "${TMP_DIR}"
git add -A
git commit -q -m "Base state from ${BASE_REF}"
)

# Copy the current local component files over the baseline repository,
# making sure to exclude vendor directories or composer-local files.
echo "Applying local changes from ${COMPONENT} to the baseline clone..." >&2
rsync -a --exclude="vendor/" --exclude="composer-local.json" "${COMPONENT}/" "${TMP_DIR}/"

# Commit the changes in the cloned split repository so we can compare them
CODE=0
if (
cd "${TMP_DIR}"
git add -A

# Check if there are any changes to commit
if ! git diff --cached --quiet; then
git commit -q -m "Apply local PR changes"
echo "Running Roave Backward Compatibility Check..." >&2

# Locate the roave binary portably
COMPOSER_BIN=$(composer global config bin-dir --absolute 2>/dev/null || echo ~/.composer/vendor/bin)
ROAVE_BIN=$(command -v roave-backward-compatibility-check || echo "${COMPOSER_BIN}/roave-backward-compatibility-check")

FORMAT=${BC_FORMAT:-markdown}
if [ "${FORMAT}" = "markdown" ]; then
# Run Roave check and capture output
OUTPUT=$("${ROAVE_BIN}" --from=HEAD~1 --format=markdown 2>/dev/null || true)
if [ -n "${OUTPUT}" ]; then
echo "<details>"
echo "<summary><b>${COMPONENT}</b>: Backwards Compatibility Breaks Detected</summary>"
echo ""
echo "${OUTPUT}"
echo "</details>"
echo ""
exit 1
fi
else
if ! "${ROAVE_BIN}" --from=HEAD~1 --format="${FORMAT}"; then
echo "❌ BC Breaks detected in ${COMPONENT}!" >&2
exit 1
else
echo "✅ No BC Breaks detected in ${COMPONENT}." >&2
fi
fi
else
echo "No files modified for ${COMPONENT} compared to ${BASE_REF}. Skipping check." >&2
fi
); then
CODE=0
else
CODE=1
fi

rm -rf "${TMP_DIR}"
exit $CODE
88 changes: 55 additions & 33 deletions .github/workflows/release-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
branches: ['main']
permissions:
contents: read
pull-requests: write

Check failure on line 8 in .github/workflows/release-checks.yaml

View workflow job for this annotation

GitHub Actions / zizmor-output

excessive-permissions

release-checks.yaml:8: overly broad permissions: pull-requests: write is overly broad at the workflow level
jobs:
# More info at https://github.com/Roave/BackwardCompatibilityCheck.
backwards-compatibility-check:
Expand All @@ -20,49 +21,70 @@
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: "8.1"
- name: "Rebase PR branch onto main branch"
if: github.event.pull_request.user.login != 'release-please[bot]'
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git config user.name "Github Actions"
git config user.email "actions@github.com"
git checkout "$HEAD_SHA"
if ! git rebase origin/main; then
echo "Failed to rebase PR branch onto main. There may be conflicts. Please resolve conflicts or rebase manually."
exit 1
fi
- name: "Install dependencies"
run: composer global require "roave/backward-compatibility-check:^8.2"
- name: "Check for BC breaks"
if: github.event.pull_request.user.login != 'release-please[bot]'
run: |
~/.composer/vendor/bin/roave-backward-compatibility-check --from=origin/main --format=github-actions
- name: "Check for BC label"
env:
HAS_BC_LABEL: ${{ contains(github.event.pull_request.title, '!:') }}
run: |
if [[ "true" == "${HAS_BC_LABEL}" ]]; then
echo "Breaking change label found in PR title"
exit 1
fi
- name: Get Latest Release
if: github.event.pull_request.user.login == 'release-please[bot]'
id: latest-release
uses: pozetroninc/github-action-get-latest-release@5ac6001ed175b443484c81ab32a20e12132be99c # master
with:
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: "Check for BC breaks (Next Release)"
if: github.event.pull_request.user.login == 'release-please[bot]'
# We've already approved and justified the breaking changes. Run the check but continue on error
continue-on-error: true
- name: "Check for BC breaks"
id: check-bc
run: |
~/.composer/vendor/bin/roave-backward-compatibility-check \
--from=${STEPS_LATEST_RELEASE_OUTPUTS_RELEASE} \
--to=origin/main --format=github-actions
env:
STEPS_LATEST_RELEASE_OUTPUTS_RELEASE: ${{ steps.latest-release.outputs.release }}
git config --global user.name "Github Actions"
git config --global user.email "actions@github.com"

# Determine base ref for git diff
BASE_REF="${{ steps.latest-release.outputs.release }}"

Check failure on line 40 in .github/workflows/release-checks.yaml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/template-injection

code injection via template expansion: may expand into attacker-controllable code
BASE_REF=${BASE_REF:-origin/main}

# Find changed component directories
CHANGED_COMPONENTS=$(git diff ${BASE_REF} --name-only | grep -E '^[A-Z][a-zA-Z0-9]*/' | cut -d'/' -f1 | sort -u || true)
if [ -z "${CHANGED_COMPONENTS}" ]; then
echo "No public component directories have changed."
exit 0
fi

IS_RELEASE_PR="${{ github.event.pull_request.user.login == 'release-please[bot]' }}"
BC_BREAKS=""
FAIL=""
BC_FORMAT="github-actions"
[ "${IS_RELEASE_PR}" = "true" ] && BC_FORMAT="markdown"

for COMPONENT in ${CHANGED_COMPONENTS}; do
OUTPUT=$(BC_FORMAT="${BC_FORMAT}" bash .github/backwards-compatibility-check.sh "${COMPONENT}" "${BASE_REF}")
STATUS=$?

if [ "${IS_RELEASE_PR}" = "true" ]; then
BC_BREAKS="${BC_BREAKS}${OUTPUT}"
else
printf "%s\n" "${OUTPUT}"
if [ ${STATUS} -ne 0 ]; then
FAIL="true"
fi
fi
done

if [ "${IS_RELEASE_PR}" = "true" ]; then
printf "bc-breaks<<EOF\n%s\nEOF\n" "${BC_BREAKS}" >> $GITHUB_OUTPUT
elif [ "${FAIL}" = "true" ]; then
exit 1
elif [[ "true" == "${{ contains(github.event.pull_request.title, '!:') }}" ]]; then
echo "❌ Error: You indicated breaking changes in your PR title (!:), but no backwards compatibility breaks were detected by the automated check."
exit 1
fi
- name: "Add or update PR comment with BC breaks for Release PRs"
if: github.event.pull_request.user.login == 'release-please[bot]' && steps.check-bc.outputs.bc-breaks != ''
uses: marocchino/sticky-pull-request-comment@v2

Check failure on line 80 in .github/workflows/release-checks.yaml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 80 in .github/workflows/release-checks.yaml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

release-checks.yaml:80: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
header: bc-breaking-changes-comment
message: |
## ⚠️ Backwards Compatibility (BC) Breaking Changes Notice
This PR contains backward compatibility breaking changes detected by the automated BC check against the latest release. Please review the details below:

${{ steps.check-bc.outputs.bc-breaks }}

# Ensure the release PR does not contain an unexpected (e.g. 2.0.0) major version release
# Add "MAJOR_VERSION_ALLOWED=component1,component2" to the PR description to allow major version
Expand Down
1 change: 1 addition & 0 deletions Gax/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ composer.lock
composer-test.lock
vendor/
build/artifacts/
tests/Conformance/showcase.pem
artifacts/
.idea
.metadata
Expand Down
Loading