Skip to content
Merged
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
128 changes: 128 additions & 0 deletions .github/workflows/cppjit.yml
Original file line number Diff line number Diff line change
@@ -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
153 changes: 153 additions & 0 deletions actions/build-and-test-cppjit/action.yml
Original file line number Diff line number Diff line change
@@ -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}"
Loading