Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e528e91
Python: add a scikit-build-core project for a thin pip/uv wheel
kennyweiss Jul 29, 2026
0492cb8
Python: Allow building the wheel against the stable ABI (abi3)
kennyweiss Jul 29, 2026
f63ff26
Python: Adds CI plan to test Python installation with uv
kennyweiss Jul 29, 2026
fe4686d
Python: Updates user docs and README about uv setup
kennyweiss Jul 29, 2026
956663b
Sidre/Python bugfix: Do not pin external views onto Sidre-owned storage
kennyweiss Jul 29, 2026
eb28a25
sidre: Fixes headers in nanobind_sidre
kennyweiss Jul 29, 2026
68b79af
Sidre/Python: Improves lifetime tests
kennyweiss Jul 29, 2026
a653809
Adds `build/` to .gitignore
kennyweiss Jul 29, 2026
9c60942
Python: Streamlines Python installation docs
kennyweiss Jul 29, 2026
6993439
Python: Tests generate files, so run run from a scratch directory
kennyweiss Jul 29, 2026
d5519d4
Minor touchups to docs
kennyweiss Jul 29, 2026
ac66160
Python: Fixes and streamlines setup for uv/pip
kennyweiss Jul 29, 2026
bf1dad5
Python: Use AXOM_DIR instead of axom_DIR
kennyweiss Jul 29, 2026
b90dec1
Python: More streamlining of docs
kennyweiss Jul 30, 2026
b11a569
Sidre/Python: Annotate and name sidre binding function arguments
kennyweiss Jul 30, 2026
f4cb4b4
Python: Improves docs about installing optional dependencies
kennyweiss Jul 30, 2026
aaef4d1
Python: Updates Python docker CI script to match docs
kennyweiss Jul 30, 2026
d1201a1
Python: More consolidation of Python setup/installation docs
kennyweiss Jul 30, 2026
3cad60b
Python: Clarifies error message and docs associate with stable API
kennyweiss Jul 30, 2026
8e46b46
Python: Improves logic for finding the conduit python module
kennyweiss Jul 30, 2026
fcaa276
Python: Resolve conduit python location to an absolute path
kennyweiss Jul 30, 2026
e78c007
Python: Fixes wheel lookup of Conduit Python path
kennyweiss Jul 30, 2026
2f38547
Python: Remove untested HIP workaround for uv bindings
kennyweiss Jul 30, 2026
28fd116
Python: Clarify and fix env shell script
kennyweiss Jul 30, 2026
f2f5efb
Sidre/Python: Improves pinning logic for external buffers
kennyweiss Jul 30, 2026
5379dfa
Sidre/Python: Adds tests and clarifying comment about repurcussions o…
kennyweiss Jul 30, 2026
a6c69a8
Python: Handle differences between two python installation paths
kennyweiss Jul 30, 2026
4516535
Python: Use `set -e` and `set -x` instead of or_die in wheels CI script
kennyweiss Jul 30, 2026
8ad35f0
Python: Ensure our Python gets MPI when Axom is configured with MPI
kennyweiss Jul 30, 2026
8082cfb
Python: Fixes CI to work locally
kennyweiss Jul 31, 2026
93e4c31
Python: Some minor fixups and clarifications
kennyweiss Jul 31, 2026
747ee1e
CI bugfix -- need to run the wheels test through a job that has an Ax…
kennyweiss Aug 2, 2026
573061b
CI bugfix -- ensure we can install uv in our CI images
kennyweiss Aug 2, 2026
427fcad
Python: Ensures the python bindings project is using the same CXX_STA…
kennyweiss Aug 11, 2026
8f67e36
Updates RELEASE-NOTES
kennyweiss Jul 29, 2026
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
19 changes: 19 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,21 @@ jobs:
cmake_opts: '-DBUILD_SHARED_LIBS=ON -DENABLE_BENCHMARKS:BOOL=ON'
do_build: 'yes'
do_benchmarks: 'yes'
do_wheel: 'yes'
- job_name: gcc@13.3.1, shared, 32bit
host_config: gcc@13.3.1.cmake
compiler_image: ${{ needs.set_image_vars.outputs.gcc_docker_image }}
cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_USE_64BIT_INDEXTYPE:BOOL=OFF -DAXOM_NO_INT64_T=1'
do_build: 'yes'
do_benchmarks: 'no'
do_wheel: 'no'
- job_name: llvm@19.0.0, shared, benchmarks, quest regression
host_config: llvm@19.0.0.cmake
compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }}
cmake_opts: '-DBUILD_SHARED_LIBS=ON -DENABLE_BENCHMARKS:BOOL=ON'
do_build: 'yes'
do_benchmarks: 'yes'
do_wheel: 'no'
include:
- build_type: Debug
config:
Expand All @@ -61,6 +64,7 @@ jobs:
cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR'
do_build: 'yes'
do_benchmarks: 'no'
do_wheel: 'no'
- build_type: Debug
config:
job_name: llvm@19.0.0, shared, no umpire
Expand All @@ -69,6 +73,7 @@ jobs:
cmake_opts: '-DBUILD_SHARED_LIBS=ON -U UMPIRE_DIR'
do_build: 'yes'
do_benchmarks: 'no'
do_wheel: 'no'
- build_type: Debug
config:
job_name: llvm@19.0.0, shared, no raja and umpire
Expand All @@ -77,6 +82,7 @@ jobs:
cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR -U UMPIRE_DIR'
do_build: 'yes'
do_benchmarks: 'no'
do_wheel: 'no'
- build_type: Debug
config:
job_name: llvm@19.0.0, shared, no profiling
Expand All @@ -85,6 +91,7 @@ jobs:
cmake_opts: '-DBUILD_SHARED_LIBS=ON -U CALIPER_DIR -U ADIAK_DIR'
do_build: 'yes'
do_benchmarks: 'no'
do_wheel: 'no'
name: ${{ matrix.build_type }} - ${{ matrix.config.job_name }}
container:
image: ${{ matrix.config.compiler_image }}
Expand Down Expand Up @@ -113,6 +120,18 @@ jobs:
CMAKE_EXTRA_FLAGS=" ${{ matrix.config.cmake_opts }} -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=${{ contains(matrix.config.job_name, 'quest regression') && 'ON' || 'OFF' }} " \
BUILD_TYPE=${{ matrix.build_type }} \
./scripts/github-actions/linux-build_and_test.sh
- name: Build and test the Python wheel - ${{ matrix.build_type }} - ${{ matrix.config.job_name }}
# Builds Python wheel (src/python) against the Axom that the step above just installed; one build type is enough.
if: matrix.config.do_wheel == 'yes' && matrix.build_type == 'Release'
shell: bash
timeout-minutes: 40
run: |
# Find the installation path
AXOM_INSTALL=$(awk -F= '/^CMAKE_INSTALL_PREFIX:PATH=/{print $2}' builddir/CMakeCache.txt)
# Run the wheels test
HOST_CONFIG=host-configs/docker/${{ matrix.config.host_config }} \
AXOM_INSTALL="${AXOM_INSTALL}" \
./scripts/github-actions/linux-wheel_and_test.sh
- name: Upload Test Results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ install-
.project
.settings
build-*
build/
install-*
_axom_build_and_test_*
*.pyc
Expand Down
4 changes: 4 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
- Klee: Adds support for lua-based input decks for shaping
- Slam: Adds convenience aliases in `axom/slam/Aliases.hpp` for the most common set and relation configurations,
including `ArraySet`, `ArrayViewSet`, `VariableRelation`, `ConstantRelation` and their `View` forms.
- Python: Adds a scikit-build-core project under `src/python/` for building a thin, pip/uv-installable `axom` wheel.
The wheel compiles Axom's bindings against an already-installed Axom.

### Removed
- Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset.
Expand Down Expand Up @@ -104,6 +106,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
- Quest: Status-returning reader/writer operations in `C2CReader`, `MFEMReader`, `ProEReader`,
`STEPReader`, `STLReader`, `STLWriter`, and their parallel variants are now marked `[[nodiscard]]`.
Callers that previously ignored returned status values must check them to avoid compiler diagnostics.
- Python: Sidre's bindings all name their arguments, so they can be passed by keyword and show up in IDE
completion and signature help.

### Fixed
- MIR/Bump: `MergeCoordsetPoints` now only emits its node-merge `SLIC_INFO` when MIR `verbose` is enabled on the Conduit options passed through ELVIRA.
Expand Down
166 changes: 166 additions & 0 deletions scripts/github-actions/linux-wheel_and_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/bin/bash
##############################################################################
# Copyright (c) Lawrence Livermore National Security, LLC and other
# Axom Project Contributors. See top-level LICENSE and COPYRIGHT
# files for dates and other details.
#
# SPDX-License-Identifier: (BSD-3-Clause)
##############################################################################

# Build the thin, pip/uv-installable Axom wheel and exercise it end to end,
# without using the run_python_with_axom.sh wrapper or updating the PYTHONPATH:
#
# 1. build the wheel from src/python against the prebuilt Axom install
# specified by AXOM_DIR or AXOM_INSTALL (find_package(axom));
# 2. install the wheel into a fresh uv venv;
# 3. verify the wheel installed conduit.pth for the same-build Conduit python module;
# 4. run the Sidre Python test suite with plain pytest.
#
# Intended for the gcc docker image, which is nanobind-enabled.

# Fail on the first error, including inside pipelines, and trace every command so
# a CI failure is readable from the log alone.
set -e
set -o pipefail
set -x

HOST_CONFIG="${HOST_CONFIG:-host-configs/docker/gcc@13.3.1.cmake}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would just fail if it is not set.


echo "~~~~ helpful info ~~~~"
echo "USER=$(id -u -n)"
echo "PWD=$(pwd)"
echo "HOST_CONFIG=${HOST_CONFIG}"
echo "~~~~~~~~~~~~~~~~~~~~~~"

absolute_path() {
local path="$1"
if [[ ! -e "${path}" ]]; then
echo "ERROR: Path does not exist: ${path}" >&2
return 1
fi
local dir
local base
dir=$(dirname "${path}")
base=$(basename "${path}")
printf "%s/%s" "$(cd "${dir}" && pwd -P)" "${base}"
}

if [[ ! -f "${HOST_CONFIG}" ]]; then
echo "ERROR: Host-config not found: ${HOST_CONFIG}" >&2
exit 1
fi
HOST_CONFIG_PATH=$(absolute_path "${HOST_CONFIG}")
echo "HOST_CONFIG_PATH=${HOST_CONFIG_PATH}"

# extract value from host-config line of the form `set(${name} ON CACHE BOOL "")`
# then capitalizes it and looks for true-like patterns
cmake_bool_from_file_is_on() {
local name="$1"
local value
value=$(awk -v name="${name}" '
$0 ~ "set\\(" name "[ \t\"]+" {
line = $0
sub("^[ \t]*set\\(" name "[ \t\"]+", "", line)
sub("[ \t\"\\)].*$", "", line)
print line
exit
}
' "${HOST_CONFIG_PATH}")
value="${value^^}"
[[ "${value}" == "ON" || "${value}" == "TRUE" || "${value}" == "YES" || "${value}" == "1" ]]
}

if [[ -n "${AXOM_INSTALL:-}" && -z "${AXOM_DIR:-}" ]]; then
AXOM_DIR="${AXOM_INSTALL%/}/lib/cmake"
fi

if [[ -z "${AXOM_DIR:-}" || ! -f "${AXOM_DIR}/axom-config.cmake" ]]; then
echo "ERROR: Axom CMake package not found." >&2
echo " Set AXOM_DIR to the directory containing axom-config.cmake," >&2
echo " or set AXOM_INSTALL to an Axom install prefix." >&2
exit 1
fi
AXOM_DIR=$(absolute_path "${AXOM_DIR}")
echo "AXOM_DIR=${AXOM_DIR}"

AXOM_WHEEL_ENABLE_MPI=OFF
if cmake_bool_from_file_is_on ENABLE_MPI; then
AXOM_WHEEL_ENABLE_MPI=ON
fi
echo "AXOM_WHEEL_ENABLE_MPI=${AXOM_WHEEL_ENABLE_MPI}"

echo "~~~~~~ ENSURE uv IS AVAILABLE ~~~~~~"
if ! command -v uv >/dev/null 2>&1; then
# Distro Pythons (e.g. Ubuntu 24.04) ship a PEP 668 EXTERNALLY-MANAGED marker, which makes `pip install --user` fail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this something you hit or randomly injected by codex?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little bit of both (?) I did run into the problem where --break-system-packages was necessary.

# uv only lands in the user site directory so opting out is safe here.
pip_args="--user"
if python3 -c 'import os, sysconfig, sys; sys.exit(0 if os.path.exists(os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED")) else 1)'; then
pip_args="${pip_args} --break-system-packages"
fi
python3 -m pip install ${pip_args} uv
export PATH="${HOME}/.local/bin:${PATH}"
fi
uv --version

echo "~~~~~~ BUILD THE THIN WHEEL FROM src/python ~~~~~~"
# Point find_package at the install with AXOM_DIR
# Conduit resolves transitively from axom's config, which records its Conduit prefix
rm -rf dist
uv build --wheel \
-C cmake.args=-C \
-C "cmake.args=${HOST_CONFIG_PATH}" \
-C "cmake.define.AXOM_DIR=${AXOM_DIR}" \
--out-dir dist \
src/python
ls -l dist
AXOM_WHEEL=$(find dist -maxdepth 1 -name 'axom-*.whl' -print -quit)
if [[ -z "${AXOM_WHEEL}" ]]; then
echo "ERROR: Axom wheel not found in dist/."
exit 1
fi

echo "~~~~~~ FRESH VENV + INSTALL THE WHEEL ~~~~~~"
# Pin the interpreter that built the wheel, so the venv cannot pick a different one.
VENV_DIR=/tmp/axom-wheel-venv
rm -rf "${VENV_DIR}"
uv venv --python "$(command -v python3)" "${VENV_DIR}"
VENV_PY="${VENV_DIR}/bin/python"
AXOM_WHEEL_EXTRAS="test"
if [[ "${AXOM_WHEEL_ENABLE_MPI}" == "ON" ]]; then
AXOM_WHEEL_EXTRAS="test,mpi"
fi
uv pip install --python "${VENV_PY}" "${AXOM_WHEEL}[${AXOM_WHEEL_EXTRAS}]"

echo "~~~~~~ VERIFY WHEEL-INSTALLED CONDUIT .pth ~~~~~~"
PLATLIB=$("${VENV_PY}" -c 'import sysconfig; print(sysconfig.get_paths()["platlib"])')
CONDUIT_PTH="${PLATLIB}/conduit.pth"
if [[ ! -f "${CONDUIT_PTH}" ]]; then
echo "ERROR: Expected wheel to install ${CONDUIT_PTH}."
echo " The wheel should expose the same-build Conduit python module without a manual PYTHONPATH update."
exit 1
fi
CONDUIT_PY_DIR=$(sed -n '1p' "${CONDUIT_PTH}")
if [[ -z "${CONDUIT_PY_DIR}" || ! -d "${CONDUIT_PY_DIR}" ]]; then
echo "ERROR: ${CONDUIT_PTH} points to missing Conduit python module directory '${CONDUIT_PY_DIR}'."
exit 1
fi
echo "verified ${CONDUIT_PTH} -> ${CONDUIT_PY_DIR}"

echo "~~~~~~ IMPORT SMOKE TEST ~~~~~~"
"${VENV_PY}" -c \
"import axom, axom.sidre, conduit, numpy; print('axom', axom.__version__); print('axom.sidre', axom.sidre.__version__)"
if [[ "${AXOM_WHEEL_ENABLE_MPI}" == "ON" ]]; then
"${VENV_PY}" -c "import mpi4py, axom.sidre as sidre; assert sidre.AXOM_ENABLE_MPI"
else
"${VENV_PY}" -c "import axom.sidre as sidre; assert not sidre.AXOM_ENABLE_MPI"
fi

echo "~~~~~~ RUN THE SIDRE PYTHON SUITE VIA PLAIN pytest ~~~~~~"
# Axom's Python tests are named *_Py.py, which pytest's default python_files patterns do not match
TEST_DIR="$(pwd)/src/axom/sidre/tests"
SCRATCH="$(mktemp -d)"
pushd "${SCRATCH}" > /dev/null
"${VENV_PY}" -m pytest -s -p no:cacheprovider \
-o python_files='*_Py.py' \
"${TEST_DIR}"
popd > /dev/null
3 changes: 3 additions & 0 deletions src/axom/sidre/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ if(NANOBIND_FOUND)
"${_axom_py_build_root}/axom/__init__.py" COPYONLY)
axom_configure_file("${_axom_py_pkg_src}/axom/py.typed"
"${_axom_py_build_root}/axom/py.typed" COPYONLY)
axom_configure_file("${_axom_py_pkg_src}/axom/config.py"
"${_axom_py_build_root}/axom/config.py" COPYONLY)
axom_configure_file("${_axom_py_pkg_src}/axom/sidre/__init__.py"
"${_axom_py_build_root}/axom/sidre/__init__.py" COPYONLY)
# Hand-written package stub: re-exports the generated _sidre.pyi statically
Expand Down Expand Up @@ -249,6 +251,7 @@ if(NANOBIND_FOUND)
# Namespace-root package files install once (not per component).
install(FILES "${_axom_py_pkg_src}/axom/__init__.py"
"${_axom_py_pkg_src}/axom/py.typed"
"${_axom_py_pkg_src}/axom/config.py"
DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom")
endif()

Expand Down
Loading