Output formats#

1 Stack lock (stack.lock.json)#

{
  "schema_version": 1,
  "toolchain": {"name": "foss", "version": "2025b"},
  "generation_label": "foss-2025b",
  "packages": [
    {
      "name": "GROMACS",
      "version": "2025.0",
      "toolchain": {"name": "foss", "version": "2025b"},
      "versionsuffix": null,
      "easyconfig_path": "foss-2025b/GROMACS-2025.0-foss-2025b.eb"
    }
  ],
  "solver": {
    "engine": "resolvo_cdcl_sat",
    "engine_version": "resolvo+eb_stack-0.3.0",
    "timestamp": "2026-01-01T00:00:00Z"
  }
}
schema_version

1 as of eb-stack 0.2.0.

toolchain

The policy’s target toolchain (not necessarily every package’s own toolchain field, though in practice they match).

generation_label

Optional human label, e.g. "foss-2025b" when produced from an easyconfig tree. Library callers may omit it for hand-built universes.

packages

One entry per co-selected package, sorted by name. Each entry is a LockPackage: name, version, its own toolchain, optional versionsuffix, and the easyconfig_path it came from.

solver

engine is always "resolvo_cdcl_sat". engine_version embeds the crate version. timestamp is RFC 3339, UTC, second precision.

2 Internal candidate universe#

{
  "toolchain": {"name": "foss", "version": "2025b"},
  "generation_label": "foss-2025b",
  "candidates": [
    {
      "name": "GROMACS",
      "version": "2025.0",
      "toolchain": {"name": "foss", "version": "2025b"},
      "versionsuffix": null,
      "easyconfig_path": "foss-2025b/GROMACS-2025.0-foss-2025b.eb",
      "dependencies": [
        {"name": "OpenMPI", "version_req": ">=4.1.6"}
      ],
      "builddependencies": []
    }
  ]
}

dependencies and builddependencies use the same DepReq shape and the same version_req grammar as policy pins (see policy reference). This structure is a Rust/library input; the version-one CLI builds it from repeatable --easyconfigs paths.

3 Build list (--build-list-out)#

Plain text, one easyconfig path per line, trailing newline, no blank lines:

foss-2025b/OpenBLAS-0.3.27-foss-2025b.eb
foss-2025b/OpenMPI-4.1.6-foss-2025b.eb
foss-2025b/FFTW-3.3.10-foss-2025b.eb
foss-2025b/GROMACS-2025.0-foss-2025b.eb

Order is a topological sort over the co-selected stack: a dependency’s path always appears before every co-selected package that needs it (runtime or build-time). Ties are broken by package name for a deterministic order independent of input list order or HashMap iteration. An empty lock produces an empty file.

4 Stack diff (--stack-diff-out)#

Markdown, meant to be pasted directly into a pull request:

# Stack diff

Baseline (`foss-2025a`) -> solved (`foss-2025b`).

## Summary

- **unchanged**: 1
- **added**: 0
- **removed**: 0
- **version-bumped**: 3

## Packages

### FFTW -- unchanged

- Baseline: `3.3.10` -- `foss-2025a/FFTW-3.3.10-foss-2025a.eb`
- Solved: `3.3.10` -- `foss-2025b/FFTW-3.3.10-foss-2025b.eb`

### GROMACS -- version-bumped

- Baseline: `2024.1` -- `foss-2025a/GROMACS-2024.1-foss-2025a.eb`
- Solved: `2025.0` -- `foss-2025b/GROMACS-2025.0-foss-2025b.eb`
- Change: `2024.1` -> `2025.0`

Every package present in either the baseline or the solved lock is classified once: unchanged (same version both sides), added (solved only), removed (baseline only), or version-bumped (present both sides, different version). Sections are sorted by package name.

5 Planned SBOM (--sbom-out, CycloneDX 1.5)#

Documents are built with the official cyclonedx-bom crate (same models as cargo-cyclonedx), serialized as CycloneDX 1.5 JSON.

Opt-in: --sbom-out has no default filename. Omit it and stack solve writes only the lock (and any requested build list / stack diff). eb-stack stack sbom --lock LOCK defaults to stack.cdx.json because that command’s sole purpose is SBOM emission.

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:…",
  "version": 1,
  "metadata": {
    "timestamp": "2026-01-01T00:00:00Z",
    "tools": [{"vendor": "SURF", "name": "eb-stack", "version": "resolvo+eb_stack-0.3.0"}],
    "component": {
      "type": "application",
      "name": "easybuild-stack-foss-2025b",
      "version": "foss-2025b",
      "description": "Planned EasyBuild stack inventory from eb-stack lock (pre-install; not a post-build compliance scan)"
    },
    "lifecycles": [{"phase": "pre-build"}],
    "properties": [
      {"name": "eb_stack:document_kind", "value": "planned-sbom-from-lock"},
      {"name": "eb_stack:solver_engine", "value": "resolvo_cdcl_sat"},
      {"name": "eb_stack:toolchain", "value": "foss-2025b"}
    ]
  },
  "components": [
    {
      "type": "library",
      "bom-ref": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b",
      "name": "GROMACS",
      "version": "2025.0",
      "purl": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b",
      "properties": [
        {"name": "easybuild:toolchain", "value": "foss-2025b"},
        {"name": "easybuild:easyconfig_path", "value": "foss-2025b/GROMACS-2025.0-foss-2025b.eb"},
        {"name": "eb_stack:lifecycle", "value": "pre-install-plan"}
      ]
    }
  ],
  "dependencies": [
    {"ref": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b", "dependsOn": ["pkg:generic/OpenMPI@4.1.6?toolchain=foss-2025b"]}
  ]
}

Each component’s bom-ref / purl is a synthetic pkg:generic/ reference (EasyBuild has no dedicated PURL type) embedding name, version, and toolchain. Metadata lifecycles: pre-build and eb_stack:lifecycle: pre-install-plan mark a planned inventory from the solver selection, not a scan of installed software.

dependsOn edges:

  • From stack solve (universe in hand): each package’s declared runtime dependencies, intersected with co-selected lock members. Build-time deps (builddependencies) go on the component property eb_stack:buildDependsOn (comma-separated refs), not the runtime graph.

  • From eb-stack stack sbom --lock LOCK (lock only): dependsOn is empty (unknown edges). The document never invents all-to-all co-stack cycles. Empty dependsOn arrays may be omitted by cyclonedx-bom serialization.