summaryrefslogtreecommitdiffstats
path: root/maint/bumplibs.in
blob: a1426600d9bd6e758b6073a371bf53d8eec3e18b (plain)
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
#!@BASH_PATH@
#
# Copyright 2012-2021 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 A-Z a-z) in
        y|yes|ano|ja|si|oui) return 0 ;;
        *) return 1 ;;
    esac
}

prompt_to_continue() {
    yesno "Continue?" || exit 0
}

find_last_release() {
    if [ ! -z "$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=$(cat $AMFILE | extract_version $LIB)

    # Check whether library existed at last release
    git cat-file -e $LAST_RELEASE:$AMFILE 2>/dev/null
    if [ $? -ne 0 ]; 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=$(expr $VER_1 + 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 -i -e "s/$(shared_lib_name "$LIB" "$VER_NOW")/$(shared_lib_name "$LIB" "$VER_1:0:0")/" $H
            done
            ;;
        c|C)
            echo "New version with backwards-compatible extensions: x+1:0:z+1"
            VER_1=$(expr $VER_1 + 1)
            VER_2=0
            VER_3=$(expr $VER_3 + 1)
            ;;
        F|f)
            echo "Code changed though interfaces didn't: x:y+1:z"
            VER_2=$(expr $VER_2 + 1)
            ;;
        *)
            echo "Not updating lib$LIB version"
            prompt_to_continue
            CHANGE=""
            ;;
    esac
    VER_NEW=$VER_1:$VER_2:$VER_3

    if [ ! -z $CHANGE ]; then
        if [ "$VER_NEW" != "$VER_NOW" ]; then
            echo "Updating lib$LIB version from $VER_NOW to $VER_NEW"
            prompt_to_continue
            sed -i "s/version-info\s*$VER_NOW/version-info $VER_NEW/" $AMFILE
        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