mod package_closure

module package_closure

Recursive package-closure planner and aggregate bundle writer.

Closes a root foreign package plan over robot candidates, optional catalog overrides, and ordered local source roots (EasyBuild / conda-forge / Spack / Cargo). Compatible robot candidates win. Holes resolve robot-first, then catalog overrides, then EasyBuild cross-generation bump, then an unambiguous foreign recipe. Package names never appear as control flow.

The aggregate writer places root artifacts at the bundle root, companion artifacts under packages/<name>-<version>-<toolchain>/, and every recipe and verified patch into one shared easyconfigs/ overlay.

Variables

const CLOSURE_BUNDLE_SCHEMA_VERSION: u32

Schema version for build-order.json and closure.plan.json.

Functions

fn merge_closure_sboms<'a, I>(sboms: I) -> Result<Value, PackageClosureError>
where
    I: IntoIterator<Item = &'a Value>

Merge root and companion CycloneDX documents into one aggregate BOM.

Components and dependency graph edges are deduplicated by bom-ref when present, otherwise by a stable type|name|version identity. The result is a real CycloneDX JSON document, not a custom wrapper.

fn package_layout_segment(bundle: &PackageBundle) -> Result<String, PackageClosureError>

Deterministic companion directory segment under packages/.

Format: <name>-<version>-<toolchain-name>-<toolchain-version>. Each field is validated as a single safe path component so no package-specific branches are required to keep the layout inside the bundle.

fn plan_package_closure(request: &NewPackageRequest, catalog: &PackageSourceCatalog) -> Result<PackageClosure, PackageClosureError>

Plan a root package and recursively close robot holes (catalog only).

Prefer plan_package_closure_with_sources when ordered EasyBuild / conda-forge / Spack source roots are configured.

fn plan_package_closure_with_sources(request: &NewPackageRequest, catalog: &PackageSourceCatalog, source_roots: &PackageSourceRoots) -> Result<PackageClosure, PackageClosureError>

Plan a root package and close robot holes via catalog overrides and source roots.

The EasyBuild robot is parsed once. Each requested root profile is solved against robot candidates plus generated companions. Holes are discovered by inspecting the admitted candidate universe, never by parsing solver text. Explicit catalog entries override discovery for the same identity.

fn write_package_closure(closure: &PackageClosure, output_directory: &Path) -> Result<WrittenPackageClosure, PackageClosureError>

Write a planned package closure into a single campaign-ready bundle layout.

Root manifest/SBOM/locks stay at the bundle root. Each companion is written under packages/<name>-<version>-<toolchain-name>-<toolchain-version>/. Every recipe and verified patch lands in one shared easyconfigs/ overlay; colliding destinations are rejected. Profile order within each package is preserved; companions precede the root in build-order.json.

Enums

enum PackageClosureError

Why a package closure could not be completed.

Most variants name an ambiguity rather than a hard failure: closing a dependency graph means choosing providers, and a choice the tool cannot make on evidence is surfaced instead of guessed at.

Workflow(PackageWorkflowError)

A package in the closure could not be planned or written.

Catalog(PackageCatalogError)

The provider catalog could not be built.

SourceRoots(PackageSourceError)

A configured source root could not be read.

Solve(ProfileSolveError)

A profile’s dependency solve failed.

Cycle

The dependency graph has a cycle, so no build order exists.

path: Vec<String>

The cycle, in the order it was walked.

MissingProvider

Nothing in the catalog provides a required package.

name: String

Package nothing provides.

version: String

Version that was required.

AmbiguousProvider

Several candidates provide it and none is clearly right.

name: String

Package with several providers.

version: String

Version that was required.

count: usize

How many candidates matched.

MissingSource

No source recipe was found to generate a needed companion from.

name: String

Package no source recipe was found for.

version_req: String

Version requirement that had to be met.

candidates: Vec<DiscoveredCandidate>

Sources that were considered and rejected.

AmbiguousSource

Several source recipes match, so which to generate from is unclear.

name: String

Package with several matching sources.

version_req: String

Version requirement that had to be met.

count: usize

How many sources matched.

candidates: Vec<DiscoveredCandidate>

The sources in question.

SourceParseFailure

Every candidate source recipe failed to parse.

name: String

Package whose sources all failed to parse.

version_req: String

Version requirement that had to be met.

failures: Vec<SourceParseFailure>

Why each candidate failed.

IncompatibleProviderVersion

The available provider cannot satisfy the required version.

name: String

Package whose provider is too old or too new.

provided: String

Version the provider offers.

required: String

Version that was needed.

GeneratedCandidateNotAdmitted

A companion was generated but the solve still would not take it, which means generating it did not close the gap.

name: String

Companion that was generated.

required: String

Version the solve still demands.

profile: String

Profile that would not take it.

MissingProfile

A package has no profile by that name.

package: String

Package that has no such profile.

profile: String

Profile that was asked for.

ProfileAmbiguity

Which profile to use could not be decided.

package: String

Package whose profile could not be decided.

reason: String

Why the choice is ambiguous.

GeneratedCandidate(String)

A generated companion was itself invalid.

Layout(String)

The closure could not be laid out on disk.

AggregateSbom(String)

The combined SBOM could not be produced.

Structs and Unions

struct ClosureBuildOrder

Declared EasyBuild build order for a closed package bundle.

schema_version: u32

Must equal CLOSURE_BUNDLE_SCHEMA_VERSION.

recipes: Vec<String>

Bundle-relative recipe paths using / separators.

struct ClosurePackageRef

One package identity inside a written closure layout.

name: String

Package name.

version: String

Package version.

toolchain: crate::domain::Toolchain

Toolchain the recipes target.

directory: String

Bundle-relative directory (. for the root package).

manifest: String

Bundle-relative path to the package plan.

sbom: String

Bundle-relative path to the package SBOM.

recipes: Vec<String>

Bundle-relative paths of the emitted easyconfigs.

struct ClosurePlanDocument

Aggregate description of a written package closure.

schema_version: u32

Must equal CLOSURE_BUNDLE_SCHEMA_VERSION.

root: ClosurePackageRef

The requested package.

companions: Vec<ClosurePackageRef>

Generated companions, dependencies before dependents.

build_order: String

Bundle-relative path of the build-order document.

struct PackageClosure

Root package bundle plus generated companions in topological build order.

root: PackageBundle

The package that was asked for.

companions: Vec<PackageBundle>

Generated companion bundles, dependencies before dependents.

struct WrittenPackageClosure

Paths written for a closed multi-package bundle.

root: WrittenPackageBundle

Where the requested package landed.

companions: Vec<WrittenPackageBundle>

Where each generated companion landed, in build order.

build_order: PathBuf

The build-order document.

closure_plan: PathBuf

The aggregate plan describing the whole closure.

closure_sbom: PathBuf

The aggregate SBOM covering every package in it.