1+ #! /usr/bin/env bash
2+
3+ # Copyright 2024 The Kubernetes Authors.
4+ #
5+ # Licensed under the Apache License, Version 2.0 (the "License");
6+ # you may not use this file except in compliance with the License.
7+ # You may obtain a copy of the License at
8+ #
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ #
11+ # Unless required by applicable law or agreed to in writing, software
12+ # distributed under the License is distributed on an "AS IS" BASIS,
13+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ # See the License for the specific language governing permissions and
15+ # limitations under the License.
16+
17+ # apidiff.sh: Compare public API changes between revisions or directories using Git worktrees.
18+
19+ set -euo pipefail
20+
21+ # Usage Information
22+ usage () {
23+ echo " Usage: $0 [-r <revision>] [-t <revision>] [directory ...]"
24+ echo " -t <revision>: Report changes in code up to and including this revision."
25+ echo " Default is the current working tree instead of a revision."
26+ echo " -r <revision>: Report changes in code added since this revision."
27+ echo " Default is the common base of origin/master and HEAD."
28+ exit 1
29+ }
30+
31+ # Default Values
32+ TARGET_REVISION=" " # -t: Target revision
33+ REFERENCE_REVISION=" " # -r: Reference revision
34+ TARGET_DIR=" ." # Default directory to compare is current working directory
35+ API_DIFF_TOOL=" apidiff"
36+ REF_API_SNAPSHOT=" ref.api"
37+ TGT_API_SNAPSHOT=" target.api"
38+ WORKTREES=() # Track created worktrees for cleanup
39+
40+ # Parse Command-Line Arguments
41+ while getopts " :t:r:" opt; do
42+ case ${opt} in
43+ t) TARGET_REVISION=" $OPTARG " ;;
44+ r) REFERENCE_REVISION=" $OPTARG " ;;
45+ \? ) echo " Error: Invalid option -$OPTARG " >&2 ; usage ;;
46+ :) echo " Error: Option -$OPTARG requires an argument." >&2 ; usage ;;
47+ esac
48+ done
49+ shift $(( OPTIND - 1 ))
50+
51+ # Remaining arguments are directories
52+ if [ " $# " -ge 1 ]; then
53+ TARGET_DIR=" $1 "
54+ fi
55+
56+ # Check for apidiff tool, install it if not found
57+ if ! command -v " ${API_DIFF_TOOL} " & > /dev/null; then
58+ echo " Installing apidiff into ${GOBIN} ."
59+ go install golang.org/x/exp/cmd/apidiff@latest
60+ fi
61+
62+ # Fetch common base if -r is not set
63+ if [ -z " ${REFERENCE_REVISION} " ]; then
64+ echo " Determining common base with origin/master..."
65+ REFERENCE_REVISION=$( git merge-base origin/master HEAD)
66+ fi
67+
68+ # Step 1: Create a temporary directory for worktrees
69+ TMP_DIR=$( mktemp -d)
70+ trap ' cleanup' EXIT
71+
72+ cleanup () {
73+ # Remove all created worktrees
74+ for worktree in " ${WORKTREES[@]} " ; do
75+ git worktree remove --force " $worktree "
76+ done
77+
78+ # Remove temporary directory
79+ rm -rf " ${TMP_DIR} "
80+ }
81+
82+ # Step 2: Export API snapshot for the reference revision
83+ REF_WORKTREE=" ${TMP_DIR} /ref"
84+ echo " Creating Git worktree for reference revision: ${REFERENCE_REVISION} "
85+ git worktree add " ${REF_WORKTREE} " " ${REFERENCE_REVISION} " --quiet
86+ WORKTREES+=(" ${REF_WORKTREE} " )
87+ echo " Exporting API snapshot for reference revision..."
88+ pushd " ${REF_WORKTREE} " > /dev/null
89+ " ${API_DIFF_TOOL} " -m -w " ${TMP_DIR} /${REF_API_SNAPSHOT} " " ${TARGET_DIR} "
90+ popd > /dev/null
91+
92+ # Step 3: Export API snapshot for the target revision
93+ TGT_WORKTREE=" ${TMP_DIR} /target"
94+ if [ -n " ${TARGET_REVISION} " ]; then
95+ echo " Creating Git worktree for target revision: ${TARGET_REVISION} "
96+ git worktree add " ${TGT_WORKTREE} " " ${TARGET_REVISION} " --quiet
97+ WORKTREES+=(" ${TGT_WORKTREE} " )
98+ TGT_PATH=" ${TGT_WORKTREE} "
99+ else
100+ # If no target revision specified, compare with current working tree
101+ TGT_PATH=" ${TARGET_DIR} "
102+ fi
103+
104+ echo " Exporting API snapshot for target revision..."
105+ pushd " ${TGT_PATH} " > /dev/null
106+ " ${API_DIFF_TOOL} " -m -w " ${TMP_DIR} /${TGT_API_SNAPSHOT} " " ${TARGET_DIR} "
107+ popd > /dev/null
108+
109+ # Step 4: Compare the two API snapshots for incompatible changes
110+ # Step 4: Compare the two API snapshots for changes
111+ echo " Checking for API changes..."
112+ # All changes
113+ all_changes=$( " ${API_DIFF_TOOL} " -m " ${TMP_DIR} /${REF_API_SNAPSHOT} " " ${TMP_DIR} /${TGT_API_SNAPSHOT} " 2>&1 | grep -v -e " ^Ignoring internal package" || true)
114+ # Incompatible changes
115+ incompatible_changes=$( " ${API_DIFF_TOOL} " -incompatible -m " ${TMP_DIR} /${REF_API_SNAPSHOT} " " ${TMP_DIR} /${TGT_API_SNAPSHOT} " 2>&1 | grep -v -e " ^Ignoring internal package" || true)
116+
117+ # Print out results
118+ echo
119+ echo " API compatibility check completed."
120+ res=0
121+ if [ -n " $incompatible_changes " ]; then
122+ res=1
123+ echo " Incompatible API changes found!"
124+ else
125+ echo " No incompatible API changes found."
126+ fi
127+ if [ -z " $all_changes " ]; then
128+ echo " No API changes found."
129+ else
130+ echo " All API changes:"
131+ echo " $all_changes "
132+ echo
133+ fi
134+
135+ exit ${res}
0 commit comments