Files
llama.cpp/scripts/check-apiabi-compat.sh
Daniel Bevenius 87f9c82f2d ci : add API/ABI check to make-release workflow [no ci] (#28947)
* ci : add API/ABI check to make-release workflow [no ci]

This commit adds an API/ABI compatibility check to the make-release
workflow.

The motivation for this to allow us to detect any potential breaking
changes in API/ABI compatibility between releases and fail the the
release if there are any.

The workflow can be triggered manually as before and this check can be
skipped if needed as it does take some time which might be useful when
doing a dry-run and not specifically interested in the API/ABI check.

By default this will check the current release against the latest
release, but this can also be configured in the workflow, or in the
script run on the command line, to check a different tag.

* add check for minor version bumps [no ci]

This commit also changes the build type to be RelWithDebInfo so that the
reported information is more useful.
2026-09-17 09:55:16 +02:00

291 lines
8.6 KiB
Bash
Executable File

#!/bin/sh
# Check for backwards-incompatible API and ABI changes between two builds
#
# Backwards-incompatible API changes, such as removing a value from an enum,
# are checked by abi-compliance-checker. Such changes can break compilation of
# existing programs.
#
# Backwards-incompatible ABI changes, such as the removal of a public function,
# are checked by libigail-tools. Such changes could break run-time dynamic
# linking of existing binaries. (We don't use a-c-c for ABI checks because it
# needs a debug build, whereas abigail does not.)
#
# Commands:
# --generate <build-dir>: Creates API/ABI dumps in <build-dir>
# <build-dir> is expected to be a CMake build result
# --check <dir1> <dir2>: Compares dumps in <dir1> and <dir2>
# Comparison exit codes
# 0: all good
# 1: backwards-incompatible changes found
#
# Options:
# --include-path <dir>: a-c-c calls gcc on headers; use this option to add
# directories to gcc's search path
#
#
# This script would typically be used before cutting a release:
#
# 1. Generate API/ABI dump for the old version
#
# $ check-apiabi-compat.sh --generate <build-dir-old> libfoo [ libbar ...]
#
# 2. <update source>
#
# 3. Generate API/ABI dump for the new version
#
# $ check-apiabi-compat.sh --generate <build-dir-new> libfoo [ libbar ...]
#
# 4. Compare the two dumps
#
# $ check-apiabi-compat.sh --check <old-build-dir> <new-build-dir>
#
# If the check exits 0, all is fine. Otherwise, backwards-incompatible
# changes were found, and the librar(ies) need a SOVER bump.
set -eu
# Preconditions
if ! command -v abi-compliance-checker >/dev/null 2>&1; then
echo "abi-compliance-checker is not installed." >&2
exit 1
elif ! command -v abidw >/dev/null 2>&1; then
echo "abigail-tools are not installed." >&2
exit 1
fi
# Some generic functions
usage() {
echo "Usage: $0 [ --include-path <dir> ] --generate <build-dir> libXXX [ libYYY ... ]" >&2
echo " $0 [ --strict ] --check <old-build-dir> <new-build-dir>" >&2
echo "" >&2
echo " --strict: fail on any API/ABI change, including backwards-compatible additions" >&2
}
get_cmake_project_name() {
sed -nr 's/^project\("(.*)".*$/\1/p' CMakeLists.txt
}
get_cmake_version() {
major="$(sed -nr 's/^set\([A-Z]+_VERSION_MAJOR ([0-9]+)\)$/\1/p' CMakeLists.txt)"
minor="$(sed -nr 's/^set\([A-Z]+_VERSION_MINOR ([0-9]+)\)$/\1/p' CMakeLists.txt)"
patch="$(sed -nr 's/^set\([A-Z]+_VERSION_PATCH ([0-9]+)\)$/\1/p' CMakeLists.txt)"
echo "$major.$minor.$patch"
}
# Option parsing and validation
DO_GEN=0
DO_CHECK=0
STRICT=0
BUILD_DIR=
BUILD_DIR_NEW=
INCLUDE_PATHS=
while [ "$#" -gt 0 ]; do
case "$1" in
--generate=*)
DO_GEN=1
BUILD_DIR="${1#*=}"
shift
;;
--generate)
DO_GEN=1
if [ -z "${2:-}" ]; then
usage
exit 1
fi
BUILD_DIR="$2"
shift 2
;;
--check)
DO_CHECK=1
if [ -z "${2:-}" ] || [ -z "${3:-}" ]; then
usage
exit 1
elif ! [ -d "$2" ]; then
echo "$2 is not a directory." >&2
exit 1
elif ! [ -d "$3" ]; then
echo "$3 is not a directory." >&2
exit 1
fi
BUILD_DIR="$2"
BUILD_DIR_NEW="$3"
shift 3
;;
--include-path=*)
INCLUDE_PATHS="$INCLUDE_PATHS ${1#*=}"
shift
;;
--include-path)
if [ -z "${2:-}" ]; then
usage
exit 1
fi
INCLUDE_PATHS="$INCLUDE_PATHS $2"
shift 2
;;
--strict)
STRICT=1
shift
;;
-h | --help)
usage
exit 1
;;
-?*)
usage
exit 1
;;
*)
break
;;
esac
done
if [ $((DO_GEN + DO_CHECK)) -gt 1 ]; then
echo "Can only use one --command." >&2
exit
fi
PROJECT_NAME="$(get_cmake_project_name)"
PROJECT_VERSION="$(get_cmake_version)"
LIB_NAMES=""
while [ "$#" -gt 0 ]; do
if [ "${1#lib}" = "$1" ]; then
echo "Library to check must start with libXXX." >&2
exit 1
fi
LIB_NAMES="$LIB_NAMES $1"
shift
done
dump_current_api() {
echo "Dumping API..."
DESCRIPTOR="$BUILD_DIR/apiabi/acc-descriptor.xml"
mkdir -p "$BUILD_DIR/apiabi"
cat >"$DESCRIPTOR" <<EOF
<version>$PROJECT_VERSION</version>
<headers>include</headers>
<add_include_paths>$INCLUDE_PATHS</add_include_paths>
EOF
# This addresses a bug between a-c-c and universal-ctags, manifested when
# a name is use both for a tag and a function name
mkdir -p "$BUILD_DIR/apiabi/.ctags.d"
echo "--fields=-t" >"$BUILD_DIR/apiabi/.ctags.d/acc.ctags"
# Change HOME so that .ctags.d gets picked up by universal-ctags, if used
HOME="$BUILD_DIR/apiabi" abi-compliance-checker \
-headers-only \
-lib "$PROJECT_NAME" \
-dump "$DESCRIPTOR" \
-log-path "$BUILD_DIR/apiabi/acc.log" \
-dump-path "$BUILD_DIR/apiabi/api.dump"
# acc generates this file with an ancient timestamp, which confuses gzip
touch "$BUILD_DIR/apiabi/api.dump"
}
dump_current_abi() {
echo "Dumping ABIs ..."
mkdir -p "$BUILD_DIR/apiabi"
# The suppressions are needed to avoid including all the internal C++
# symbols, and system types
cat >"$BUILD_DIR/apiabi/abidw.suppress" <<EOF
[suppress_function]
label = suppress internal C++ mangled functions
symbol_name_regexp = ^_Z
drop = yes
[suppress_variable]
label = suppress internal C++ mangled variables
symbol_name_regexp = ^_Z
drop = yes
[suppress_type]
label = Suppress types outside of our own source
source_location_not_regexp = ^include/
drop = yes
EOF
# In abidw 2.5, handling of undefined stuff was changed a bit
abidw_version="$(abidw --version | sed -r 's/^abidw: ([0-9]+\.[0-9]+).*$/\1/')"
abidw_major="${abidw_version%.*}"
abidw_minor="${abidw_version#*.}"
if [ "$abidw_major" -gt 2 ] || [ "$abidw_minor" -gt 4 ]; then
abidw_undefined_syms_options="--no-load-undefined-interfaces"
else
abidw_undefined_syms_options="--drop-undefined-syms"
fi
for lib_name in $LIB_NAMES; do
# Depending on where add_library resides, the libraries can end up in
# build/src or build/bin
lib_path="$BUILD_DIR/src/$lib_name.so"
if ! [ -f "$lib_path" ]; then
lib_path="$BUILD_DIR/bin/$lib_name.so"
if ! [ -f "$lib_path" ]; then
echo "Cannot find library $lib_name.so" >&2
exit 1
fi
fi
abidw \
--headers-dir include \
"$abidw_undefined_syms_options" \
--suppressions "$BUILD_DIR/apiabi/abidw.suppress" \
--out-file "${BUILD_DIR}/apiabi/$lib_name.abi.xml" \
"$lib_path"
done
}
# Run the actual commands
if [ "$DO_GEN" -eq 1 ]; then
dump_current_api
dump_current_abi
exit 0
elif [ "$DO_CHECK" -eq 1 ]; then
# From here on, we don't want to exit on first error
set +e
abi-compliance-checker \
-strict \
-source \
-library "$PROJECT_NAME" \
-old "$BUILD_DIR/apiabi/api.dump" \
-new "$BUILD_DIR_NEW/apiabi/api.dump" \
-src-report-path "$BUILD_DIR_NEW/apiabi/api_compat_report.html"
API_RESULT=$?
ABI_RESULT=0
for xml_file in "$BUILD_DIR/apiabi/"lib*.abi.xml; do
xml_file_new="$BUILD_DIR_NEW/apiabi/$(basename "$xml_file")"
if ! [ -f "$xml_file_new" ]; then
echo "Cannot compare, missing file: $xml_file_new" >&2
exit 1
fi
abidiff "$xml_file" "$xml_file_new"
res=$?
[ "$((res & 8))" -ne 0 ] && ABI_RESULT=1
# check bit 2 for compatible ABI changes (like new symbols) and if
# STRICT is set then handle this as an error.
[ "$STRICT" -eq 1 ] && [ "$((res & 4))" -ne 0 ] && ABI_RESULT=1
done
if [ "$API_RESULT" -gt 0 ]; then
if [ "$STRICT" -eq 1 ]; then
echo "ERROR: API changed — a minor version bump is required." >&2
else
echo "ERROR: API changed with backwards-incompatible problems — a major version bump is required." >&2
fi
fi
if [ "$ABI_RESULT" -gt 0 ]; then
if [ "$STRICT" -eq 1 ]; then
echo "ERROR: ABI changed — a minor version bump is required." >&2
else
echo "ERROR: ABI changed with backwards-incompatible problems — a major version bump is required." >&2
fi
fi
if [ "$((API_RESULT + ABI_RESULT))" -gt 0 ]; then
exit 1
fi
fi