Site R extras on EESSI¶
Turn a CRAN DESCRIPTION, CRAN JSON object, or package list into a
Bundle easyconfig with exts_defaultclass = 'RPackage'. Base-R
packages (methods, utils, …) are dropped. R (>= x) becomes a
dependency on the EasyBuild R module. Packages already listed in
R-bundle-CRAN exts_list are satisfied as provides.
Inputs¶
a DESCRIPTION file,
cran.json, or a one-spec-per-line package list;the EESSI robot tree;
a stack policy that locks
RandR-bundle-CRANto the versions that EESSI version ships. Seeexamples/stacks/eessi-r-extras.toml.
Inspect¶
eb-stack package inspect \
--source fixtures/foreign_ingest/cran_jsonlite/DESCRIPTION \
--format cran \
--toolchain-name foss \
--toolchain-version 2026.1 \
--out-dir /tmp/jsonlite-inspect
package.plan.json has origin = cran and easyblock = Bundle.
Plan¶
eb-stack package plan \
--source fixtures/foreign_ingest/cran_jsonlite/cran.json \
--format cran \
--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-r-extras.toml \
--source-checksum <sha256-of-the-tarball> \
--out-dir /tmp/jsonlite
DESCRIPTION files rarely carry a SHA-256. Pass --source-checksum for
the tarball, or use a JSON document that already includes one. Plan
refuses to emit a buildable bundle without it.
Install with the eessi-extend target¶
Same as the Python extras workflow: plan on the host, then
eb-stack campaign run \
--bundle /tmp/jsonlite \
--config examples/targets/eessi-extend.toml \
--target eessi-extend-user \
--state /tmp/jsonlite.campaign.json
Project one-offs can keep using R_LIBS_USER instead of a site bundle.
Dependencies that state no version¶
A CRAN package routinely imports another by bare name, with no version at all.
Nothing is wrong with that, but an exts_list entry needs one concrete
version, so planning stops with
matrixStats is not in the robot, and ">=0" names no version to install as an
extension. Give the ecosystem index with --package-index, or pin the dependency
Give it the index CRAN publishes:
curl -sSLO https://cran.r-project.org/src/contrib/PACKAGES
eb-stack package plan \
--source brms/DESCRIPTION \
--format cran \
--toolchain-name foss --toolchain-version 2025b \
--easyconfigs "$EBROOTEASYBUILD/easybuild/easyconfigs" \
--stack-policy examples/stacks/eessi-r-extras.toml \
--package-index PACKAGES \
--source-checksum <sha256 of the package tarball> \
--out-dir /tmp/brms
The index is a control file: stanzas of Field: value separated by blank
lines. Versions for unpinned leftovers come from it, and so do their checksums,
since CRAN publishes an MD5sum per package and EasyBuild accepts a tagged
md5: value. It is a file, not a network call, so a plan stays reproducible
and works offline against a copy taken at a known time.
Without an index the error above is the right outcome: installing whatever version happens to be current, unrecorded, is worse than stopping.
When this emits a Bundle¶
A CRAN package whose imports the robot already carries emits one RPackage
recipe. One whose imports it does not carry emits a Bundle with
exts_defaultclass = 'RPackage' and the leftovers in exts_list, the same
shape upstream uses for R-bundle-CRAN:
easyblock = 'Bundle'
exts_defaultclass = 'RPackage'
exts_list = [
('processx', '3.8.4', {...}),
]
The choice is made from the plan rather than by a flag: leftovers exist or they
do not. Both shapes are pinned by tests,
plan_cran_emits_a_single_r_package and
plan_cran_with_leftovers_emits_an_r_bundle.
An already-provided import collapses to its provider instead of becoming an
extension, through the same mechanism the Python side uses: an R-bundle-CRAN
in the robot tree satisfies a requirement for one of its exts_list entries.