diff --git a/.github/wheel_test.sh b/.github/wheel_test.sh new file mode 100644 index 0000000..3d3a3fd --- /dev/null +++ b/.github/wheel_test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Smoke first to fail fast, then the full suite, against the installed +# cppjit. Run from the repository (or cibuildwheel test-sources) root; +# shared by the cibuildwheel test phase and the relocation and sdist jobs. +set -e +export CPPINTEROP_EXTRA_INTERPRETER_ARGS=-std=c++20 +python -X faulthandler .github/wheel_smoke.py +cd test +make -j"$(python -c 'import os; print(os.cpu_count())')" PYTHON=python +python -m pytest -ra diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 9d90731..02ae701 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -5,19 +5,18 @@ name: Wheels # linux mounts it into the build container, the same manylinux_2_28 image # the toolchain was built on. +# Every code PR and every push to main builds one python per platform +# with the full suite inside; the `build-wheels` label switches a PR to +# the full python list plus the sdist install test. Schedule, tags, and +# dispatch always build the full list. on: workflow_dispatch: pull_request: - paths: - - '.github/workflows/wheels.yml' - - '.github/wheel_smoke.py' - - '.github/wheel_contents_check.py' - - 'pyproject.toml' - - 'CMakeLists.txt' - - 'cmake/**' - - 'src/interop/**' - - 'python/cppjit/_cpython_cppjit.py' + types: [opened, synchronize, reopened, labeled] + paths-ignore: + - 'README.md' push: + branches: [main] tags: ['v*'] schedule: - cron: '30 4 * * 1' @@ -32,10 +31,14 @@ concurrency: jobs: wheels: name: wheels ${{ matrix.label }} + # A labeled event re-runs the workflow only for build-wheels itself; + # jobs without a needs edge to this one repeat the guard by alias. + if: &label-guard ${{ github.event.action != 'labeled' || github.event.label.name == 'build-wheels' }} strategy: fail-fast: false matrix: - include: + # One row per shipped wheel; test-relocation reuses this list. + include: &platforms - { os: ubuntu-24.04, label: manylinux-x86_64, arch: x86_64 } - { os: ubuntu-24.04-arm, label: manylinux-aarch64, arch: aarch64 } - { os: macos-26, label: macosx-arm64, arch: arm64 } @@ -62,6 +65,17 @@ jobs: RECIPE_PATH: ${{ steps.llvm.outputs.path }} run: sudo mv "$RECIPE_PATH" /opt/llvm + # The inverse of test-sdist's full-run condition: plain PRs and + # main pushes build one python; schedule, dispatch, tags, and PRs + # labeled build-wheels fall through to pyproject's full list. + - name: Build one python unless a full run is requested + if: >- + ${{ !(github.event_name == 'schedule' + || github.event_name == 'workflow_dispatch' + || startsWith(github.ref, 'refs/tags/') + || contains(github.event.pull_request.labels.*.name, 'build-wheels')) }} + run: echo 'CIBW_BUILD=cp314-*' >> "$GITHUB_ENV" + - uses: pypa/cibuildwheel@v4.2.0 - name: Assert the build left the checkout clean @@ -76,8 +90,54 @@ jobs: path: wheelhouse/*.whl if-no-files-found: error + # Relocation: each wheel installs and runs the full suite away from its + # build machine. The linux image starts bare (g++ because the JIT reads + # the host's C++ standard library headers). The label guard reaches + # this job through needs. + test-relocation: + name: test relocation (${{ matrix.label }}) + needs: wheels + strategy: + fail-fast: false + matrix: + include: *platforms + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - uses: actions/download-artifact@v8 + with: + name: wheels-${{ matrix.label }} + path: dist + + - name: Install the cp314 wheel and run the suite on python:3.14-slim + if: runner.os == 'Linux' + run: | + docker run --rm -v "$PWD":/src -w /src -e PYTHONSAFEPATH=1 \ + -e DEBIAN_FRONTEND=noninteractive python:3.14-slim bash -ec ' + apt-get -qq update && apt-get -qq -y install g++ make libeigen3-dev libboost-dev + pip install dist/cppjit-*cp314*.whl -r requirements.txt + bash .github/wheel_test.sh' + + - uses: actions/setup-python@v7 + if: runner.os == 'macOS' + with: + python-version: '3.14' + + - name: Install the cp314 wheel and run the suite + if: runner.os == 'macOS' + env: + PYTHONSAFEPATH: '1' + run: | + brew install eigen boost + pip install dist/cppjit-*cp314*.whl -r requirements.txt + bash .github/wheel_test.sh + sdist: name: sdist + if: *label-guard runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 @@ -95,48 +155,17 @@ jobs: 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. - test-wheel: - name: test wheel (full suite) - needs: wheels - 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: actions/download-artifact@v8 - with: - name: wheels-manylinux-x86_64 - path: wheelhouse - - - name: Install the test suite's native deps - # test_eigen/test_boost need them; the CI cells install the same pair. - run: sudo apt-get -q update && sudo apt-get -y install libeigen3-dev libboost-dev - - - name: Install the wheel and the test requirements - run: python -m pip install wheelhouse/cppjit-*cp312*.whl -r requirements.txt - - - name: Smoke the wheel outside pytest - run: python -X faulthandler .github/wheel_smoke.py - - - name: Run the test suite against the installed wheel - env: - CPPINTEROP_EXTRA_INTERPRETER_ARGS: -std=c++20 - run: | - cd test - make -j$(nproc) PYTHON=python - python -m pytest -ra - # Build from the sdist and run the full suite against the install. + # PR source-build coverage lives in ci.yml; this job rides the full + # runs (schedule/tag/dispatch and the build-wheels label). test-sdist: name: test sdist (build + full suite) needs: sdist + if: >- + ${{ github.event_name == 'schedule' + || github.event_name == 'workflow_dispatch' + || startsWith(github.ref, 'refs/tags/') + || contains(github.event.pull_request.labels.*.name, 'build-wheels') }} runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 @@ -173,13 +202,5 @@ jobs: --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 + run: bash .github/wheel_test.sh diff --git a/pyproject.toml b/pyproject.toml index 598c430..b269fe4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,21 +41,22 @@ archs = ["auto64"] 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" +test-sources = ["test", "requirements.txt", ".github/wheel_smoke.py", ".github/wheel_test.sh"] +# smoke + full suite; the script is shared with the relocation and sdist jobs +test-command = "bash .github/wheel_test.sh" # imports must resolve from the installed wheel, not the checkout test-environment = { PYTHONSAFEPATH = "1" } [tool.cibuildwheel.linux] manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" +before-test = "python -m pip install -r {project}/requirements.txt" # /opt/llvm is staged on the runner by wheels.yml. 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] 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]