From ba37741b76f96e3c34389b48046f81e639506359 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 19:59:22 +0530 Subject: [PATCH 01/12] add CI workflow to run the test suite Runs make test on every push/PR. Kernel verifier tests need passwordless sudo and a kernel with BTF/BPF enabled, which isn't guaranteed on every runner, so the workflow probes for working sudo first and only attempts them if it's available. Also add ctypeslib2 to the test extra: make test now regenerates vmlinux.py via tools/vmlinux-gen.py, which needs clang2py. --- .github/workflows/test.yml | 53 ++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..617b1d7c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,53 @@ +# Runs the pytest suite (IR generation + LLC compilation). Kernel verifier +# tests additionally need passwordless sudo and a kernel with BTF/BPF +# enabled, which isn't guaranteed on every runner, so we probe for working +# sudo first and only attempt them if it's there. + +name: Test + +on: + workflow_dispatch: + push: + pull_request: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang llvm libclang-dev bpftool + + - name: Install uv + run: pip install uv + + - name: Install project + run: uv pip install --system -e ".[test]" + + - name: Run test suite + run: make test + + - name: Check whether sudo is usable + id: sudo-check + run: | + if sudo -n true 2>/dev/null; then + echo "Passwordless sudo is available." + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "No passwordless sudo on this runner; kernel verifier tests will be skipped." + echo "available=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run kernel verifier tests + if: steps.sudo-check.outputs.available == 'true' + run: | + sudo -v + make test-verifier diff --git a/pyproject.toml b/pyproject.toml index 7a200834..bc822d59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ docs = [ test = [ "pytest>=8.0", "pytest-cov>=5.0", + "ctypeslib2", ] [tool.setuptools.packages.find] From 39600862545ab8797b98cbd682efcc5bd2876a8f Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:10:23 +0530 Subject: [PATCH 02/12] fix CI: bpftool is a virtual package on Ubuntu, install linux-tools instead --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 617b1d7c..b6b73219 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,10 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y clang llvm libclang-dev bpftool + # bpftool isn't an installable package by itself on Ubuntu: it's a + # virtual package provided by linux-tools-common + a kernel-flavor + # linux-tools- package. + sudo apt-get install -y clang llvm libclang-dev linux-tools-common linux-tools-generic - name: Install uv run: pip install uv From 34b59189da52a30a54a452e9c657a371ba7ca70f Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:11:38 +0530 Subject: [PATCH 03/12] fix CI: locate bpftool binary and add it to PATH manually linux-tools-generic's update-alternatives symlink for bpftool doesn't fire on GitHub-hosted runners, since their kernel version has no matching linux-tools- package. --- .github/workflows/test.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6b73219..787219a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,8 +26,19 @@ jobs: sudo apt-get update # bpftool isn't an installable package by itself on Ubuntu: it's a # virtual package provided by linux-tools-common + a kernel-flavor - # linux-tools- package. + # linux-tools- package. The runner's exact kernel version + # has no matching linux-tools- package, so the + # update-alternatives symlink for `bpftool` doesn't get set up; + # find whatever binary the generic-flavor package installed and + # put it on PATH ourselves. sudo apt-get install -y clang llvm libclang-dev linux-tools-common linux-tools-generic + bpftool_bin=$(sudo find /usr/lib/linux-tools* -name bpftool -type f 2>/dev/null | head -1) + if [ -z "$bpftool_bin" ]; then + echo "::error::Could not find a bpftool binary after installing linux-tools-generic" + exit 1 + fi + sudo ln -sf "$bpftool_bin" /usr/local/bin/bpftool + bpftool version - name: Install uv run: pip install uv From f67ff6cef30d9a578302901a5281ca2d7bad471a Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:14:26 +0530 Subject: [PATCH 04/12] pin clang bindings to 16.0.6 to match Ubuntu's default libclang pip installs the latest 'clang' release by default, whose libclang API surface is newer than Ubuntu 24.04's apt libclang-16, causing a LibclangError about an undefined symbol. Older bindings against a newer libclang stay compatible, so pin the bindings low rather than the system library. --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index bc822d59..208650c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,13 @@ test = [ "pytest>=8.0", "pytest-cov>=5.0", "ctypeslib2", + # Pinned rather than left to ctypeslib2's own (unpinned) dependency: pip + # installs the latest release by default, and its libclang API surface + # can be newer than the system libclang (e.g. Ubuntu 24.04 ships + # libclang-16), which fails with a LibclangError about an undefined + # symbol. Older bindings against a newer libclang stay compatible, so + # pin to an old-enough release instead of pinning apt's libclang. + "clang==16.0.6", ] [tool.setuptools.packages.find] From 02d4216a50ea79c7aa673fdc3507a8015b8c0fd0 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:17:59 +0530 Subject: [PATCH 05/12] DEBUG: dump struct_vmbus_channel_offer_channel on test failure --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 787219a2..2c011623 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,6 +49,10 @@ jobs: - name: Run test suite run: make test + - name: DEBUG - dump offending struct on failure + if: failure() + run: grep -n -B2 -A40 'class struct_vmbus_channel_offer_channel' vmlinux.py || true + - name: Check whether sudo is usable id: sudo-check run: | From 8abde43eef8da688c868863f1c8f7c8725180639 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:23:40 +0530 Subject: [PATCH 06/12] remove debug step now that the bitfield issue is fixed --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c011623..787219a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,10 +49,6 @@ jobs: - name: Run test suite run: make test - - name: DEBUG - dump offending struct on failure - if: failure() - run: grep -n -B2 -A40 'class struct_vmbus_channel_offer_channel' vmlinux.py || true - - name: Check whether sudo is usable id: sudo-check run: | From d473c2b0a6aba878cfd54c893afa71a7db6c112c Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:30:44 +0530 Subject: [PATCH 07/12] fix CI: install LLVM 19 from apt.llvm.org, Ubuntu's default is too old llvmlite>=0.49's ArgumentAttributes only recognizes the LLVM 19+ 'captures(none)' spelling of the renamed 'nocapture' attribute. Ubuntu noble's default llvm/clang packages are LLVM 18, whose llc can't parse that attribute in the emitted .ll text. --- .github/workflows/test.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 787219a2..4f2d61c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,23 @@ jobs: - name: Install system dependencies run: | sudo apt-get update + sudo apt-get install -y lsb-release wget software-properties-common gnupg linux-tools-common linux-tools-generic + + # Ubuntu's default `llvm`/`clang` packages (LLVM 18 on noble) are + # too old to assemble the IR llvmlite>=0.49 emits: llvmlite's + # ArgumentAttributes only knows the LLVM 19+ 'captures(none)' + # spelling (LLVM renamed 'nocapture' to it), and an older llc + # can't parse that attribute in the .ll text. Install a + # known-recent LLVM from apt.llvm.org instead of the distro + # default, and make its tools the ones found on PATH. + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 all + sudo ln -sf /usr/bin/clang-19 /usr/local/bin/clang + sudo ln -sf /usr/bin/llc-19 /usr/local/bin/llc + clang --version + llc --version + # bpftool isn't an installable package by itself on Ubuntu: it's a # virtual package provided by linux-tools-common + a kernel-flavor # linux-tools- package. The runner's exact kernel version @@ -31,7 +48,6 @@ jobs: # update-alternatives symlink for `bpftool` doesn't get set up; # find whatever binary the generic-flavor package installed and # put it on PATH ourselves. - sudo apt-get install -y clang llvm libclang-dev linux-tools-common linux-tools-generic bpftool_bin=$(sudo find /usr/lib/linux-tools* -name bpftool -type f 2>/dev/null | head -1) if [ -z "$bpftool_bin" ]; then echo "::error::Could not find a bpftool binary after installing linux-tools-generic" From 2ec5e9cbd6555e02a02396ddce76851bc36767ff Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:34:12 +0530 Subject: [PATCH 08/12] DEBUG: show real llc stderr on failure --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f2d61c4..fa480d3d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,6 +65,16 @@ jobs: - name: Run test suite run: make test + - name: DEBUG - show real llc stderr + if: failure() + run: | + python3 -c " +from pythonbpf.codegen import compile_to_ir +compile_to_ir('tests/passing_tests/if.py', '/tmp/debug.ll') +" + cat /tmp/debug.ll + llc -march=bpf -filetype=obj -O2 /tmp/debug.ll -o /tmp/debug.o + - name: Check whether sudo is usable id: sudo-check run: | From d0d8364c0b7e25454cabdf14c4a9351ad5389bba Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:34:39 +0530 Subject: [PATCH 09/12] fix YAML syntax in debug step --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa480d3d..8b18d24a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,10 +68,7 @@ jobs: - name: DEBUG - show real llc stderr if: failure() run: | - python3 -c " -from pythonbpf.codegen import compile_to_ir -compile_to_ir('tests/passing_tests/if.py', '/tmp/debug.ll') -" + python3 -c "from pythonbpf.codegen import compile_to_ir; compile_to_ir('tests/passing_tests/if.py', '/tmp/debug.ll')" cat /tmp/debug.ll llc -march=bpf -filetype=obj -O2 /tmp/debug.ll -o /tmp/debug.o From 7cf31466d914591f6ebf7444496271dcc2b85c79 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:38:04 +0530 Subject: [PATCH 10/12] fix CI: install LLVM 22 (matching llvmlite's bundled version), not 19 Verified on CI: LLVM 19's llc still rejects 'captures(none)' as a parse error ('expected ) at end of argument list'). llvmlite 0.49 bundles LLVM 22.1.0 internally; match that generation instead. --- .github/workflows/test.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b18d24a..61fcf586 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,16 +28,18 @@ jobs: # Ubuntu's default `llvm`/`clang` packages (LLVM 18 on noble) are # too old to assemble the IR llvmlite>=0.49 emits: llvmlite's - # ArgumentAttributes only knows the LLVM 19+ 'captures(none)' - # spelling (LLVM renamed 'nocapture' to it), and an older llc - # can't parse that attribute in the .ll text. Install a - # known-recent LLVM from apt.llvm.org instead of the distro - # default, and make its tools the ones found on PATH. + # ArgumentAttributes only knows the 'captures(none)' spelling of + # the renamed 'nocapture' attribute, matching the LLVM 22.1.0 it + # bundles internally - and an llc from an older LLVM (verified: + # 19 still rejects it as a parse error) can't read that attribute + # in the .ll text. Install a matching-generation LLVM from + # apt.llvm.org instead of the distro default, and make its tools + # the ones found on PATH. wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 19 all - sudo ln -sf /usr/bin/clang-19 /usr/local/bin/clang - sudo ln -sf /usr/bin/llc-19 /usr/local/bin/llc + sudo ./llvm.sh 22 all + sudo ln -sf /usr/bin/clang-22 /usr/local/bin/clang + sudo ln -sf /usr/bin/llc-22 /usr/local/bin/llc clang --version llc --version From 3a2fa45345dd89097cbc9c79fe09f0d3057beb72 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:41:48 +0530 Subject: [PATCH 11/12] remove debug step, LLVM 22 fix confirmed working on CI --- .github/workflows/test.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61fcf586..e5769360 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,13 +67,6 @@ jobs: - name: Run test suite run: make test - - name: DEBUG - show real llc stderr - if: failure() - run: | - python3 -c "from pythonbpf.codegen import compile_to_ir; compile_to_ir('tests/passing_tests/if.py', '/tmp/debug.ll')" - cat /tmp/debug.ll - llc -march=bpf -filetype=obj -O2 /tmp/debug.ll -o /tmp/debug.o - - name: Check whether sudo is usable id: sudo-check run: | From 7ea46e5a9130adaea047a31ee4f2e17668a00889 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Sun, 30 Aug 2026 20:47:55 +0530 Subject: [PATCH 12/12] avoid running CI twice per push: scope push trigger to master Branches live in this repo, not forks, so a push to a branch with an open PR fired both push and pull_request for the same commit. push now only fires for master (a post-merge check); pull_request already covers every commit on a feature branch. --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5769360..f510c699 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,12 +2,19 @@ # tests additionally need passwordless sudo and a kernel with BTF/BPF # enabled, which isn't guaranteed on every runner, so we probe for working # sudo first and only attempt them if it's there. +# +# `push` is scoped to master only: branches here live in this repo rather +# than forks, so a push to a branch with an open PR would otherwise fire +# both `push` and `pull_request` for the same commit, running everything +# twice. `pull_request` covers feature branches; `push` still gives master +# a post-merge check. name: Test on: workflow_dispatch: push: + branches: [master] pull_request: jobs: