Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .abicheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# abicheck configuration, picked up from the repo root by `abicheck compare`.
# Validated against the abicheck 0.5.0 config schema.
#
# Kept identical to .abicheck.yml in the innersource repo: the two repos build
# the same runtime library and must not disagree on how it is parsed.

# castxml's default cc-gnu detection parses SVS's C++20 headers at the wrong
# standard (std::span / constexpr dtor fail). The clang AST frontend handles
# C++20 cleanly; layout-only checks fall back to the .so's DWARF.
compile:
frontend: clang
std: c++20

# Scope the compared surface to what we publish, and collapse the std/gnu_cxx
# template-instantiation noise that leaks past --exclude-libs,ALL.
scope:
public: true
collapse_versioned_symbols: true

suppression:
require_justification: true
27 changes: 27 additions & 0 deletions .github/abi-baseline-tag
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The released tag that runtime-bindings ABI compatibility is measured against.
# Assets are pulled from this repo's GitHub releases at check time.
#
# Bumped deliberately as part of cutting a release, so that resetting the ABI
# baseline is a reviewable act rather than a side effect of tagging. Pinning also
# keeps a new release from silently moving the target under already-open PRs.
#
# `latest` is accepted and resolves dynamically, but is not the default: patch
# tags can land out of order (v0.3.1 and v0.3.2 were both tagged after v0.4.0),
# so "newest tag" is not always "current release line".
#
# Keep in sync with .github/abi-baseline-tag in the innersource repo.
v0.4.0
82 changes: 82 additions & 0 deletions .github/abi-suppressions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ABI check suppression rules for the SVS C++ runtime bindings, used by
# napetrov/abicheck. Owned here rather than in the innersource repo so that both
# repos measure libsvs_runtime.so against the same rules -- a suppression present
# on one side only would make the innersource-vs-public equivalence check report
# a difference that is really just an asymmetric filter.
#
# Scope is the runtime bindings alone. The innersource repo keeps its own file
# for libsvs_shared_library.so, which leaks a far larger third-party surface.
#
# Measured on libsvs_runtime.so v0.4.0: 253 exported symbols, of which 55 are the
# declared svs::runtime::v0 API. Nearly everything suppressed below is a
# libstdc++ template instantiation that reached the export table because the
# bindings are built without -fvisibility=hidden.

version: 1
suppressions:
# --- C++ standard library / runtime (never part of the SVS API) ---
- symbol_pattern: "^_ZNSt.*"
reason: "C++ standard library symbols (leaked template instantiations)"
label: "stdlib"

- symbol_pattern: "^_ZSt.*"
reason: "C++ standard library functions"
label: "stdlib"

- symbol_pattern: "^_ZN9__gnu_cxx.*"
reason: "libstdc++ __gnu_cxx internals (numeric_traits etc.), leaked"
label: "stdlib"

# RTTI/vtable/VTT for std types mangle as _ZTI/_ZTS/_ZTV/_ZTT, not _ZNSt.
- symbol_pattern: "^_ZT[VIST]N?St.*"
reason: "RTTI/vtable/VTT for std:: types (e.g. thread::_State_impl, future)"
label: "stdlib"

- symbol_pattern: "^_ZT[VIST].*__gnu_cxx.*"
reason: "RTTI/vtable for __gnu_cxx types, leaked"
label: "stdlib"

- symbol_pattern: "^__cxa_.*"
reason: "C++ ABI runtime symbols"
label: "cxx-abi"

- symbol_pattern: "^__gxx_.*"
reason: "GCC C++ runtime symbols"
label: "cxx-abi"

# --- Header-only third-party deps that leak via template instantiation ---
- symbol_pattern: "^_ZN[0-9]+toml.*"
reason: "toml++ header-only symbols leaked via instantiation"
label: "third-party"

- symbol_pattern: "^_ZN3fmt.*"
reason: "fmt header-only symbols leaked via instantiation"
label: "third-party"

- symbol_pattern: "^_ZN3tsl.*"
reason: "tsl::robin_map header-only symbols leaked via instantiation"
label: "third-party"

- symbol_pattern: "^_ZN6spdlog.*"
reason: "spdlog header-only symbols leaked via instantiation"
label: "third-party"

# --- svs::runtime::v0 and svs::...detail...: deliberately NOT suppressed ---
# The 55 declared v0 functions are the whole point of the check. `detail`
# symbols are not suppressed either: users reach them transitively through
# template instantiation, so a vtable or layout change there is a real break.
# See the same note in the innersource suppression file.
126 changes: 126 additions & 0 deletions .github/scripts/abi-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/bash
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Compare the ABI of two SVS build tarballs with napetrov/abicheck.
#
# One primitive serves both questions asked of it:
# temporal old = a release tarball, new = this build -- did we break users?
# equivalence old and new are the same source revision built two ways -- did
# the build paths diverge?
# The distinction is in what the caller passes, not in the logic, so this lives
# in one place. It is called from this repo's build-cpp-runtime-bindings.yml and
# from build-share-lib.yml in the innersource repo via the submodule path.
#
# Usage: abi-check.sh <old-label> <old-tarball> <new-label> <new-tarball>
#
# Overridable via environment:
# LIBRARY library basename to compare (default libsvs_runtime.so)
# HEADER_SUBDIR header root inside the tarball (default include/svs/runtime)
# SUPPRESSIONS suppression file (default .github/abi-suppressions.yml)
# POLICY abicheck policy (default strict_abi)
# REPORT markdown report path (default abi-report.md)
# WORKDIR scratch directory (default abi-work)
#
# Exit status is abicheck's: 0 compatible, 2 source-level API break, 4 binary
# ABI break, 64 invalid invocation. 77 is added to mean "an input was missing",
# which is not a finding and must never be reported as one.

set -euo pipefail

OLD_LABEL="${1:?Usage: abi-check.sh <old-label> <old-tarball> <new-label> <new-tarball>}"
OLD_TARBALL="${2:?missing old tarball}"
NEW_LABEL="${3:?missing new label}"
NEW_TARBALL="${4:?missing new tarball}"

LIBRARY="${LIBRARY:-libsvs_runtime.so}"
HEADER_SUBDIR="${HEADER_SUBDIR:-include/svs/runtime}"
SUPPRESSIONS="${SUPPRESSIONS:-.github/abi-suppressions.yml}"
POLICY="${POLICY:-strict_abi}"
REPORT="${REPORT:-abi-report.md}"
WORKDIR="${WORKDIR:-abi-work}"

# Prints the resolved library path on success. Release tarballs and CI artifacts
# have differed on whether the unversioned symlink is included, so accept the
# versioned real file too.
unpack() {
local tarball="$1" dest="$2" label="$3" lib

if [ ! -f "$tarball" ]; then
echo "::warning::$label: tarball not found at $tarball" >&2
return 1
fi

rm -rf "$dest"
mkdir -p "$dest"
tar -xzf "$tarball" -C "$dest"

lib=$(find "$dest" -name "$LIBRARY" -o -name "$LIBRARY.*" | sort | head -1)
if [ -z "$lib" ]; then
echo "::error::$label: $LIBRARY not found in $tarball" >&2
return 1
fi
if [ ! -d "$dest/$HEADER_SUBDIR" ]; then
echo "::error::$label: headers missing from $tarball ($HEADER_SUBDIR)" >&2
return 1
fi
echo "$lib"
}

OLD_DIR="$WORKDIR/old"
NEW_DIR="$WORKDIR/new"

OLD_LIB=$(unpack "$OLD_TARBALL" "$OLD_DIR" "$OLD_LABEL") || exit 77
NEW_LIB=$(unpack "$NEW_TARBALL" "$NEW_DIR" "$NEW_LABEL") || exit 77

echo "Comparing $LIBRARY: [$OLD_LABEL] -> [$NEW_LABEL]"

suppress_args=()
[ -f "$SUPPRESSIONS" ] && suppress_args=(--suppress "$SUPPRESSIONS")

rc=0
# Without pipefail the pipe into tee masks abicheck's exit status.
set -o pipefail
# castxml is not installed on the runners; clang is. `auto` does not fall back to
# clang on its own. If header parsing degrades, abicheck still reports from the
# ELF tier -- which is where vtable-size findings come from anyway.
abicheck compare \
"$OLD_LIB" \
"$NEW_LIB" \
--header old="$OLD_DIR/$HEADER_SUBDIR" \
--header new="$NEW_DIR/$HEADER_SUBDIR" \
--include old="$OLD_DIR/include" \
--include new="$NEW_DIR/include" \
--version old="$OLD_LABEL" \
--version new="$NEW_LABEL" \
--ast-frontend clang \
--gcc-options "-std=c++20 -include cstddef" \
--policy "$POLICY" \
"${suppress_args[@]}" \
--format markdown | tee "$REPORT" || rc=$?

case "$rc" in
0)
echo "ABI compatible: $OLD_LABEL -> $NEW_LABEL"
;;
64)
echo "::error::abicheck rejected the invocation (exit 64) comparing" \
"$OLD_LABEL -> $NEW_LABEL. This is a harness bug, not an ABI finding."
;;
*)
echo "::warning::ABI incompatibility: $NEW_LABEL differs from $OLD_LABEL (rc=$rc)"
;;
esac

exit "$rc"
99 changes: 99 additions & 0 deletions .github/workflows/build-cpp-runtime-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,102 @@ jobs:
-e ENABLE_LVQ_LEANVEC=${{ matrix.enable_lvq_leanvec }} \
svs-manylinux228:latest \
/bin/bash .github/scripts/test-faiss.sh

# Check the built runtime bindings against the last published release.
#
# Only the default variant is checked: releases publish
# svs-cpp-runtime-bindings.tar.gz but no -public-only asset, so that variant has
# nothing to compare against. It is covered indirectly -- both variants build
# from the same headers.
#
# The comparison primitive is .github/scripts/abi-check.sh, which the
# innersource repo also calls through its submodule path so the two repos cannot
# drift on how the same library is measured.
abi-check:
name: ABI check against the last release
needs: build-cpp-runtime-bindings
runs-on: ubuntu-22.04
# An intentionally-breaking PR still gets a full report: the label downgrades
# this job from blocking to informational rather than skipping it, so the
# break is recorded on the PR instead of going unmeasured.
continue-on-error: ${{ contains(toJson(github.event.pull_request.labels.*.name), '"API/ABI breaking change"') }}

steps:
- uses: actions/checkout@v6

# The hosted ubuntu-22.04 image ships only clang-13/14, which degrades
# abicheck's clang AST frontend to ELF-tier and drops constrained-template
# diffs. innersource gets a c++20-capable clang from its self-hosted
# toolchain; this keeps the two repos measuring at the same fidelity.
- name: Install clang for the AST frontend
run: |
wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh
chmod +x /tmp/llvm.sh
sudo /tmp/llvm.sh 18
sudo apt-get install -y --no-install-recommends libclang-18-dev llvm-18-dev
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100

- name: Install abicheck
run: pip install 'abicheck==0.5.*'

- name: Resolve baseline release tag
id: baseline
env:
GH_TOKEN: ${{ github.token }}
run: |
tag=$(grep -v '^#' .github/abi-baseline-tag | tr -d '[:space:]')
if [ "$tag" = 'latest' ]; then
tag=$(gh release view --json tagName --jq '.tagName')
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"

# A missing baseline is a hard failure: a green check that silently skipped
# the comparison is worse than a red one.
- name: Download release baseline
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.baseline.outputs.tag }}
run: |
mkdir -p baseline
if ! gh release download "$TAG" --pattern svs-cpp-runtime-bindings.tar.gz \
--dir baseline; then
echo "::error::Release $TAG has no svs-cpp-runtime-bindings.tar.gz asset." \
"Fix the pin in .github/abi-baseline-tag."
exit 1
fi

- name: Download this build
uses: actions/download-artifact@v8
with:
name: svs-cpp-runtime-bindings
path: current

- name: Compare
env:
LIBRARY: libsvs_runtime.so
HEADER_SUBDIR: include/svs/runtime
SUPPRESSIONS: .github/abi-suppressions.yml
POLICY: strict_abi
run: |
chmod +x .github/scripts/abi-check.sh
label='${{ github.event.pull_request.number && format('PR {0}', github.event.pull_request.number) || github.ref_name }}'
rc=0
.github/scripts/abi-check.sh \
'${{ steps.baseline.outputs.tag }}' baseline/svs-cpp-runtime-bindings.tar.gz \
"$label" current/svs-cpp-runtime-bindings.tar.gz || rc=$?

if [ -s abi-report.md ]; then
{
echo "## ABI vs ${{ steps.baseline.outputs.tag }}"
cat abi-report.md
} >> "$GITHUB_STEP_SUMMARY"
fi

# 77 means an input tarball was missing; that is a harness/plumbing
# problem and must fail loudly rather than pass as "no findings".
if [ "$rc" -eq 77 ] || [ "$rc" -eq 64 ]; then
echo "::error::ABI check could not run (rc=$rc)."
exit 1
fi
exit "$rc"
Loading