Site Python extras on EESSI¶
Turn a PyPI package or requirements.txt into a PythonBundle easyconfig
that installs on top of EESSI. Already-shipped scientific Python
(numpy via SciPy-bundle, soupsieve via Python-bundle-PyPI) is
satisfied as an exts_list provide. The emitted recipe lists the parent
bundle as a dependency and keeps only the leftover package in exts_list.
Do not pip install --target and prepend PYTHONPATH. That shadows
toolchain-built extensions and has no claim on the EESSI compat layer.
Inputs¶
Warehouse-shaped JSON (
info+urls) or arequirements.txt;the EESSI robot tree (or a fixture that mirrors its provides);
a stack policy that locks
Python,SciPy-bundle, andPython-bundle-PyPIto the versions that EESSI version actually ships. Seeexamples/stacks/eessi-python-extras.toml.
Inspect does not need a robot. Plan does.
Inspect¶
eb-stack package inspect \
--source fixtures/foreign_ingest/pypi_bs4/pypi.json \
--format pypi \
--toolchain-name foss \
--toolchain-version 2026.1 \
--out-dir /tmp/bs4-inspect
package.plan.json has origin = pypi and easyblock = PythonBundle.
Optional extras (extra ==) become residuals and are not solved.
A requirements.txt is also accepted. The first spec is the root
package; later specs are additional run dependencies.
Plan¶
eb-stack package plan \
--source fixtures/foreign_ingest/pypi_bs4/pypi.json \
--format pypi \
--toolchain-name foss \
--toolchain-version 2026.1 \
--easyconfigs /cvmfs/software.eessi.io/versions/2025.06/software/linux/x86_64/amd/zen4/software/EasyBuild/5.3.0/easybuild/easyconfigs \
--stack-policy examples/stacks/eessi-python-extras.toml \
--out-dir /tmp/bs4
The lock collapses soupsieve (when the EESSI Python-bundle-PyPI
provides it) to that bundle. The emitted recipe is a PythonBundle
whose exts_list contains beautifulsoup4 only.
Asking for numpy (or scipy, or torch / PyTorch) against a robot
that already ships the provider produces an empty delta and an
already-provided residual. The leftover package is the only thing
emitted: a recipe that depends on numpy lists SciPy-bundle, not a
pip numpy. If the robot has no SciPy-bundle / PyTorch, the plan
refuses rather than emitting a PythonBundle wheel overlay.
A leftover that is not in EasyBuild yet (eon-akmc) keeps unsatisfied
PyPI run dependencies in the same exts_list. numpy still maps to
SciPy-bundle. A maturin/PyO3 leftover such as readcon is closed from
a Cargo source root (--cargo-source / kind = "cargo") into a
companion PythonPackage (implicit Rust / maturin / binutils)
instead of a pip extension. Pass the crate metadata next to the PyPI
plan; do not pip install the crate into the overlay.
Resolvo picks every robot-provided name. Extra site packages that Warehouse
omits (measured PyYAML for import eon) go in the ingest as a second
Warehouse object in a JSON array, or as a later requirements.txt line.
A registry name (--source eon-akmc) fetches Warehouse once and writes
ingest/pypi/<name>-<version>.json. Replan from that dump; SAT does not
hit the network. Meson wrap natives come from subprojects/*.wrap in
the sdist tree next to the dump (or from build_system.requires). Do not
edit the emitted .eb to add those names: put the modules in
--easyconfigs and let the lock select them. An undeclared import is
a residual, not a silent SAT edge. A PEP 518 build_system
block with build-backend = mesonpy adds the listed build requirements
and emits wrap_mode=default plus the same cargo-on-EESSI isolation
prelude used by Cargo leftovers: drop host rustc wrappers, set
LINKER=${CC:-gcc}, put the EESSI compat ld on PATH, and point
RUSTFLAGS -B at that ld. The cargo target triple and compat arch
come from the build host (rustc -vV, uname -m), not from a
plan-time x8664literal. Hatchling and meson-python already on
Python-bundle-PyPI stay modules; they are not pip extras.
Dependencies that state no version¶
A Python project routinely requires another package with no version. An
exts_list entry still needs one, so planning stops and asks for an index.
On this side the index is a pinned requirements file, which is what
pip freeze writes:
pip freeze > constraints.txt
eb-stack package plan ... --package-index constraints.txt
One name==version per line; comments and blank lines are ignored, and a line
that names a range rather than a version supplies nothing, since a range is not
a version. The same flag takes a CRAN PACKAGES file for the R side; the
format is detected from the content.
Install with the eessi-extend target¶
Plan on the host. Install through campaign run and
examples/targets/eessi-extend.toml (or a site layer that sets
EESSI_SITE_INSTALL=1):
eb-stack campaign run \
--bundle /tmp/bs4 \
--config examples/targets/eessi-extend.toml \
--target eessi-extend-user \
--state /tmp/bs4.campaign.json
The runtime is eessi_container.sh, not the host CVMFS client. The
wrapper scripts/eessi-extend-eb.sh loads EESSI-extend and execs
eb. That claim is eessi-verified.
User one-offs stay as python -m venv --system-site-packages on EESSI
Python. This workflow is for a shared site or project extra.