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