mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-17 20:31:47 +02:00
* 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.
291 lines
8.6 KiB
Bash
Executable File
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
|