From e862140b5a908bf9acb6473c3929fd7c0b74a4b3 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 1 Sep 2026 13:11:27 +0200 Subject: [PATCH] [ci] Harden the wheels workflow and cibuildwheel configuration --- .github/wheel_contents_check.py | 32 +++++++++++++++ .github/workflows/wheels.yml | 69 +++++++++++++++++++++++++++++++++ pyproject.toml | 12 ++++-- zizmor.yml | 9 +++++ 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 .github/wheel_contents_check.py create mode 100644 zizmor.yml diff --git a/.github/wheel_contents_check.py b/.github/wheel_contents_check.py new file mode 100644 index 0000000..fb39f0c --- /dev/null +++ b/.github/wheel_contents_check.py @@ -0,0 +1,32 @@ +"""Fail when a wheel holds a file outside the install-layout allowlist. + +Usage: python wheel_contents_check.py [ ...]""" + +import fnmatch +import sys +import zipfile + +# fnmatch's * crosses path separators, so one pattern covers a subtree. +ALLOWED = [ + "cppjit/*.py", + "cppjit/libcppjit.so", + "cppjit/interop/lib/libclangCppInterOp*", + "cppjit/interop/lib/clang/*", + "cppjit/interop/include/*", + "cppjit-*.dist-info/*", +] + + +def check(path): + # directory entries (trailing slash) carry no content + members = [m for m in zipfile.ZipFile(path).namelist() if not m.endswith("/")] + bad = [m for m in members if not any(fnmatch.fnmatch(m, p) for p in ALLOWED)] + for member in bad: + print(f"{path}: unexpected member {member}") + return not bad + + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit(__doc__) + sys.exit(0 if all([check(path) for path in sys.argv[1:]]) else 1) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a7b3cb4..0ea475c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -11,6 +11,7 @@ on: paths: - '.github/workflows/wheels.yml' - '.github/wheel_smoke.py' + - '.github/wheel_contents_check.py' - 'pyproject.toml' - 'CMakeLists.txt' - 'cmake/**' @@ -41,6 +42,8 @@ jobs: steps: - uses: actions/checkout@v7 + with: + persist-credentials: false # ref pins the recipe content the cache key is computed from. - uses: compiler-research/ci-workflows/actions/setup-recipe@main @@ -59,23 +62,36 @@ jobs: - uses: pypa/cibuildwheel@v4.2.0 + - name: Assert the build left the checkout clean + run: git diff --exit-code + + - name: Check the wheels against the content allowlist + run: python3 .github/wheel_contents_check.py wheelhouse/*.whl + - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.label }} path: wheelhouse/*.whl + if-no-files-found: error sdist: name: sdist runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 + with: + persist-credentials: false - run: pipx run build --sdist + - name: Check the sdist metadata + run: pipx run twine check dist/*.tar.gz + - uses: actions/upload-artifact@v7 with: name: sdist path: dist/*.tar.gz + if-no-files-found: error # Run the full suite on a plain runner, outside the manylinux # container the wheel was built in. @@ -85,6 +101,8 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 + with: + persist-credentials: false - uses: actions/setup-python@v7 with: @@ -112,3 +130,54 @@ jobs: cd test make -j$(nproc) PYTHON=python python -m pytest -ra + + # Build from the sdist and run the full suite against the install. + test-sdist: + name: test sdist (build + full suite) + needs: sdist + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - uses: actions/setup-python@v7 + with: + python-version: '3.12' + + - uses: compiler-research/ci-workflows/actions/setup-recipe@main + id: llvm + with: + recipe: llvm-wheel + version: '21.1.8' + os: ubuntu-24.04 + arch: x86_64 + ref: b760e4c171961786b7b20e2cc514302df5373eef + + - uses: actions/download-artifact@v8 + with: + name: sdist + path: dist + + - name: Install the test suite's native deps + run: sudo apt-get -q update && sudo apt-get -y install libeigen3-dev libboost-dev + + - name: Build and install from the sdist with the test requirements + env: + RECIPE_PATH: ${{ steps.llvm.outputs.path }} + run: > + python -m pip install dist/cppjit-*.tar.gz -v + --config-settings=cmake.define.LLVM_DIR="$RECIPE_PATH/lib/cmake/llvm" + --config-settings=cmake.define.Clang_DIR="$RECIPE_PATH/lib/cmake/clang" + -r requirements.txt + + - name: Smoke the install outside pytest + run: python -X faulthandler .github/wheel_smoke.py + + - name: Run the test suite against the sdist install + env: + CPPINTEROP_EXTRA_INTERPRETER_ARGS: -std=c++20 + run: | + cd test + make -j$(nproc) PYTHON=python + python -m pytest -ra diff --git a/pyproject.toml b/pyproject.toml index b9a2821..521cde6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ minimum-version = "build-system.requires" wheel.install-dir = "." wheel.packages = ["python/cppjit"] cmake.build-type = "Release" +sdist.exclude = [".github", ".gitignore", ".clang-format"] [[tool.dynamic-metadata]] provider = "scikit_build_core.metadata.regex" @@ -32,23 +33,28 @@ field = "version" input = "python/cppjit/_version.py" [tool.cibuildwheel] +# cp314t needs a free-threading audit first; cp315 joins at its release. build = ["cp312-*", "cp313-*", "cp314-*"] skip = ["*-musllinux*"] build-verbosity = 1 +audit-requires = ["twine"] +audit-command = "twine check {wheel}" test-sources = ["test", "requirements.txt", ".github/wheel_smoke.py"] test-command = "python .github/wheel_smoke.py" +# imports must resolve from the installed wheel, not the checkout +test-environment = { PYTHONSAFEPATH = "1" } [tool.cibuildwheel.linux] archs = ["x86_64"] manylinux-x86_64-image = "manylinux_2_28" # /opt/llvm is staged on the runner by wheels.yml. -container-engine = { name = "docker", create-args = ["--volume=/opt/llvm:/opt/llvm"] } +container-engine = { name = "docker", create-args = ["--volume=/opt/llvm:/opt/llvm"], disable-host-mount = true } environment = { CMAKE_ARGS = "-DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DClang_DIR=/opt/llvm/lib/cmake/clang" } [tool.cibuildwheel.macos] archs = ["arm64"] -before-test = "brew install eigen boost" -test-command = "python -m pip install -r requirements.txt && python .github/wheel_smoke.py && cd test && make -j$(sysctl -n hw.ncpu) PYTHON=python && CPPINTEROP_EXTRA_INTERPRETER_ARGS=-std=c++20 python -m pytest -ra" +before-test = "brew install eigen boost && python -m pip install -r {project}/requirements.txt" +test-command = "python .github/wheel_smoke.py && cd test && make -j$(sysctl -n hw.ncpu) PYTHON=python && CPPINTEROP_EXTRA_INTERPRETER_ARGS=-std=c++20 python -m pytest -ra" environment = { CMAKE_ARGS = "-DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DClang_DIR=/opt/llvm/lib/cmake/clang", MACOSX_DEPLOYMENT_TARGET = "14.0" } [tool.pytest.ini_options] diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 0000000..7bb1574 --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,9 @@ +# Version tags for the actions we consume; compiler-research/* rides @main. +rules: + unpinned-uses: + config: + policies: + "actions/*": ref-pin + "pypa/*": ref-pin + "compiler-research/*": ref-pin + "*": hash-pin