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
6 changes: 4 additions & 2 deletions .github/workflows/qa-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ jobs:

- name: Install package
env:
SKBUILD_CMAKE_DEFINE: "monoprop_ENABLE_CXX_UNIT_TESTS=OFF;monoprop_ENABLE_MPI=ON"
SKBUILD_CMAKE_DEFINE: "monoprop_ENABLE_CXX_UNIT_TESTS=OFF"
monoprop_ENABLE_MPI: "ON"
run: |
uv sync --no-progress --all-extras -v

Expand Down Expand Up @@ -305,7 +306,8 @@ jobs:

- name: Build (generates compile_commands.json)
env:
SKBUILD_CMAKE_DEFINE: "CMAKE_CXX_COMPILER=clang++;monoprop_ENABLE_CXX_UNIT_TESTS=OFF;monoprop_ENABLE_MPI=ON"
SKBUILD_CMAKE_DEFINE: "CMAKE_CXX_COMPILER=clang++;monoprop_ENABLE_CXX_UNIT_TESTS=OFF"
monoprop_ENABLE_MPI: "ON"
run: |
uv sync --no-progress --all-extras -v

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ We also use [`just`](https://github.com/casey/just) for task automation.

```bash
uv sync --all-groups --all-extras -v # Build & install (workspace-wide)
monoprop_ENABLE_MPI=ON uv sync --all-extras --reinstall-package monoprop --no-cache -v # MPI-enabled build
uv run pytest # Run tests (monoprop's suite + the workspace members' suites)
SKBUILD_CMAKE_BUILD_TYPE=AsanUbsan SKBUILD_CMAKE_DEFINE="monoprop_SANITIZER=asan-ubsan" uv sync --group workspace-test --all-extras --reinstall-package monoprop --no-cache -v # Rebuild when changing sanitizer settings.
LD_PRELOAD="$(g++ -print-file-name=libasan.so):$(g++ -print-file-name=libstdc++.so.6)" ASAN_OPTIONS=detect_leaks=0 uv run pytest # Python tests against a sanitizer tree
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Python bindings (via [uv](https://github.com/astral-sh/uv)):
```bash
uv sync --all-extras -v
# with MPI:
uv sync --all-extras -v --config-settings=cmake.define.monoprop_ENABLE_MPI=ON
monoprop_ENABLE_MPI=ON uv sync --all-extras -v
```

C++ unit-test build:
Expand Down
4 changes: 2 additions & 2 deletions cpp/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ uv sync --all-extras -v
ctest --test-dir build/editable/Release
```

For an MPI-enabled tree, rerun `uv sync` with
`--config-settings=cmake.define.monoprop_ENABLE_MPI=ON`.
For an MPI-enabled tree, rerun `uv sync` with `monoprop_ENABLE_MPI=ON` in the
environment.
## Running Tests

```bash
Expand Down
50 changes: 45 additions & 5 deletions docs/content/docs/building.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mechanism differs by build:

| Build | Enable MPI with |
| --- | --- |
| Python bindings and C++ tests (scikit-build / `uv` / `pip`) | `--config-settings=cmake.define.monoprop_ENABLE_MPI=ON` (or export `SKBUILD_CMAKE_ARGS="-Dmonoprop_ENABLE_MPI=ON"`) |
| Python bindings and C++ tests (scikit-build / `uv` / `pip`) | `monoprop_ENABLE_MPI=ON` in the environment |

The prebuilt wheels published to PyPI (`pip install monoprop`) are also built
without MPI, so a from-source build is required for multi-rank runs.
Expand Down Expand Up @@ -48,19 +48,59 @@ This produces a single-process build with no MPI dependency.

### With MPI

Pass a `config-settings` override to enable MPI:
Set `monoprop_ENABLE_MPI=ON` in the environment:

```bash
monoprop_ENABLE_MPI=ON uv sync --all-extras -v
```

It works the same with `pip` when installing from a checkout:

```bash
monoprop_ENABLE_MPI=ON pip install .
```

That one variable does two things, matched by the
`[[tool.scikit-build.overrides]]` block in `pyproject.toml`: it sets the
`monoprop_ENABLE_MPI` CMake option, and it adds `mpi4py` to the build
requirements. mpi4py is a build-time input β€” the bindings need its headers to
hand an MPI communicator across the nanobind boundary β€” but only for MPI
builds, so it is injected per-build through PEP 517's
`get_requires_for_build_wheel` rather than sitting in `build-system.requires`
where every build would pay for it.

<Callout type="warn">
Because it is an environment variable, it does not participate in uv's build
cache key. Add `--reinstall-package monoprop --no-cache` when flipping MPI on
or off in an existing environment, or uv may reuse the previous build.
</Callout>

#### Driving it with config-settings instead

A scikit-build-core override can only match environment variables, never
config-settings, so `cmake.define` alone will not pull in mpi4py β€” the build
would fail at configure time with a message telling you as much. Pass both
settings to use this route:

```bash
uv sync --all-extras -v \
--config-settings=cmake.define.monoprop_ENABLE_MPI=ON
--config-settings=cmake.define.monoprop_ENABLE_MPI=ON \
--config-settings=build.requires=mpi4py>=4.1.0
```

The same override works with `pip` when installing from a checkout:
In a workspace, prefer the per-package form so the settings do not reach the
sibling distributions in `packages/`:

```bash
pip install . --config-settings=cmake.define.monoprop_ENABLE_MPI=ON
uv sync --all-extras -v \
--config-settings-package="monoprop:cmake.define.monoprop_ENABLE_MPI=ON" \
--config-settings-package="monoprop:build.requires=mpi4py>=4.1.0"
```

The cost of this route is that the mpi4py pin is repeated on the command line
instead of living once in `pyproject.toml`; the environment switch is preferred
for that reason.

### Verify the install

```bash
Expand Down
12 changes: 8 additions & 4 deletions docs/content/docs/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ just test-mpi-matrix # MPI-marked tests across a rank matrix

To do it by hand, build with MPI on, then run under `mpiexec` with `--no-sync`
so each rank reuses that build. `--reinstall-package` and `--no-cache` force a
genuine rebuild, since uv does not key its build cache on `SKBUILD_CMAKE_ARGS`:
genuine rebuild, since uv does not key its build cache on the environment:

```bash
SKBUILD_CMAKE_ARGS="-Dmonoprop_ENABLE_MPI=ON" \
monoprop_ENABLE_MPI=ON \
uv sync --all-extras --reinstall-package monoprop --no-cache
mpiexec --allow-run-as-root -n 2 uv run --no-sync python -m pytest tests --with-mpi
```

`monoprop_ENABLE_MPI` also provisions mpi4py as a build input; see
[Building from source](/building#with-mpi) for why, and for the
config-settings equivalent.

### C++ unit tests

The C++ tests run through CTest. The build tree is produced by `uv sync` (via
Expand All @@ -65,7 +69,7 @@ With MPI, reuse the MPI-enabled `uv sync` from the Python MPI section above,
then run CTest against the same tree:

```bash
SKBUILD_CMAKE_ARGS="-Dmonoprop_ENABLE_MPI=ON" \
monoprop_ENABLE_MPI=ON \
uv sync --all-extras --reinstall-package monoprop --no-cache
ctest --test-dir build/editable/Release --output-on-failure
```
Expand All @@ -89,7 +93,7 @@ CTest runs each Boost case as its own process, so an MPI build's `MPI_Init` prob
fabric device per case, whether or not the test sends anything. `monoprop_TEST_EXCLUDE_MPI_FABRIC=ON`
(default) skips that probe for the single-process `serial` variants only; multi-rank variants
keep the full component set, since they exchange real messages. Turn it off by adding
`-Dmonoprop_TEST_EXCLUDE_MPI_FABRIC=OFF` to the `SKBUILD_CMAKE_ARGS` MPI build above.
`SKBUILD_CMAKE_DEFINE="monoprop_TEST_EXCLUDE_MPI_FABRIC=OFF"` to the MPI build above.

CTest registers every Boost case individually as a `serial` variant. When the
build has MPI enabled and a launcher is found, it also registers the whole suite
Expand Down
7 changes: 4 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ test:
# Pass RANKS as either a single integer or a semicolon-separated list (e.g. "1;2;4").

test-mpi RANKS='':
uv sync --all-extras --group workspace-test --reinstall-package monoprop --no-cache --config-settings-package="monoprop:cmake.define.monoprop_ENABLE_MPI=ON" -v
monoprop_ENABLE_MPI=ON \
uv sync --all-extras --group workspace-test --reinstall-package monoprop --no-cache -v
export OMPI_MCA_rmaps_base_oversubscribe="1"; \
ranks="${1:-${monoprop_MPI_TEST_PROCS:-2}}"; \
for r in ${ranks//;/ }; \
Expand Down Expand Up @@ -149,8 +150,8 @@ bench-mpi LABEL RANKS *MPIARGS:

# Rebuild monoprop with MPI enabled (editable). Run once before `just bench-mpi`.
bench-build-mpi:
uv sync --all-extras --group bench --reinstall-package monoprop --no-cache \
--config-settings-package="monoprop:cmake.define.monoprop_ENABLE_MPI=ON" -v
monoprop_ENABLE_MPI=ON \
uv sync --all-extras --group bench --reinstall-package monoprop --no-cache -v

# Quick sanity run: tiny sizes, skip the slow static benchmarks.
bench-smoke:
Expand Down
18 changes: 14 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[build-system]
requires = [
"mpi4py>=4.1.0",
"nanobind-backend>=1.0.0",
"nanobind==3.0.0",
"scikit-build-core>=1.0.3,<2",
Expand Down Expand Up @@ -149,11 +148,22 @@ sdist.exclude = [
".prettierignore",
"cspell.json",
]
sdist.include = ["src/monoprop/_version.py", "src/monoprop/_dispatch.py", ".github/license-header.txt"]
sdist.include = [
"src/monoprop/_version.py",
"src/monoprop/_dispatch.py",
".github/license-header.txt",
]
build.verbose = true
build.tool-args = ["-j2"]
# Default to a single build job for Python bindings; CI and one-off builds can
# still override this via config-settings or environment variables.
# `monoprop_ENABLE_MPI` in the environment is the single switch: it both provisions the
# build input and flips the CMake option.
# NOTE: An override's `if.` clause can only match environment variables, never config-settings, so the
# `--config-settings=cmake.define.monoprop_ENABLE_MPI=ON` route cannot reach this table
# and has to carry `--config-settings=build.requires=mpi4py>=4.1.0` alongside it.
[[tool.scikit-build.overrides]]
if.env.monoprop_ENABLE_MPI = true
build.requires = ["mpi4py>=4.1.0"]
cmake.define.monoprop_ENABLE_MPI = "ON"


[tool.setuptools_scm]
Expand Down
18 changes: 17 additions & 1 deletion src/monoprop/bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
if(monoprop_ENABLE_MPI)
include(${PROJECT_SOURCE_DIR}/cmake/custom/FindPythonModule.cmake)
find_python_module(mpi4py REQUIRED)
find_python_module(mpi4py QUIET)

if(NOT mpi4py_FOUND)
message(
FATAL_ERROR
"MPI is enabled but mpi4py is not importable from ${Python_EXECUTABLE}. "
"mpi4py is a build-time input of the bindings: its headers hand the MPI "
"communicator across the nanobind boundary.\n"
"Enable MPI with the environment switch, which provisions it automatically:\n"
" monoprop_ENABLE_MPI=ON uv sync --all-extras\n"
"If you drive the build with config-settings, request it explicitly too, as "
"config-settings cannot trigger a scikit-build-core override:\n"
" uv sync --all-extras \\\n"
" --config-settings=cmake.define.monoprop_ENABLE_MPI=ON \\\n"
" --config-settings=build.requires=mpi4py>=4.1.0"
)
endif()

# we also need the include directories for mpi4py
if(mpi4py_FOUND)
Expand Down
Loading