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
231 changes: 231 additions & 0 deletions .github/workflows/check-c-abi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
name: C ABI Compatibility Check

on:
workflow_dispatch: # Allow manual trigger for bootstrap
workflow_call:
pull_request:
paths:
- 'c/include/**'
- 'ci/check_c_abi.py'
- '.github/workflows/check-c-abi.yaml'
push:
branches:
- main
paths:
- 'c/include/**'
- 'ci/check_c_abi.py'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# Extract and store baseline ABI from main branch
update-baseline:
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
Comment on lines +24 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job and check-pr are different enough, and have different enough conditions, that I think they should be in separate files.

runs-on: ubuntu-latest
concurrency:
group: abi-baseline-update
cancel-in-progress: false # Queue updates, don't skip
steps:
- name: Checkout main branch
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libclang-dev \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is libclang-dev needed for the Python API? When you pip install libclang below, does it build against these headers?

clang \
cmake \
ninja-build

- name: Install Python dependencies
run: |
pip install --upgrade pip
pip install msgspec libclang termcolor

- name: Build C++ project to get dependencies (dlpack)
run: |
mkdir -p cpp/build
cd cpp/build
cmake .. \
-GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF
echo "Build directory created and dependencies fetched"

- name: Extract ABI from main branch
run: |
mkdir -p baseline
python ci/check_c_abi.py extract \
--header-path c/include \
--include-file cuvs/core/all.h \
--output-file baseline/c_abi.json.gz
echo "ABI extracted from main branch (commit: ${{ github.sha }})"

- name: Store commit-specific baseline
uses: actions/upload-artifact@v4
with:
name: c-abi-baseline-${{ github.sha }}
path: baseline/c_abi.json.gz
retention-days: 90 # Keep for 3 months

- name: Store main baseline (latest, never expires)
uses: actions/upload-artifact@v4
with:
name: c-abi-baseline-main
path: baseline/c_abi.json.gz
retention-days: 0 # Never expire

# Check PRs for breaking ABI changes
check-pr:
if: github.event_name == 'pull_request' || github.event_name == 'workflow_call'
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libclang-dev \
clang \
cmake \
ninja-build

- name: Install Python dependencies
run: |
pip install --upgrade pip
pip install msgspec libclang termcolor

- name: Build C++ project to get dependencies (dlpack)
run: |
mkdir -p cpp/build
cd cpp/build
cmake .. \
-GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF
echo "Build directory created and dependencies fetched"

- name: Find merge base commit
id: merge-base
run: |
git fetch origin main
MERGE_BASE=$(git merge-base HEAD origin/main)
echo "merge_base_sha=${MERGE_BASE}" >> $GITHUB_OUTPUT
echo "Merge base commit: ${MERGE_BASE}"

- name: Try to download baseline for merge-base commit (most accurate)
id: download-merge-base
continue-on-error: true
uses: dawidd6/action-download-artifact@v3
with:
name: c-abi-baseline-${{ steps.merge-base.outputs.merge_base_sha }}
workflow: check-c-abi.yaml
commit: ${{ steps.merge-base.outputs.merge_base_sha }}
path: baseline/

- name: Try to download latest main baseline (fallback 1)
id: download-main
if: steps.download-merge-base.outcome == 'failure'
continue-on-error: true
uses: dawidd6/action-download-artifact@v3
with:
name: c-abi-baseline-main
workflow: check-c-abi.yaml
branch: main
path: baseline/

- name: Extract baseline ABI from main branch (fallback 2)
if: steps.download-merge-base.outcome == 'failure' && steps.download-main.outcome == 'failure'
run: |
echo "⚠️ No baseline artifacts found, extracting from main branch..."
echo "This is slower but ensures we always have a baseline for comparison."
git worktree add ../cuvs-main main
mkdir -p ../cuvs-main/cpp/build
cd ../cuvs-main/cpp/build
cmake .. \
-GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF
cd ../../..
mkdir -p baseline
python ci/check_c_abi.py extract \
--header-path ../cuvs-main/c/include \
--include-file cuvs/core/all.h \
--output-file baseline/c_abi.json.gz
echo "✓ Baseline ABI extracted from main branch"

- name: Report baseline source
run: |
if [ "${{ steps.download-merge-base.outcome }}" == "success" ]; then
echo "✓ Using baseline from merge-base commit: ${{ steps.merge-base.outputs.merge_base_sha }}"
elif [ "${{ steps.download-main.outcome }}" == "success" ]; then
echo "✓ Using latest main baseline (merge-base baseline not yet available)"
else
echo "✓ Using freshly extracted baseline from main branch"
fi

- name: Analyze current branch for ABI breaking changes
run: |
python ci/check_c_abi.py analyze \
--abi-file baseline/c_abi.json.gz \
--header-path c/include \
--include-file cuvs/core/all.h

- name: Comment on PR with results
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## ⚠️ C ABI Breaking Changes Detected

This PR introduces breaking changes to the C ABI. Please review the changes carefully.

Breaking ABI changes are only allowed in major releases. If this is intentional for a major release,
the baseline ABI will need to be updated after merge.

See the job logs for details on what specific changes were detected.

### What are breaking ABI changes?

Breaking ABI changes include:
- Removing functions from the public API
- Changing function signatures (parameters or return types)
- Removing struct members or changing their types
- Removing or changing enum values

### Next steps

1. Review the changes flagged in the CI logs
2. If these changes are unintentional, update your PR to maintain ABI compatibility
3. If these changes are required, ensure:
- This is part of a major version release
- The changes are documented in the changelog
- Migration guide is provided for users

For more information, see the [C ABI documentation](../docs/source/c_developer_guide.md).
`
});
4 changes: 4 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
- check-nightly-ci
- changed-files
- checks
- check-c-abi
- conda-cpp-build
- conda-cpp-tests
- conda-cpp-checks
Expand Down Expand Up @@ -302,6 +303,9 @@ jobs:
with:
enable_check_generated_files: false
ignored_pr_jobs: "telemetry-summarize"
check-c-abi:
needs: telemetry-setup
uses: ./.github/workflows/check-c-abi.yaml
conda-cpp-build:
needs: checks
secrets: inherit
Expand Down
74 changes: 74 additions & 0 deletions .github/workflows/store-c-abi-baseline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Archive C ABI Baseline for Release

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version tag to archive baseline for (e.g., v26.02.00)'
required: true
type: string

jobs:
archive-baseline:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Download main baseline artifact
uses: dawidd6/action-download-artifact@v3
with:
name: c-abi-baseline-main
workflow: check-c-abi.yaml
branch: main
path: baseline/

- name: Archive baseline with release version
uses: actions/upload-artifact@v4
with:
name: c-abi-baseline-${{ github.event.release.tag_name || inputs.version }}
path: baseline/c_abi.json.gz
retention-days: 400 # ~13 months

- name: Commit baseline to repository (for long-term storage)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Create a baselines branch if it doesn't exist
git fetch origin baselines:baselines 2>/dev/null || git checkout --orphan baselines
git checkout baselines 2>/dev/null || true

# Copy the baseline file with version name
mkdir -p c-abi-baselines
cp baseline/c_abi.json.gz c-abi-baselines/c_abi_${{ github.event.release.tag_name || inputs.version }}.json.gz

# Commit and push
git add c-abi-baselines/
git commit -m "Archive C ABI baseline for ${{ github.event.release.tag_name || inputs.version }}"
git push origin baselines
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create release comment
if: github.event_name == 'release'
uses: actions/github-script@v7
with:
script: |
const tagName = context.payload.release.tag_name;

github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.payload.release.target_commitish,
body: `✅ C ABI baseline archived for release ${tagName}

This baseline has been archived from the main branch and will be available for historical reference.

The baseline is stored in:
- Artifact: \`c-abi-baseline-${tagName}\` (available for ~13 months)
- Branch: \`baselines\` (permanent storage)
`
});
Loading