Commit 87f9c82f2 for llama.cpp

commit 87f9c82f2d19b18ec6d1a08ab0f7727dc4ae4928
Author: Daniel Bevenius <daniel.bevenius@gmail.com>
Date:   Thu Sep 17 09:55:16 2026 +0200

    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.

diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml
index 6644a80cc..d1c6dca5d 100644
--- a/.github/workflows/make-release.yml
+++ b/.github/workflows/make-release.yml
@@ -13,6 +13,16 @@ on:
         required: true
         type: boolean
         default: true
+      skip_apiabi_check:
+        description: 'Skip API/ABI compatibility check'
+        required: false
+        type: boolean
+        default: false
+      apiabi_compare_tag:
+        description: 'Tag to compare against for API/ABI check (default: latest release)'
+        required: false
+        type: string
+        default: ''

 env:
   GH_TOKEN: ${{ github.token }}
@@ -33,12 +43,18 @@ jobs:
           ref: ${{ inputs.commit != '' && inputs.commit || github.ref_name }}
           fetch-depth: 0

+      - name: Install API/ABI check tools
+        if: ${{ github.event.inputs.skip_apiabi_check != 'true' }}
+        run: sudo apt-get install -y abi-compliance-checker abigail-tools
+
       - name: Run release checks
         id: checks
         run: bash scripts/make-release-checks.sh ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}
         env:
           GITHUB_REPOSITORY: ${{ github.repository }}
           RELEASE_BRANCH: ${{ github.ref_name }}
+          SKIP_APIABI_CHECK: ${{ github.event.inputs.skip_apiabi_check }}
+          APIABI_COMPARE_TAG: ${{ github.event.inputs.apiabi_compare_tag }}

       - name: Create release tag
         if: ${{ github.event.inputs.dry_run == 'false' }}
diff --git a/scripts/check-apiabi-compat.sh b/scripts/check-apiabi-compat.sh
index 078abb864..8075aaf9f 100755
--- a/scripts/check-apiabi-compat.sh
+++ b/scripts/check-apiabi-compat.sh
@@ -55,7 +55,9 @@ fi
 # Some generic functions
 usage() {
     echo "Usage: $0 [ --include-path <dir> ] --generate <build-dir> libXXX [ libYYY ... ]" >&2
-    echo "       $0 --check <old-build-dir> <new-build-dir>" >&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() {
@@ -72,6 +74,7 @@ get_cmake_version() {
 # Option parsing and validation
 DO_GEN=0
 DO_CHECK=0
+STRICT=0
 BUILD_DIR=
 BUILD_DIR_NEW=
 INCLUDE_PATHS=
@@ -119,6 +122,10 @@ while [ "$#" -gt 0 ]; do
         INCLUDE_PATHS="$INCLUDE_PATHS $2"
         shift 2
         ;;
+    --strict)
+        STRICT=1
+        shift
+        ;;
     -h | --help)
         usage
         exit 1
@@ -258,13 +265,24 @@ elif [ "$DO_CHECK" -eq 1 ]; then
         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
-        echo "ERROR: API changed with possible backwards-compatibility problems." >&2
+        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
-        echo "ERROR: ABI changed with possible backwards-compatibility problems." >&2
+        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
diff --git a/scripts/check-release-apiabi.sh b/scripts/check-release-apiabi.sh
new file mode 100755
index 000000000..d31d24041
--- /dev/null
+++ b/scripts/check-release-apiabi.sh
@@ -0,0 +1,131 @@
+#!/bin/bash
+# Check API/ABI compatibility between the previous release tag and current HEAD.
+#
+# Finds the most recent vX.Y.Z tag, checks it out in a temporary git worktree,
+# builds both versions with shared libs enabled, and uses check-apiabi-compat.sh
+# to compare the results.
+#
+# Exit codes:
+#   0: compatible, or check was skipped
+#   1: backwards-incompatible changes found, or build failed
+#
+# Options:
+#   --tag <version>: compare against this tag instead of the latest release
+#
+# Environment:
+#   SKIP_APIABI_CHECK:    set to 1 or true to skip
+#   APIABI_COMPARE_TAG:   equivalent to --tag (used by CI)
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+usage() {
+    echo "Usage: $0 [--tag <version>]" >&2
+    echo "  --tag <version>  Compare against this release tag (default: latest)" >&2
+}
+
+COMPARE_TAG="${APIABI_COMPARE_TAG:-}"
+while [[ "$#" -gt 0 ]]; do
+    case "$1" in
+    --tag)
+        if [[ -z "${2:-}" ]]; then usage; exit 1; fi
+        COMPARE_TAG="$2"
+        shift 2
+        ;;
+    --tag=*)
+        COMPARE_TAG="${1#*=}"
+        shift
+        ;;
+    -h | --help)
+        usage; exit 0
+        ;;
+    *)
+        usage; exit 1
+        ;;
+    esac
+done
+
+if [[ "${SKIP_APIABI_CHECK:-}" == "1" || "${SKIP_APIABI_CHECK:-}" == "true" ]]; then
+    echo "SKIP_APIABI_CHECK is set - skipping API/ABI compatibility check"
+    exit 0
+fi
+
+if ! command -v abi-compliance-checker >/dev/null 2>&1 || ! command -v abidw >/dev/null 2>&1; then
+    echo "Warning: abi-compliance-checker or abigail-tools not installed - skipping API/ABI check"
+    exit 0
+fi
+
+discover_libs() {
+    local build_dir="$1"
+    local libs=()
+    for dir in "$build_dir/src" "$build_dir/bin"; do
+        [[ -d "$dir" ]] || continue
+        for f in "$dir"/lib*.so; do
+            [[ -f "$f" ]] && libs+=("$(basename "$f" .so)")
+        done
+    done
+    echo "${libs[@]}"
+}
+
+if [[ -n "${COMPARE_TAG}" ]]; then
+    PREV_TAG="${COMPARE_TAG}"
+    if ! git -C "$REPO_ROOT" rev-parse --verify "${PREV_TAG}^{}" >/dev/null 2>&1; then
+        echo "Error: tag '${PREV_TAG}' not found in repository." >&2
+        exit 1
+    fi
+else
+    PREV_TAG=$(git -C "$REPO_ROOT" tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)
+    if [[ -z "${PREV_TAG}" ]]; then
+        echo "Warning: no previous release tag found - skipping API/ABI check"
+        exit 0
+    fi
+fi
+OLD_VERSION="${PREV_TAG#v}"
+OLD_MAJOR="${OLD_VERSION%%.*}"
+OLD_MINOR="${OLD_VERSION#*.}"; OLD_MINOR="${OLD_MINOR%%.*}"
+
+NEW_MAJOR=$(grep "set(LLAMA_VERSION_MAJOR" "$REPO_ROOT/CMakeLists.txt" | sed 's/.*MAJOR \([0-9]*\).*/\1/')
+NEW_MINOR=$(grep "set(LLAMA_VERSION_MINOR" "$REPO_ROOT/CMakeLists.txt" | sed 's/.*MINOR \([0-9]*\).*/\1/')
+
+if [[ "$NEW_MAJOR" -gt "$OLD_MAJOR" ]]; then
+    echo "Major version increment ($OLD_MAJOR -> $NEW_MAJOR): API/ABI breaking changes are expected, skipping compatibility check."
+    exit 0
+fi
+
+CHECK_FLAGS=()
+if [[ "$NEW_MINOR" -eq "$OLD_MINOR" ]]; then
+    echo "Patch version bump detected: checking for any API/ABI changes (a minor bump is required if any are found)..."
+    CHECK_FLAGS+=(--strict)
+else
+    echo "Minor version bump detected: checking for backwards-incompatible API/ABI changes..."
+fi
+
+echo "Checking API/ABI compatibility against ${PREV_TAG}..."
+
+WORKTREE_DIR=$(mktemp -d)
+BUILD_OLD=$(mktemp -d)
+BUILD_NEW=$(mktemp -d)
+
+cleanup() {
+    git -C "$REPO_ROOT" worktree remove --force "$WORKTREE_DIR" 2>/dev/null || true
+    rm -rf "$WORKTREE_DIR" "$BUILD_OLD" "$BUILD_NEW"
+}
+trap cleanup EXIT
+
+git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" "$PREV_TAG"
+
+cmake -S "$WORKTREE_DIR" -B "$BUILD_OLD" -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
+cmake --build "$BUILD_OLD" --parallel "$(nproc)"
+OLD_LIBS=($(discover_libs "$BUILD_OLD"))
+echo "Libraries found in old build: ${OLD_LIBS[*]}"
+
+cmake -S "$REPO_ROOT"    -B "$BUILD_NEW" -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
+cmake --build "$BUILD_NEW" --parallel "$(nproc)"
+NEW_LIBS=($(discover_libs "$BUILD_NEW"))
+echo "Libraries found in new build: ${NEW_LIBS[*]}"
+
+(cd "$WORKTREE_DIR" && "$SCRIPT_DIR/check-apiabi-compat.sh" --include-path ggml/include --generate "$BUILD_OLD" "${OLD_LIBS[@]}")
+(cd "$REPO_ROOT"    && "$SCRIPT_DIR/check-apiabi-compat.sh" --include-path ggml/include --generate "$BUILD_NEW" "${NEW_LIBS[@]}")
+(cd "$REPO_ROOT"    && "$SCRIPT_DIR/check-apiabi-compat.sh" "${CHECK_FLAGS[@]}" --check "$BUILD_OLD" "$BUILD_NEW")
diff --git a/scripts/make-release-checks.sh b/scripts/make-release-checks.sh
index d78fa1457..2b60e870f 100755
--- a/scripts/make-release-checks.sh
+++ b/scripts/make-release-checks.sh
@@ -165,6 +165,23 @@ else
     fi
 fi

+echo "Checking API/ABI compatibility..."
+set +e
+bash "$SCRIPT_DIR/check-release-apiabi.sh"
+APIABI_RESULT=$?
+set -e
+if [[ $APIABI_RESULT -ne 0 ]]; then
+    if [[ "$DRY_RUN" == "true" ]]; then
+        echo "Warning: API/ABI check found backwards-incompatible changes (dry run, continuing)."
+        CHECKS_PASSED=false
+    else
+        echo "Error: API/ABI check found backwards-incompatible changes."
+        exit 1
+    fi
+else
+    echo "API/ABI compatibility check passed - OK"
+fi
+
 if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
     echo "checks_passed=${CHECKS_PASSED}" >> "$GITHUB_OUTPUT"
 fi