A reproducible build of the LFRic Apps Spack environment for the
Isambard 3 supercomputer (GCC 14.3, Grace/aarch64). It turns a set of pinned
source repositories into a ready-to-use environment you load with one module
command — giving you rose, cylc, psyclone, xios and the full LFRic
dependency stack, without you having to drive Spack yourself.
You do not need to know Spack or pixi to use this repo.
The steps below use plain git, module and sbatch. A pixi shortcut is
offered separately at the end for those who want it.
One prerequisite build (Stage 1), then examples you run on the built env. The examples are siblings, not later "stages" — each depends only on Stage 1.
Stage 1 — BUILD the environment (run once; heavy; on a compute node)
pinned sources ─▶ Spack builds everything ─▶ a loadable module under $LFRIC_PREFIX
Example: minimal-compile — USE the env to compile a target (lightweight)
module load lfric-env/<version>/<variant> ─▶ rose / cylc / psyclone / spack …
└▶ compile lfric_atm (examples/minimal-compile/)
Example: science-suites — RUN a real suite (cylc on the login node ─▶ Slurm)
cylc vip a Rose/Cylc LFRic suite on the built env (examples/science-suites/)
- Stage 1 is the reproducible core of this repo — the one true prerequisite. It
needs the repo + a Python in [3.7, 3.12). It produces a self-contained Lmod
modulefile under
$LFRIC_PREFIX. (We keep the name "Stage 1"; the examples below build on it rather than following it as stages.) - The minimal-compile example (
examples/minimal-compile/) is the smallest thing you do with the built env: compile thelfric_atmtarget, no science run. It needs only the modulefile — no Spack, no pixi, and the repo can even have moved or been deleted. Adapt it for your own science target. - The science-suite examples (
examples/science-suites/) run real Rose/Cylc suites on the built env the way scientists do —cylcschedules the suite and submits to Slurm; each suite declares its LFRic source refs independencies.yamland compiles its ownlfric_atm(e.g.pixi run run-suite u-dr932, on the defaultcrayenv). Each suite is the upstream tree itself plus a patch, so what we changed for Isambard 3 is a reviewable diff — but they live in two different places, so getting one differs: u-dr932 is a pinned submodule (pixi run init-suitesfirst), while u-dn704 and u-dt000 are Met Office rose suites in MOSRS subversion that you check out yourself withrosie checkout(needs a MOSRS account).run-suite.shprints the exact command if the checkout is missing.
There are two dependency variants, chosen with LFRIC_STACK:
LFRIC_STACK |
MPI + parallel I/O | Use for |
|---|---|---|
cray (default) |
system cray-mpich + Cray parallel HDF5/netCDF | production runs on Isambard |
spack |
mpich + HDF5/netCDF built from source | portability / CI / comparison |
So there are four things you can build: {the env build (Stage 1), the minimal-compile
example} × {cray, spack}.
- An Isambard 3 account, and the basics of using it: the difference between a
login node (where you clone + submit jobs) and a compute node (where the
heavy build runs, via
sbatch). - GitHub access for one private submodule. The sources are pulled in as git
submodules (a submodule is just another git repo nested inside this one, pinned
to an exact commit). All six LFRic source repos —
lfric_apps,lfric_core,casim,jules,socrates,ukca— are public and clone anonymously over HTTPS, so no credentials are needed for them. The one exception isvendor/mo-spack-packages, which is still a private Met Office repo used by the Stage-1 build: it is on agit@github.com:URL and needs an SSH key registered with GitHub and authorised for theMetOfficeorganisation's SSO (GitHub → Settings → SSH keys → Configure SSO). If asubmodule updatefails, it is almost always that one.
Run this session from a login node. The two git submodule lines fetch only
the Stage‑1 (core) sources; the heavy build itself runs on a compute node.
# 1. Clone the repo and fetch the Stage-1 (core) submodules.
git clone <repo-url> lfric-env-isambard
cd lfric-env-isambard
git submodule update --init --recursive --jobs 4 -- \
vendor/spack vendor/spack-packages vendor/lfric_apps vendor/lfric_core vendor/mo-spack-packages
# 2. Build on a compute node. The config block at the top of scripts/build.sbatch
# sets WHERE to build (edit it if your paths differ) — see "Configuration".
sbatch scripts/build.sbatch # cray variant (default)
sbatch --export=ALL,LFRIC_STACK=spack scripts/build.sbatch # spack variantThe job writes its log to logs/build-<jobid>.out; a successful run ends with
BUILD_OK. From scratch the build takes roughly 1–3 hours; re-runs are incremental
(Spack skips already-built packages). The two variants share one install tree,
so building the second one only rebuilds the MPI-dependent part.
Everything installs under a versioned prefix $LFRIC_PREFIX/<version> (default
base $PROJECTDIR/$USER/opt/Linux-aarch64, version read from the repo's VERSION
file, e.g. v2026.07.21), which is outside the repo — see
Configuration. The version keeps independent builds in distinct
trees, so a rebuild never silently overwrites an environment others are loading.
Why a compute node? The login nodes cap the number of processes per user, so a full parallel build fails there with
fork: Resource temporarily unavailable.sbatchruns it on a Grace compute node with enough cores and memory.
Spack downloads each package's source the first time it builds it — including a
git clone of XIOS from gitlab.in2p3.fr. To do all the network + disk I/O up
front (and make the compute-node build robust to an intermittent source-host
outage), pre-fetch everything on the login node first:
bash scripts/fetch.sh # cray variant (default)
LFRIC_STACK=spack bash scripts/fetch.sh # spack variantThis clones any missing Stage-1 submodules, concretizes the variant, then
downloads every source into the shared cache under $LFRIC_PREFIX. The subsequent
sbatch build reuses that cache and fetches nothing. Like the build, it needs a
Python in [3.7, 3.12) (module load cray-python/3.11.7, or use pixi run fetch).
Concurrency is capped for the login node's process limit (FETCH_JOBS, default 4).
Once Stage 1 has finished, load the environment in any shell — no pixi, no Spack:
# Point at the base you built into (the default is shown):
export LFRIC_PREFIX="$PROJECTDIR/$USER/opt/$(uname -sm | tr ' ' -)"
module use "$LFRIC_PREFIX/modulefiles"
module avail lfric-env # list every built version × variant
module load lfric-env/v2026.07.21/cray # or: .../v2026.07.21/spack
rose --version; cylc --version; psyclone --versionThe modulefiles live in ONE shared tree ($LFRIC_PREFIX/modulefiles) keyed by
lfric-env/<version>/<variant>, so module avail lfric-env shows every build —
pick the version you want. Expected (exact versions track the pinned sources):
rose 2.4.2
cylc 8.4.2
PSyclone version: 3.3.1
The modulefile carries absolute paths, so this keeps working even if the repo
moves or is deleted. Loading one variant/version swaps out the other; bare
module load lfric-env resolves to the most-recently-built version's cray, and
module load lfric-env/<version> to that version's cray.
This writes a run directory + an isambard3 Slurm platform into ~/.cylc. It is
opt-in (building the environment never touches your home directory):
bash scripts/setup-cylc.shA worked example of building a science target on the environment. It needs the Stage‑2 physics submodules (private Met Office repos — same SSH access as above):
git submodule update --init --jobs 4 -- \
vendor/physics/casim vendor/physics/jules vendor/physics/socrates vendor/physics/ukca
sbatch examples/minimal-compile/build.sbatch # cray
sbatch --export=ALL,LFRIC_STACK=spack examples/minimal-compile/build.sbatch # spackBuild one variant at a time — both compile in the same lfric_apps working tree, so don't run the two
lfric-atmjobs concurrently. A successful run ends withLFRIC_ATM_OK.
A successful run ends with LFRIC_ATM_OK. See
examples/minimal-compile/README.md for how to adapt it
for your own suite.
Already have your own LFRic Rose/Cylc suite? You do not need anything under
examples/ to run it against this environment — those directories are integration
tests and adaptation templates, not a required layer. Point your own suite at the
built environment the way you would at any prebuilt toolchain: load the module,
then let your suite inherit the toolchain from it.
-
Build Stage 1 once (above) and
module load lfric-env/<version>/<variant>wherever your suite activates its environment — for a Rose/Cylc suite that is a taskenv-script/pre-script, or anACTIVATE_ENV-style script the tasks source. -
Inherit the compiler — don't hard-code it. That single
module loadalready exports the whole toolchain for the variant you loaded; you configure none of it:The module sets on crayon spackFC,LDMPIftnthe view's mpif90CXXCCthe view's mpic++Cray PE modules PrgEnv-gnu+ Cray HDF5/netCDF loaded(none — self-contained) FFLAGS-I<view>/include(prepended)same LDFLAGS-L<view>/lib{,64}+-rpath+ shumlib (prepended)same So in your suite's environment, refer to those instead of naming a literal compiler:
# flow.cylc [[[environment]]] / rose-app.conf [env] — inherit from the module FC = $FC LDMPI = $LDMPI # CXX likewise if your suite sets it; otherwise the module's value is used as-is
The one thing to watch: if your suite currently hard-codes a compiler — the upstream Met Office EX suites ship
FC = mpif90/LDMPI = mpif90— that literal overrides the module'sftnand breaks the build on thecrayenv. Change it to$FC/$LDMPI. If your suite doesn't setFCat all, there is nothing to do: it already inherits the module's. -
You don't wire include/lib paths by hand. The module puts the view's headers on
FFLAGSand its libraries (plus shumlib) onLDFLAGS, prepended to any existing value. As long as your build chain appends its own flags rather than overwriting these — LFRic's Makefiles do — XIOS/HDF5/netCDF/shumlib are found with no extra-I/-Lfrom you.
That is the entire contract: module load + inherit FC/LDMPI. Everything the
Stage-1 build knows about the Cray toolchain lives in the modulefile, so your suite
stays decoupled from how the environment was built. The science-suite examples under
examples/science-suites/ are exactly this pattern wired
into real suites — site/activate-env.sh (a thin module-load activator) and each
u-*/flow.cylc (FC = $FC) — so copy from them if it helps.
pixi is only a convenience for Stage 1: it supplies the
Python that runs Spack and gives you task shortcuts, and it auto-loads the built
module on every pixi run. Nothing below is required — each task just wraps the
script the no-pixi sections above already use.
pixi run submodule-init # = the Stage-1 `git submodule update` above
pixi run fetch # = scripts/fetch.sh (cray) — pre-fetch sources on a login node
pixi run fetch-spack # = scripts/fetch.sh (spack)
pixi run concretize # = scripts/concretize.sh (cray) — solve only (cheap login-node check)
pixi run concretize-spack # = scripts/concretize.sh (spack)
pixi run build # = scripts/build.sh (cray) — run on a compute node
pixi run build-spack # = scripts/build.sh (spack)
pixi run activate # report rose / cylc / psyclone versions
# minimal-compile example:
pixi run init-physics # = the physics `git submodule update` above
pixi run build-lfric-atm # = examples/minimal-compile/build.sh
pixi run setup-cylc # = scripts/setup-cylc.shThe heavy build still needs a compute node: either submit scripts/build.sbatch
(its last line shows how to switch it to exec pixi run build), or use pixi run concretize interactively for a quick solve-only check before submitting.
Inside pixi you can skip the explicit module load: after a build, every
pixi run … / pixi shell auto-loads the LFRIC_STACK variant, so
pixi run rose --version / pixi run spack find work directly.
The build is configured entirely through a few environment variables. The sbatch scripts set them explicitly in a config block at the top — read or edit that block to see/change exactly where things go.
| Variable | Default | What it controls |
|---|---|---|
LFRIC_STACK |
cray |
Dependency variant: cray or spack. |
LFRIC_ENV_VERSION |
contents of ./VERSION (e.g. v2026.07.21) |
Environment version (CalVer). Selects the versioned install prefix $LFRIC_PREFIX/<version> and the module name lfric-env/<version>/<variant>. Read from the committed VERSION file; bump it with bash scripts/bump-env-version.sh (pixi run bump-env-version). Distinct from any LFRic apps/core version. |
LFRIC_PREFIX |
$PROJECTDIR/$USER/opt/<arch> |
Base install location (the per-arch container, shared across versions). The actual install goes into the versioned prefix $LFRIC_PREFIX/$LFRIC_ENV_VERSION: the Spack install tree, the per-variant environment + view. The shared modulefiles tree ($LFRIC_PREFIX/modulefiles) and the source/misc download caches sit at this base and are version-independent. Outside the repo. |
LFRIC_WORKING_DIR |
$LFRIC_PREFIX/<version>/stage |
Transient Spack build/compile scratch. On a compute node the sbatch points this at node‑local NVMe ($LOCALDIR/…) so the build stays off the shared Lustre. Safe to delete anytime. |
SPACK_JOBS |
$SLURM_CPUS_PER_TASK |
Parallel build jobs (Stage 1). |
MAKE_JOBS |
$SLURM_CPUS_PER_TASK |
Parallel make jobs (minimal-compile example). |
FETCH_JOBS |
4 |
Concurrency cap for the optional login-node pre-fetch (scripts/fetch.sh); kept small for the login node's process limit. |
The versioned prefix is what makes the minimal-compile example repo-independent: the build records absolute
paths into it, so once built you can move or delete the repo and module load
still works. To publish a rebuilt environment without disturbing the one already in
use, pixi run bump-env-version (or bash scripts/bump-env-version.sh), commit
VERSION, then rebuild — the new build lands in a fresh $LFRIC_PREFIX/<version>
and shows up alongside the old one in module avail lfric-env.
There is no clean task — removal is a plain rm. To remove one built version,
delete its versioned prefix; to remove all versions, delete the base:
rm -rf "$LFRIC_PREFIX/$(cat VERSION)" # just this version's install tree + env
rm -rf "$LFRIC_PREFIX" # ALL versions + the shared modulefiles/cachesThe transient stage ($LFRIC_WORKING_DIR, on node-local disk) is disposable and
generally cleared with the node; delete it directly if you want it gone sooner.
submodule updatefails / "Permission denied (publickey)". This isvendor/mo-spack-packages, the one remaining private submodule: your SSH key is not authorised for Met Office SSO (see Prerequisites). The six LFRic source submodules are public and clone anonymously over HTTPS.fork: Resource temporarily unavailableduring a build. You are building on a login node — submitscripts/build.sbatchto a compute node instead.Killed signal terminated program cc1plus(out of memory). Give the job more memory; the sbatch scripts already request a node's full per-core share. See the memory note inMAINTAINER.md.Unable to clone XIOS …/ a source download fails mid-build. Usually a transient source-host blip. Re-running resumes from the cache; to avoid it entirely, pre-fetch on the login node first (see Optional: pre-fetch the sources).
MAINTAINER.md— how it works inside, and how to maintain it (variants, patches, bumping pinned versions, the modulefile, tuning).examples/minimal-compile/README.md— the minimal-compile example and how to adapt it.examples/science-suites/README.md— the science-suite examples: running real Rose/Cylc LFRic suites on the built env.CLAUDE.md— orientation for AI coding agents working in this repo.