Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/ci_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ jobs:
--test llms_txt \
--test mcp_registry \
--test mcp_stdio \
--test module_tree_dump_script \
--test oom_profile \
--test optimisation_criteria \
--test pr_merge_order \
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ All notable changes to this project are documented here.

### Added

- `stack provided` includes the one-based build-list line on every
`provided` and `unparsed` finding in its text report, as it already did for
hook, toolchain, family-lock, and licensed findings.
- `scripts/dump-module-trees.sh` captures the live site, older EESSI, and
target EESSI `module -t avail` inventories in isolated, purged shells for
`stack gap`.
- `stack gap` writes `version-change.tsv`, one row per version in the site
and older-EESSI inventories. A module in the target is `moved` and names
its EESSI version; an excluded module is `site-only`; every other missing
route is `gone` until it lands.
- `stack shard` splits one Jenkins build list into shards that can
build at the same time. A line's set is its closure over
`--easyconfigs` minus the modules in `--installed-modules-dir` Lmod
Expand Down
195 changes: 195 additions & 0 deletions artifacts/REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# U3–U5 delivery report

Base: `da07561a0c430957c2d014f31855bbe973064197`

Feature tip: `682ea65` (`cursor/stack-gap-lint-5553`)

## Baseline coverage

The requested baseline already contained the core U3 and U4 commands:

- `stack provided` already rejected names from an installed listing or
baseline lock and rejected per-line `--hooks`/`--hook`.
- `stack gap` already read live module trees and dumps, accepted both
open-pull-request inputs, and applied the ns-006 route priority.

Following the brief's skip rule, this series does not duplicate those
implementations. It closes the remaining differences: default-text line
numbers for every U3 error, the U4 dump helper and exact one-row-per-route
fixture, and all of U5.

## Commits

1. `25f6010 fix(stack): report build-list line numbers`
- Reuses `shard::parse_lines` for source line identity and
`jenkins::parse_build_list` for filenames/flags.
- Adds one-based line numbers to `provided` and `unparsed` text findings,
matching the existing hook/toolchain diagnostics.
2. `57757bd feat(stack): capture live module inventories`
- Adds `scripts/dump-module-trees.sh`, which captures the site, older
EESSI, and target EESSI trees in separately purged shells.
- Adds an executable fixture test and a seven-name fixture with exactly
one row in each route TSV.
3. `1342ae9 feat(stack): write module version changes`
- Preserves versions from `module -t avail`, spider-style text, and Lmod
trees.
- Writes `version-change.tsv`: `in-stack` becomes `moved`, `excluded`
becomes `site-only`, and every other missing route is `gone` until it
lands.
4. `02aec42 docs(stack): refresh module gap index`
- Regenerates the checked-in long-form documentation index.
5. `3ada26f docs(stack): use configured upstream trees`
- Makes the pull-request inventory walkthrough use the configured pinned
checkout variables.
6. `55b5a73 ci(stack): run module dump helper test`
- Adds the helper test to the explicit integration-test CI slice.
7. `682ea65 docs(stack): fix shard API literal`
- Fixes a pre-existing malformed generated RST literal so the required
Sphinx warnings-as-errors build is clean.

## Minimal working examples

### U3: build-list lint

Command:

```sh
eb-stack stack provided \
--build-list tests/provided_fixtures/site.list \
--installed tests/provided_fixtures/installed.txt \
--baseline-lock tests/provided_fixtures/baseline.lock.json
```

Before (`da07561a`, exit 1), baseline and unparsed findings had no source
line:

```text
provided GROMACS-2026.0-foss-2026.1.eb name=GROMACS list=tests/provided_fixtures/site.list
unparsed not-a-version.eb list=tests/provided_fixtures/site.list
hooks FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list line=4 sets=--hooks=/sw/hooks/site.py; which replaces EESSI's hooks
```

After (exit 1), every failing line is located:

```text
provided GROMACS-2026.0-foss-2026.1.eb name=GROMACS list=tests/provided_fixtures/site.list line=3
unparsed not-a-version.eb list=tests/provided_fixtures/site.list line=7
hooks FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list line=4 sets=--hooks=/sw/hooks/site.py; which replaces EESSI's hooks
```

### U4: live dumps and one row per route

Portable helper test:

```sh
cargo test --locked --test module_tree_dump_script
```

Before:

```text
error: no test target named `module_tree_dump_script`
exit=101
```

After:

```text
running 1 test
test helper_dumps_site_older_and_target_module_trees ... ok
test result: ok. 1 passed; 0 failed
```

The site command documented for a real initialized Lmod shell is:

```sh
scripts/dump-module-trees.sh \
/tmp/module-dumps - EESSI/2025.06 EESSI/2026.06
```

The exact route fixture:

```sh
eb-stack stack gap \
--site tests/gap_routes/one-per-route.txt \
--installed tests/gap_routes/installed.txt \
--easyconfigs tests/gap_routes/easyconfigs \
--generation 2026.1 \
--exclude-file tests/gap_routes/site-only.tsv \
--filter-deps zlib \
--open-layer tests/gap_routes/open-layer.json \
--open-easyconfigs tests/gap_routes/open-easyconfigs.json \
--out-dir /tmp/gap-one-per-route
```

Output:

```text
site=7 in-stack=1 filtered=1 open-layer=1 recipe=1 open-easyconfigs=1 no-recipe=1 excluded=1 out=/tmp/gap-one-per-route
```

### U5: version-change table

Run the live-tree fixture from `docs/orgmode/howto/module-gap.org`, using
`/tmp/gap-mwe` as `--out-dir`, then inspect
`/tmp/gap-mwe/version-change.tsv`.

Before:

```text
site=7 in-stack=2 filtered=1 open-layer=1 recipe=0 open-easyconfigs=0 no-recipe=1 excluded=2 out=/tmp/gap-mwe
version-change.tsv missing
```

After:

```text
name old_version eessi_version status
CUDA 12.6.0 12.6.0 moved
FFTW 3.3.10 3.3.10 moved
LAMMPS 2023.0 - gone
LAMMPS 2024.0 - gone
MATLAB 2024b - site-only
QuantumESPRESSO 7.3 - gone
binutils 2.42 - gone
code-server 4.96.2 - site-only
```

## Verification

All final gates passed with Rust stable 1.99.1 (the checkout's default Rust
1.83 is below the declared Rust 1.88 minimum):

```sh
cargo fmt --all --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked --all-targets
cargo package --locked
uvx prek run -a
pixi run -e docs docbld
scripts/check-doc-links.sh docs/build
python3 scripts/gen-llms-txt.py --check
```

The Sphinx build completed with `build succeeded.` under `-W --keep-going`.
All eight prek hooks passed.

## Reproduction

```sh
git clone /path/to/eb-stack eb-stack-u3-u5
cd eb-stack-u3-u5
git fetch artifacts/eb-stack-u3-u5.bundle \
cursor/stack-gap-lint-5553:u3-u5
git switch u3-u5
```

Alternatively, apply `artifacts/patches/*.patch` in numeric order to
`da07561a`.

## Left undone

No requested code or fixture work remains. The cloud runner has no site Lmod
service, so the helper was verified with a deterministic `module` fixture
rather than a production site tree. No upstream EasyBuild or EESSI repository
was mutated.
Binary file added artifacts/eb-stack-u3-u5.bundle
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
From 25f60108093be02fcc6a5ade9431ab5c080f5914 Mon Sep 17 00:00:00 2001
From: Rohit Goswami <rgoswami@ieee.org>
Date: Tue, 3 Nov 2026 09:11:43 +0000
Subject: [PATCH 1/7] fix(stack): report build-list line numbers

---
CHANGELOG.md | 3 +++
docs/orgmode/howto/build-list-provided.org | 8 ++++----
docs/orgmode/reference/cli.org | 2 +-
src/provided.rs | 24 +++++++++++-----------
tests/stack_provided.rs | 19 +++++++++++++----
5 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed98bc6..3866917 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ All notable changes to this project are documented here.

### Added

+- `stack provided` includes the one-based build-list line on every
+ `provided` and `unparsed` finding in its text report, as it already did for
+ hook, toolchain, family-lock, and licensed findings.
- `stack shard` splits one Jenkins build list into shards that can
build at the same time. A line's set is its closure over
`--easyconfigs` minus the modules in `--installed-modules-dir` Lmod
diff --git a/docs/orgmode/howto/build-list-provided.org b/docs/orgmode/howto/build-list-provided.org
index 77c1d99..b0cc7d6 100644
--- a/docs/orgmode/howto/build-list-provided.org
+++ b/docs/orgmode/howto/build-list-provided.org
@@ -29,10 +29,10 @@ eb-stack stack provided \
That command printed:

#+begin_example
-provided GROMACS-2026.0-foss-2026.1.eb name=GROMACS list=tests/provided_fixtures/site.list
-provided FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list
-provided fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/Python-3.12.3-foss-2025b.eb name=Python list=tests/provided_fixtures/site.list
-unparsed not-a-version.eb list=tests/provided_fixtures/site.list
+provided GROMACS-2026.0-foss-2026.1.eb name=GROMACS list=tests/provided_fixtures/site.list line=3
+provided FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list line=4
+provided fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/Python-3.12.3-foss-2025b.eb name=Python list=tests/provided_fixtures/site.list line=5
+unparsed not-a-version.eb list=tests/provided_fixtures/site.list line=7
toolchain fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/Python-3.12.3-foss-2025b.eb name=Python list=tests/provided_fixtures/site.list line=5 uses=foss-2025b; a site-layer line on EESSI 2026.06 must use a 2026.1 toolchain or GCCcore-15.2.0 below it (GCCcore-15.2.0, GCC-15.2.0, gfbf-2026.1, gompi-2026.1, foss-2026.1)
hooks FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list line=4 sets=--hooks=/sw/hooks/site.py; which replaces EESSI's hooks
hits=3 unparsed=1 allowed=0 provided=3 toolchain=1 hooks=1 family=0 licensed=0
diff --git a/docs/orgmode/reference/cli.org b/docs/orgmode/reference/cli.org
index 055fe02..d172d48 100644
--- a/docs/orgmode/reference/cli.org
+++ b/docs/orgmode/reference/cli.org
@@ -694,7 +694,7 @@ path, not left as site layer. ~--licensed~ and ~--licensed-file~ warn
for group-restricted names; ~data/site-licensed.txt~ is that list for
VASP, MATLAB, ANSYS, ORCA, and Gurobi. With neither flag there is no
warning. ~--strict~ makes a remaining warning exit 1. Stdout lists each
-hit as ~provided <file> name=<Name> list=<path>~ and ends with
+hit as ~provided <file> name=<Name> list=<path> line=<N>~ and ends with
~hits= unparsed= allowed= provided= toolchain= hooks= family= licensed=~.
~--format~ is ~text~ by default, ~github~ for workflow commands, or
~json~. ~--json~ is the same as ~--format json~. See [[file:../howto/build-list-provided.org][fail a build list that names software the baseline provides]]
diff --git a/src/provided.rs b/src/provided.rs
index 816d5e5..1ea83b8 100644
--- a/src/provided.rs
+++ b/src/provided.rs
@@ -19,6 +19,7 @@ use crate::easystack::filename_toolchain;
use crate::gap::{names_from_listing, software_name};
use crate::hierarchy::known_hierarchy;
use crate::jenkins::parse_build_list;
+use crate::shard::parse_lines;
use anyhow::{Context, Result};
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
@@ -437,16 +438,12 @@ struct Entry {
}

fn entries(text: &str) -> Vec<Entry> {
- text.lines()
- .enumerate()
- .filter_map(|(index, raw)| {
- let trimmed = raw.trim();
- if trimmed.is_empty() || trimmed.starts_with('#') {
- return None;
- }
- let line = parse_build_list(trimmed).into_iter().next()?;
+ parse_lines(text)
+ .into_iter()
+ .filter_map(|shard_line| {
+ let line = parse_build_list(&shard_line.text).into_iter().next()?;
Some(Entry {
- line: index + 1,
+ line: shard_line.number,
file: line.file,
flags: line.flags,
})
@@ -636,12 +633,15 @@ fn render_text(report: &ProvidedReport) -> String {
let mut out = String::new();
for hit in &report.hits {
out.push_str(&format!(
- "provided {} name={} list={}\n",
- hit.file, hit.name, hit.list
+ "provided {} name={} list={} line={}\n",
+ hit.file, hit.name, hit.list, hit.line
));
}
for line in &report.unparsed {
- out.push_str(&format!("unparsed {} list={}\n", line.file, line.list));
+ out.push_str(&format!(
+ "unparsed {} list={} line={}\n",
+ line.file, line.list, line.line
+ ));
}
for note in report
.toolchain
diff --git a/tests/stack_provided.rs b/tests/stack_provided.rs
index 9b8f4f1..182812a 100644
--- a/tests/stack_provided.rs
+++ b/tests/stack_provided.rs
@@ -32,12 +32,23 @@ fn a_line_the_baseline_provides_exits_1_and_an_allow_keeps_it() {
let stderr = String::from_utf8_lossy(&failed.stderr);
assert_eq!(failed.status.code(), Some(1), "{stdout}{stderr}");
assert!(
- stdout.contains("provided FFTW-3.3.10-gompi-2026.1.eb name=FFTW "),
+ stdout.contains(
+ "provided FFTW-3.3.10-gompi-2026.1.eb name=FFTW list=tests/provided_fixtures/site.list line=4"
+ ),
+ "{stdout}"
+ );
+ assert!(
+ stdout.contains("name=Python list=tests/provided_fixtures/site.list line=5"),
+ "{stdout}"
+ );
+ assert!(
+ stdout.contains("name=GROMACS list=tests/provided_fixtures/site.list line=3"),
+ "{stdout}"
+ );
+ assert!(
+ stdout.contains("unparsed not-a-version.eb list=tests/provided_fixtures/site.list line=7"),
"{stdout}"
);
- assert!(stdout.contains("name=Python "), "{stdout}");
- assert!(stdout.contains("name=GROMACS "), "{stdout}");
- assert!(stdout.contains("unparsed not-a-version.eb "), "{stdout}");
assert!(!stdout.contains("VASP"), "{stdout}");
assert!(
stderr.contains("3 build-list lines name software the baseline already provides"),
--
2.43.0

Loading
Loading