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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
#!@BASH_PATH@
#
# Copyright 2012-2023 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
# List regular expressions (not globs) that match all of a library's public API
# headers. Any files ending in "internal.h" will be excluded from matches.
declare -A HEADERS
HEADERS[cib]="include/crm/cib.h include/crm/cib/.*.h"
HEADERS[crmcommon]="include/crm/crm.h
include/crm/msg_xml.h
include/crm/common/.*.h"
HEADERS[crmcluster]="include/crm/cluster.h include/crm/cluster/.*.h"
HEADERS[crmservice]="include/crm/services.*.h"
HEADERS[lrmd]="include/crm/lrmd.*.h"
HEADERS[pacemaker]="include/pacemaker.*.h"
HEADERS[pe_rules]="include/crm/pengine/ru.*.h"
HEADERS[pe_status]="include/crm/pengine/[^r].*.h include/crm/pengine/r[^u].*.h"
HEADERS[stonithd]="include/crm/stonith-ng.h include/crm/fencing/.*.h"
yesno() {
local RESPONSE
read -p "$1 " RESPONSE
case $(echo "$RESPONSE" | tr '[:upper:]' '[:lower:]') in
y|yes|ano|ja|si|oui) return 0 ;;
*) return 1 ;;
esac
}
prompt_to_continue() {
yesno "Continue?" || exit 0
}
sed_in_place() {
cp -p "$1" "$1.$$"
sed -e "$2" "$1" > "$1.$$"
mv "$1.$$" "$1"
}
find_last_release() {
if [ -n "$1" ]; then
echo "$1"
else
git tag -l | grep Pacemaker | grep -v rc | sort -Vr | head -n 1
fi
}
find_libs() {
find lib -name "*.am" -exec grep "lib.*_la_LDFLAGS.*version-info" \{\} \; \
| sed -e 's/lib\(.*\)_la_LDFLAGS.*/\1/'
}
find_makefile() {
find lib -name Makefile.am -exec grep -l "lib${1}_la.*version-info" \{\} \;
}
find_sources() {
local LIB="$1"
local AMFILE="$2"
local SOURCES
# Library makefiles should use "+=" to break up long sources lines rather
# than backslashed continuation lines, to allow this script to detect
# source files correctly. Warn if that's not the case.
if
grep "lib${LIB}_la_SOURCES.*\\\\" "$AMFILE"
then
echo -e "\033[1;35m -- Sources list for lib$LIB is probably truncated! --\033[0m"
echo "Edit to use '+=' rather than backslashed continuation lines"
prompt_to_continue
fi
SOURCES=$(grep "^lib${LIB}_la_SOURCES" "$AMFILE" \
| sed -e 's/.*=//' -e 's/\\//' -e 's:\.\./gnu/:lib/gnu/:')
for SOURCE in $SOURCES; do
if
echo "$SOURCE" | grep -q "/"
then
echo "$SOURCE"
else
echo "$(dirname "$AMFILE")/$SOURCE"
fi
done
}
find_headers_as_of() {
local TAG
local LIB
local FILE
local PATTERN
TAG="$1"
LIB="$2"
for FILE in $(git ls-tree -r --name-only "$TAG"); do
for PATTERN in ${HEADERS[$LIB]}; do
if [[ $FILE =~ $PATTERN ]] && [[ ! $FILE =~ internal.h$ ]]; then
echo "$FILE"
break
fi
done
done
}
extract_version() {
grep "lib${1}_la.*version-info" | sed -e 's/.*version-info\s*\(\S*\)/\1/'
}
shared_lib_name() {
local LIB="$1"
local VERSION="$2"
echo "lib${LIB}.so.$(echo "$VERSION" | cut -d: -f 1)"
}
process_lib() {
local LIB="$1"
local LAST_RELEASE="$2"
local AMFILE
local SOURCES
local HEADERS_LAST
local HEADERS_HEAD
local HEADERS_DIFF
local HEADERS_GONE
local HEADERS_ADDED
local CHANGE
local DEFAULT_CHANGE
if [ -z "${HEADERS[$LIB]}" ]; then
echo "Can't check lib$LIB until this script is updated with its headers"
prompt_to_continue
fi
AMFILE="$(find_makefile "$LIB")"
# Get current shared library version
VER_NOW=$(extract_version "$LIB" < "$AMFILE")
# Check whether library existed at last release
if ! git cat-file -e "$LAST_RELEASE:$AMFILE" 2>/dev/null; then
echo "lib$LIB is new, not changing version ($VER_NOW)"
prompt_to_continue
echo ""
return
fi
HEADERS_LAST="$(find_headers_as_of "$LAST_RELEASE" "$LIB")"
HEADERS_HEAD="$(find_headers_as_of "HEAD" "$LIB")"
HEADERS_DIFF="$(diff <(echo "$HEADERS_LAST") <(echo "$HEADERS_HEAD"))"
HEADERS_GONE="$(echo "$HEADERS_DIFF" | sed -n -e 's/^< //p')"
HEADERS_ADDED="$(echo "$HEADERS_DIFF" | sed -n -e 's/^> //p')"
# Check whether there were any changes to headers or sources
SOURCES="$(find_sources "$LIB" "$AMFILE")"
if [ -n "$HEADERS_GONE" ]; then
DEFAULT_CHANGE="i" # Removed public header is incompatible change
elif [ -n "$HEADERS_ADDED" ]; then
DEFAULT_CHANGE="c" # Additions are likely compatible
elif git diff --quiet -w "$LAST_RELEASE..HEAD" $HEADERS_HEAD $SOURCES ; then
echo "No changes to $LIB interface"
prompt_to_continue
echo ""
return
else
DEFAULT_CHANGE="f" # Sources changed, so it's at least a fix
fi
# Show all header changes since last release
echo "- Changes in lib$LIB public headers since $LAST_RELEASE:"
if [ -n "$HEADERS_GONE" ]; then
for HEADER in $HEADERS_GONE; do
echo "-- $HEADER was removed"
done
fi
if [ -n "$HEADERS_ADDED" ]; then
for HEADER in $HEADERS_ADDED; do
echo "++ $HEADER is new"
done
fi
git --no-pager diff --color -w "$LAST_RELEASE..HEAD" $HEADERS_HEAD
echo ""
if yesno "Show commits (minus refactor/build/merge) touching lib$LIB since $LAST_RELEASE [y/N]?"
then
git log --color "$LAST_RELEASE..HEAD" -z $HEADERS_HEAD $SOURCES "$AMFILE" \
| grep -vzE "Refactor:|Build:|Merge pull request"
echo
prompt_to_continue
fi
# @TODO this seems broken ...
#echo ""
#if yesno "Show merged PRs touching lib$LIB since $LAST_RELEASE [y/N]?"
#then
# git log --merges $LAST_RELEASE..HEAD $HEADERS_HEAD $SOURCES $AMFILE
# echo
# prompt_to_continue
#fi
# Show summary of source changes since last release
echo ""
echo "- Headers: $HEADERS_HEAD"
echo "- Changed sources since $LAST_RELEASE:"
git --no-pager diff --color -w "$LAST_RELEASE..HEAD" --stat $SOURCES
echo ""
# Ask for human guidance
echo "Are the changes to lib$LIB:"
read -p "[c]ompatible additions, [i]ncompatible additions/removals or [f]ixes? [$DEFAULT_CHANGE]: " CHANGE
[ -z "$CHANGE" ] && CHANGE="$DEFAULT_CHANGE"
# Get (and show) shared library version at last release
VER=$(git show "$LAST_RELEASE:$AMFILE" | extract_version "$LIB")
VER_1=$(echo "$VER" | awk -F: '{print $1}')
VER_2=$(echo "$VER" | awk -F: '{print $2}')
VER_3=$(echo "$VER" | awk -F: '{print $3}')
echo "lib$LIB version at $LAST_RELEASE: $VER"
# Show current shared library version if changed
if [ "$VER_NOW" != "$VER" ]; then
echo "lib$LIB version currently: $VER_NOW"
fi
# Calculate new library version
case $CHANGE in
i|I)
echo "New backwards-incompatible version: x+1:0:0"
(( VER_1++ ))
VER_2=0
VER_3=0
# Some headers define constants for shared library names,
# update them if the name changed
for H in $HEADERS_HEAD; do
sed_in_place "$H" "s/$(shared_lib_name "$LIB" "$VER_NOW")/$(shared_lib_name "$LIB" "$VER_1:0:0")/"
done
;;
c|C)
echo "New version with backwards-compatible extensions: x+1:0:z+1"
(( VER_1++ ))
VER_2=0
(( VER_3++ ))
;;
F|f)
echo "Code changed though interfaces didn't: x:y+1:z"
(( VER_2++ ))
;;
*)
echo "Not updating lib$LIB version"
prompt_to_continue
CHANGE=""
;;
esac
VER_NEW=$VER_1:$VER_2:$VER_3
if [ -n "$CHANGE" ]; then
if [ "$VER_NEW" != "$VER_NOW" ]; then
echo "Updating lib$LIB version from $VER_NOW to $VER_NEW"
prompt_to_continue
sed_in_place "$AMFILE" "s/version-info\s*$VER_NOW/version-info $VER_NEW/"
else
echo "No version change needed for lib$LIB"
prompt_to_continue
fi
fi
echo ""
}
echo "Definitions:"
echo "- Compatible additions: new public API functions, structs, etc."
echo "- Incompatible additions/removals: new arguments to public API functions,"
echo " new members added to the middle of public API structs,"
echo " removal of any public API, etc."
echo "- Fixes: any other code changes at all"
echo ""
echo "When possible, improve backward compatibility first:"
echo "- move new members to the end of structs"
echo "- use bitfields instead of booleans"
echo "- when adding arguments, create a new function that the old one can wrap"
echo ""
prompt_to_continue
LAST_RELEASE=$(find_last_release "$1")
for LIB in $(find_libs); do
process_lib "$LIB" "$LAST_RELEASE"
done
# Show all proposed changes
git --no-pager diff --color -w
|