#!@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