diff --git a/.github/abicheck.yml b/.github/abicheck.yml
new file mode 100644
index 00000000..1ea4f690
--- /dev/null
+++ b/.github/abicheck.yml
@@ -0,0 +1,5 @@
+# Trusted abicheck config, passed explicitly (build-config) by the abicheck
+# jobs. The stored release baseline (v1.34.0: 133 MB of JSON, about 5M nodes
+# counting keys and scalars) is above abicheck's default decode ceiling of 1M.
+resource_limits:
+ max_bundle_facts_decode_nodes: 12000000
diff --git a/.github/workflows/abicheck-baseline.yml b/.github/workflows/abicheck-baseline.yml
new file mode 100644
index 00000000..243b4d6d
--- /dev/null
+++ b/.github/workflows/abicheck-baseline.yml
@@ -0,0 +1,71 @@
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: Existing release tag to attach the abicheck baseline to
+ required: true
+
+permissions: read-all
+
+defaults:
+ run:
+ shell: bash
+
+# Stores the ABI facts of a release (loader + both layers, one zstd JSON
+# asset, about 1.4 MB) so build-quick.yml compares PRs against the shipped
+# ABI without rebuilding the tag. Built with the same Release flags and
+# Ubuntu 24.04 toolchain as build-quick.yml's ubuntu:24.04 leg. Write-once: a re-run never replaces the asset; to
+# re-baseline, delete it deliberately first.
+
+jobs:
+ baseline:
+ if: github.repository_owner == 'oneapi-src'
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: write
+ env:
+ TAG: ${{ github.event.release.tag_name || inputs.tag }}
+ ASSET: level-zero.abicheck-bundle.json.zst
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ ref: ${{ env.TAG }}
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ path: cfg
+ sparse-checkout: .github
+ - name: Build and stage ${{ env.TAG }}
+ run: |
+ cmake -B build -D CMAKE_BUILD_TYPE=Release
+ cmake --build build -j$(nproc)
+ cmake --install build --prefix "$PWD/abi-inst"
+ mkdir -p abi/lib
+ cp -a abi-inst/include abi/
+ cp -a abi-inst/lib/libze_loader.so* abi-inst/lib/libze_validation_layer.so* abi-inst/lib/libze_tracing_layer.so* abi/lib/
+ - name: Record facts
+ uses: abicheck/abicheck@9ce5f169d5c5164be9812d61337ba4e712e3ab69 # main 2026-10-21; keep in sync with build-quick.yml
+ with:
+ old-library: abi
+ new-library: abi
+ old-header: abi/include/level_zero
+ new-header: abi/include/level_zero
+ old-version: ${{ env.TAG }}
+ new-version: ${{ env.TAG }}
+ build-config: cfg/.github/abicheck.yml
+ python-version: '3.15t'
+ extra-args: --bundle-facts-out level-zero.abicheck-bundle.json
+ pr-comment: 'false'
+ fail-on-breaking: 'false'
+ fail-on-api-break: 'false'
+ - name: Attach to release (write-once)
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ zstd -q -19 -T0 level-zero.abicheck-bundle.json -o "$ASSET"
+ if gh release view "$TAG" --json assets -q '.assets[].name' | grep -Fxq "$ASSET"; then
+ echo "::error::$ASSET already exists on $TAG. The baseline is write-once; delete it deliberately (gh release delete-asset $TAG $ASSET) to re-baseline." >&2
+ exit 1
+ fi
+ gh release upload "$TAG" "$ASSET"
diff --git a/.github/workflows/build-quick.yml b/.github/workflows/build-quick.yml
index a7234323..951c850d 100644
--- a/.github/workflows/build-quick.yml
+++ b/.github/workflows/build-quick.yml
@@ -46,6 +46,20 @@ jobs:
ZEL_LIBRARY_PATH: '${{ github.workspace }}/build/lib'
working-directory: build
run: ctest -V
+ - name: Stage libraries and headers for abicheck
+ if: matrix.container == 'ubuntu:24.04'
+ run: |
+ cmake --install build --prefix "$PWD/abi-inst"
+ # Only the shipped loader and layers; not the NULL test driver.
+ mkdir -p abi/lib
+ cp -a abi-inst/include abi/
+ cp -a abi-inst/lib/libze_loader.so* abi-inst/lib/libze_validation_layer.so* abi-inst/lib/libze_tracing_layer.so* abi/lib/
+ - if: matrix.container == 'ubuntu:24.04'
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ with:
+ name: abicheck-inst
+ path: abi
+ retention-days: 3
- name: Install loader and NULL driver
working-directory: build
run: make install
@@ -67,6 +81,82 @@ jobs:
working-directory: build
run: make uninstall || xargs rm -v < install_manifest.txt
+ # Advisory ABI scan with https://github.com/abicheck/abicheck, next to
+ # check-abi.yml. Compares the ubuntu:24.04 build above, as one directory
+ # (loader + both layers), against the facts that abicheck-baseline.yml
+ # stored on the latest release. Reports breaks, additions and removals;
+ # never fails the PR.
+ abicheck:
+ needs: build-linux
+ if: github.repository_owner == 'oneapi-src' && github.event_name == 'pull_request'
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ fetch-depth: 0
+ sparse-checkout: .github
+ - uses: actions-ecosystem/action-get-latest-tag@b7c32daec3395a9616f88548363a42652b22d435 # v1.6.0
+ id: tag
+ with:
+ semver_only: true
+ - name: Fetch stored baseline of ${{ steps.tag.outputs.tag }}
+ id: baseline
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ steps.tag.outputs.tag }}
+ run: |
+ if gh release download "$TAG" -R "$GITHUB_REPOSITORY" -p level-zero.abicheck-bundle.json.zst; then
+ echo "found=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "Release $TAG has no level-zero.abicheck-bundle.json.zst; run abicheck-baseline.yml with tag=$TAG to attach it." >> "$GITHUB_STEP_SUMMARY"
+ fi
+ - if: steps.baseline.outputs.found == 'true'
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
+ with:
+ name: abicheck-inst
+ path: new
+ - name: abicheck vs ${{ steps.tag.outputs.tag }}
+ if: steps.baseline.outputs.found == 'true'
+ id: abicheck
+ uses: abicheck/abicheck@9ce5f169d5c5164be9812d61337ba4e712e3ab69 # main 2026-10-21; keep in sync with abicheck-baseline.yml
+ with:
+ old-library: level-zero.abicheck-bundle.json.zst
+ new-library: new
+ new-header: new/include/level_zero
+ new-version: ${{ github.event.pull_request.head.sha }}
+ build-config: .github/abicheck.yml
+ python-version: '3.15t'
+ extra-args: -o markdown=abicheck.md -o json=abicheck.json
+ annotate: 'true'
+ annotate-additions: 'true'
+ pr-comment: 'false'
+ fail-on-breaking: 'false'
+ fail-on-api-break: 'false'
+ - name: Job summary
+ if: always() && steps.baseline.outputs.found == 'true'
+ # Counts and symbol lists come from the JSON report: the directory
+ # markdown has no added/removed columns yet (abicheck/abicheck#1706).
+ run: |
+ [ -f abicheck.json ] || exit 0
+ jq -r '
+ "## abicheck: additions, removals and breaks per library", "",
+ "| Library | Verdict | Breaking | Added | Removed | Changed |", "|---|---|---|---|---|---|",
+ (.libraries | to_entries[] | .value.result_counts as $c
+ | "| \(.key) | `\(.value.verdict)` | \($c.gating) | \($c.public_additions) | \($c.public_removals) | \($c.public_modifications) |"),
+ "",
+ (.libraries | to_entries[] | .key as $lib | .value.changes as $ch
+ | ("removed", "added") as $k
+ | [$ch[] | select(.kind | endswith("_" + $k)) | .symbol | select(startswith("__abicheck_") | not)] | unique
+ | select(length > 0)
+ | "\($lib): \($k) (\(length))
\n\n" + (map("- `\(.)`") | join("\n")) + "\n \n")
+ ' abicheck.json >> "$GITHUB_STEP_SUMMARY"
+ { echo "Full abicheck report
"; echo; cat abicheck.md; echo " "; } >> "$GITHUB_STEP_SUMMARY"
+ - if: always() && steps.baseline.outputs.found == 'true'
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ with:
+ name: abicheck-report
+ path: abicheck.*
+
build-windows:
if: github.repository_owner == 'oneapi-src'
runs-on: [windows-latest]