From ca567c960d0e93562f756272b5130200f07b2671 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 10 Jun 2026 16:39:41 +0200 Subject: [PATCH] Add reuseable build-and-test actions for CppJIT --- .github/workflows/cppjit.yml | 128 +++++++++++++++++++ actions/build-and-test-cppjit/action.yml | 153 +++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 .github/workflows/cppjit.yml create mode 100644 actions/build-and-test-cppjit/action.yml diff --git a/.github/workflows/cppjit.yml b/.github/workflows/cppjit.yml new file mode 100644 index 0000000..ca90184 --- /dev/null +++ b/.github/workflows/cppjit.yml @@ -0,0 +1,128 @@ +name: CppJIT build and test (reusable) + +# Reusable (per matrix entry) single-cell workflow: builds CppJIT against a chosen CppInterOp +# ref and runs its test suite. Callers own the matrix and fan out (one call per row): +# - cppjit's own ci.yml: the full supported matrix. +# - cppjit's nightly.yml: full matrix + sweeps + valgrind. +# - CppInterOp: 1-2 cells, passing its PR SHA as cppinterop-ref to +# verify the change doesn't break CppJIT downstream. + +on: + workflow_call: + inputs: + cppjit-repo: + type: string + default: compiler-research/cppjit + description: 'owner/repo of CppJIT to check out.' + cppjit-ref: + type: string + default: master + description: 'CppJIT git ref to check out.' + cppinterop-ref: + type: string + description: | + CppInterOp ref to build against (passed to ExternalProject). + Callers supply their own pinned commit/tag + os: + type: string + required: true + description: 'Runner image, e.g. ubuntu-24.04 | ubuntu-24.04-arm | macos-26 | macos-26-intel.' + llvm-version: + type: string + required: true + description: 'LLVM major: "20" | "21" | "22".' + llvm-flavor: + type: string + default: system + description: "setup-llvm flavor: 'system' (apt/brew, LLVM 20/21), '' (recipe cache, LLVM 22), or 'cling'." + llvm-flavor-version: + type: string + default: '' + description: "setup-llvm flavor-version (e.g. 'cling-llvm20' for flavor:cling); empty otherwise." + cling: + type: boolean + default: false + description: 'Build CppInterOp with Cling (CPPJIT_USE_CLING=ON, llvm-flavor: cling)' + python-version: + type: string + required: true + description: 'Python version for actions/setup-python.' + cxx-standard: + type: string + default: '20' + description: 'Runtime C++ standard ("17"/"20").' + valgrind: + type: boolean + default: false + description: 'Run the valgrind pass (Linux only).' + run-sweeps: + type: boolean + default: false + description: 'Run the xfail-crash and xfail->skipif sweeps.' + ci-workflows-ref: + type: string + default: main + description: 'ci-workflows ref setup-llvm reads recipes/ from (recipe flavors only).' + outputs: + result-summary: + description: 'Last line of the pytest summary for this cell.' + value: ${{ jobs.build-test.outputs.summary }} + +permissions: + contents: read + +jobs: + build-test: + runs-on: ${{ inputs.os }} + name: ${{ inputs.os }}-llvm${{ inputs.llvm-version }}-py${{ inputs.python-version }}-cxx${{ inputs.cxx-standard }}${{ inputs.valgrind && '-vg' || '' }} + outputs: + summary: ${{ steps.summary.outputs.line }} + steps: + - name: Checkout CppJIT + uses: actions/checkout@v4 + with: + repository: ${{ inputs.cppjit-repo }} + ref: ${{ inputs.cppjit-ref }} + + - name: Setup Python ${{ inputs.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + + - name: Setup LLVM ${{ inputs.llvm-version }} + uses: compiler-research/ci-workflows/actions/setup-llvm@main + with: + version: ${{ inputs.llvm-version }} + os: ${{ inputs.os }} + flavor: ${{ inputs.llvm-flavor }} + flavor-version: ${{ inputs.llvm-flavor-version }} + ref: ${{ inputs.ci-workflows-ref }} + + - name: Build and test + uses: compiler-research/ci-workflows/actions/build-and-test-cppjit@main + with: + cppinterop-ref: ${{ inputs.cppinterop-ref }} + cxx-standard: ${{ inputs.cxx-standard }} + llvm-version: ${{ inputs.llvm-version }} + valgrind: ${{ inputs.valgrind }} + run-sweeps: ${{ inputs.run-sweeps }} + cling: ${{ inputs.cling }} + + - name: Capture summary + id: summary + if: always() + shell: bash + run: | + set -uo pipefail + line="$(cat "${GITHUB_WORKSPACE}/pytest-summary.txt" 2>/dev/null || echo 'no summary (build or setup failed)')" + # Always (re)write the file so a build-failed cell still uploads an artifact and shows in the report + printf '%s\n' "${line}" > "${GITHUB_WORKSPACE}/pytest-summary.txt" + echo "line=${line}" >> "$GITHUB_OUTPUT" + + - name: Upload result + if: always() + uses: actions/upload-artifact@v4 + with: + name: result-${{ inputs.os }}-llvm${{ inputs.llvm-version }}-py${{ inputs.python-version }}-cxx${{ inputs.cxx-standard }}${{ inputs.valgrind && '-vg' || '' }} + path: ${{ github.workspace }}/pytest-summary.txt + if-no-files-found: warn diff --git a/actions/build-and-test-cppjit/action.yml b/actions/build-and-test-cppjit/action.yml new file mode 100644 index 0000000..5d66497 --- /dev/null +++ b/actions/build-and-test-cppjit/action.yml @@ -0,0 +1,153 @@ +name: 'Build and test CppJIT' +description: | + Builds CppJIT against a chosen CppInterOp ref and runs its test suite. + Expects setup-llvm to have exported LLVM_DIR, CppJIT checked out at + $GITHUB_WORKSPACE, and the target Python on PATH. Every cell parameter + is an explicit input (matrix context doesn't reach composite steps under + nektos/act). +inputs: + cppinterop-ref: + required: false + description: | + CppInterOp ref to build CppJIT against. Unset (default) uses CppJIT's own + pinned CPPINTEROP_GIT_TAG which defines the current version/tag that CppJIT is based on + cxx-standard: + required: false + default: '20' + description: 'Runtime C++ standard ("17"/"20") (passed to CPPINTEROP_EXTRA_INTERPRETER_ARGS)' + cling: + required: false + default: 'false' + description: 'Build CppInterOp with Cling (CPPJIT_USE_CLING=ON, llvm-flavor: cling)' + llvm-version: + required: false + default: '' + description: 'LLVM major; selects the clangN valgrind suppression.' + valgrind: + required: false + default: 'false' + description: 'Run the valgrind pass ("true"/"false"), linux only.' + run-sweeps: + required: false + default: 'false' + description: 'Also run the disabled xfail crash/skipif tests' + +runs: + using: composite + steps: + - name: Install build and test dependencies + shell: bash + env: + VALGRIND: ${{ inputs.valgrind }} + run: | + set -euo pipefail + if [[ "${RUNNER_OS}" == "Linux" ]]; then + sudo apt-get update -qq + sudo apt-get install -y libboost-dev libeigen3-dev + if [[ "${VALGRIND}" == "true" ]]; then + sudo apt-get install -y valgrind libc6-dbg + fi + elif [[ "${RUNNER_OS}" == "macOS" ]]; then + brew install boost eigen gnu-sed + fi + python -m pip install --upgrade pip + # Test/CI deps come from the cppjit/requirements.txt + python -m pip install -r "${GITHUB_WORKSPACE}/requirements.txt" + + - name: Build and install CppJIT + shell: bash + env: + CPPINTEROP_REF: ${{ inputs.cppinterop-ref }} + CLING: ${{ inputs.cling }} + run: | + set -euo pipefail + if [[ "${RUNNER_OS}" == "macOS" ]]; then + export CMAKE_BUILD_PARALLEL_LEVEL="$(sysctl -n hw.ncpu)" + else + export CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" + fi + # Set CPPJIT_USE_CLING=ON for Cling (Cling_DIR auto-derives from LLVM_DIR, + # setup-llvm flavor:cling installs cling at the sibling cmake path). + # cppinterop-ref: overrides CPPINTEROP_GIT_TAG when explicitly specified (e.g for nightlies + # or testing a development branch), otherwise default to CppJIT's own pinned version + EXTRA_ARGS=() + [[ "${CLING}" == "true" ]] && EXTRA_ARGS+=(--config-settings=cmake.define.CPPJIT_USE_CLING=ON) + [[ -n "${CPPINTEROP_REF}" ]] && EXTRA_ARGS+=(--config-settings=cmake.define.CPPINTEROP_GIT_TAG="${CPPINTEROP_REF}") + python -m pip install . -v \ + --config-settings=cmake.define.LLVM_DIR="${LLVM_DIR}" \ + ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} + + - name: Smoke import + shell: bash + run: | + set -euo pipefail + # Neutral dir so the in-tree python/cppjit can't shadow the install. + cd "${RUNNER_TEMP}" + python -c "import cppjit.cppyy; print('cppjit import OK')" + + - name: Run test suite + shell: bash + env: + CXX_STD: ${{ inputs.cxx-standard }} + VALGRIND: ${{ inputs.valgrind }} + RUN_SWEEPS: ${{ inputs.run-sweeps }} + LLVM_VERSION: ${{ inputs.llvm-version }} + run: | + set -uo pipefail + cd test + make -j4 PYTHON=python || { echo "::error::dictionary build (make) failed"; exit 1; } + export CPPINTEROP_EXTRA_INTERPRETER_ARGS="-std=c++${CXX_STD}" + + SED=sed + [[ "${RUNNER_OS}" == "macOS" ]] && SED=gsed + + RETCODE=0 + + echo "::group::Full test suite" + # -p no:cacheprovider prevents writing .pytest_cache into the upstream-identical tree + set -o pipefail + python -m pytest -ra --tb=short -p no:cacheprovider --durations=25 \ + | tee complete_testrun.log 2>&1 || RETCODE=$? + set +o pipefail + tail -n1 complete_testrun.log > "${GITHUB_WORKSPACE}/pytest-summary.txt" || true + echo "::endgroup::" + + if [[ "${RUN_SWEEPS}" == "true" ]]; then + # A crash here = an xfail that stopped crashing and should drop its marker. + echo "::group::xfail-crash sweep" + find . -name "*.py" -exec "${SED}" -i '/run=False/!s/^ *@mark.xfail\(.*\)/#&/' {} \; + python -m pytest -n 1 -m "xfail" --runxfail -ra --max-worker-restart 512 | tee test_crashed.log 2>&1 || true + git checkout . + echo "::endgroup::" + + echo "::group::xfail->skipif sweep" + find . -name "*.py" -exec "${SED}" -i -E 's/(^ *)@mark.xfail\(run=(.*)/\1@mark.skipif(condition=not \2/g' {} \; + python -m pytest --runxfail -ra | tee test_xfailed.log 2>&1 || true + git checkout . + echo "::endgroup::" + fi + + if [[ "${VALGRIND}" == "true" && "${RUNNER_OS}" == "Linux" ]]; then + echo "::group::valgrind" + if [[ "${RUNNER_ARCH}" == "ARM64" ]]; then + PERVER="../etc/clang${LLVM_VERSION}-valgrind_arm.supp" + else + PERVER="../etc/clang${LLVM_VERSION}-valgrind.supp" + fi + # Stack all suppression files (valgrind aborts on a missing file). + SUPP_ARGS=() + for _supp in "../etc/valgrind-cppyy-cling.supp" \ + "../etc/cppinterop-clang${LLVM_VERSION}-valgrind.supp" \ + "${PERVER}"; do + [[ -f "${_supp}" ]] && SUPP_ARGS+=(--suppressions="${_supp}") || echo "::notice::no ${_supp}" + done + export IS_VALGRIND=true + valgrind --show-error-list=yes --error-exitcode=1 --track-origins=yes \ + --gen-suppressions=all ${SUPP_ARGS[@]+"${SUPP_ARGS[@]}"} \ + python -m pytest -m "not xfail" -ra --ignore=test_leakcheck.py || RETCODE=$? + unset IS_VALGRIND + echo "::endgroup::" + fi + + echo "Summary: $(cat "${GITHUB_WORKSPACE}/pytest-summary.txt" 2>/dev/null || echo 'no summary')" + exit "${RETCODE}"