mod package_workflow

module package_workflow

Canonical foreign-package planning bundle: manifest, SBOM, locks, recipes.

Functions

fn complete_package_bump(request: &BumpPackageRequest, mut plan: PackagePlan, candidates: &[crate::domain::Candidate], stack_policy: &StackPolicy) -> Result<PackageBundle, PackageWorkflowError>

Solve the bump plan against a candidate universe and emit the retargeted .eb.

Preserves source recipe build mechanics, source/patch identity, and checksum order via the annual-bump emitter. Stack-policy preferred pins remain a Resolvo input; lock evidence records selection and fallback outcomes. Solve the bumped plan and emit the new easyconfig.

fn complete_package_bundle(plan: PackagePlan, sbom: Value, candidates: &[crate::domain::Candidate], stack_policy: &StackPolicy) -> Result<PackageBundle, PackageWorkflowError>

Solve every plan output profile and emit easyconfigs against a candidate universe. Solve every profile against candidates and emit its easyconfig.

fn complete_package_bundle_with_hierarchy(plan: PackagePlan, sbom: Value, candidates: &[crate::domain::Candidate], stack_policy: &StackPolicy, hierarchy_fixture: Option<&Path>) -> Result<PackageBundle, PackageWorkflowError>

Like complete_package_bundle, with an optional hierarchy fixture path.

fn inspect_new_package(source: &Path, format: Option<ForeignFormat>, toolchain: &Toolchain, package_layers: &[PackageConfigLayer]) -> Result<(PackagePlan, Value), PackageWorkflowError>

Ingest a foreign recipe and apply configuration layers, without solving.

Returns the plan and its planned SBOM. Nothing is written and no robot tree is consulted, so this is the cheap look before committing to a bundle.

fn plan_new_package(request: &NewPackageRequest) -> Result<PackageBundle, PackageWorkflowError>

The whole new-package path: ingest, layer, solve, emit.

Fails with PackageWorkflowError::NoEasyconfigRoots rather than solving against an empty universe, which would silently produce a bundle whose dependencies resolve to nothing.

fn plan_package_bump(request: &BumpPackageRequest) -> Result<PackageBundle, PackageWorkflowError>

The whole bump path: read, re-target, solve, emit.

Fails with PackageWorkflowError::NoEasyconfigRoots rather than solving against an empty universe.

fn prepare_new_package_plan(request: &NewPackageRequest) -> Result<(PackagePlan, Value), PackageWorkflowError>

Parse foreign source, apply package layers and optional checksum overrides.

Does not solve profiles or emit recipes. Used by both single-package planning and recursive package-closure expansion against a shared robot universe. inspect_new_package for a request, then apply the positional source checksums it carries.

fn prepare_package_bump(request: &BumpPackageRequest) -> Result<(PackagePlan, Value), PackageWorkflowError>

Parse an existing EasyBuild recipe into a retargeted EasyBuild-origin plan.

Does not solve dependencies or emit the next-generation recipe. Used by both standalone package bump and catalog-backed closure planning for easybuild-bump providers. Read the source easyconfig and build the plan for its new generation.

fn relative_posix(path: &Path) -> String

Join path components with / regardless of host separator. A path as posix-separated text, keeping only normal components.

Used for overlay keys so collision detection compares the same string on every platform.

fn stack_policy_with_bump_overrides(stack_policy: &StackPolicy, overrides: &HashMap<String, String>) -> StackPolicy

Fold package-specific --dep overrides into locked stack pins. The policy with each override applied as an exact pin, replacing any pin the policy already held for that package.

fn validate_path_segment(segment: &str, kind: &str) -> Result<(), PackageWorkflowError>

Reject path segments that would escape the bundle layout. Reject a path segment that could escape the bundle root.

Package and profile names reach the filesystem, and a recipe is untrusted input, so an empty segment, a separator, or a parent reference is an error rather than something to sanitise and continue with.

fn write_package_bundle(bundle: &PackageBundle, output_directory: &Path) -> Result<WrittenPackageBundle, PackageWorkflowError>

Write a bundle to one directory, artifacts and recipe overlay together.

fn write_package_bundle_into(bundle: &PackageBundle, artifact_directory: &Path, recipe_bundle_root: &Path, claimed_paths: &mut BTreeMap<String, String>) -> Result<WrittenPackageBundle, PackageWorkflowError>

Write package artifacts under artifact_directory and recipes/patches under the shared recipe_bundle_root/easyconfigs/<letter>/<name>/ overlay.

claimed_paths tracks every overlay destination (posix-relative to recipe_bundle_root) so multi-package writers can reject collisions.

Enums

enum PackageWorkflowError

Why a package could not be planned or written.

Foreign(String)

The foreign recipe could not be read or understood.

EasyBuild(String)

The source easyconfig could not be resolved into a plan.

Config(String)

A configuration layer was invalid or could not be applied.

Sbom(String)

The planned SBOM could not be produced.

NoEasyconfigRoots

No robot tree was given. Solving against nothing would produce a bundle whose dependencies silently resolve to nothing.

NoSourceArtifacts

The recipe declares nothing to download, so there is nothing to build.

UndecidedPatches(String)

Strict patch mode: a patch’s applicability to the new version could not be decided from tree evidence.

SourceChecksumCount

The number of positional checksums does not match the artifact count.

expected: usize

Source artifacts the plan carries.

actual: usize

Checksums supplied.

InvalidSourceChecksum

A source checksum is not a SHA-256 hex digest.

index: usize

Position of the offending value.

checksum: String

The value as given.

MissingSourceChecksums(Vec<usize>)

Some source artifacts have no checksum, so the recipe would download unverified bytes.

MissingPatchChecksums(Vec<String>)

Some patches have no checksum.

InvalidPatchChecksum

A patch checksum is not a SHA-256 hex digest.

filename: String

Patch the value belongs to.

checksum: String

The value as given.

MissingPatchSource(String)

A patch is declared but names no file to copy.

PatchIo(PathBuf, std::io::Error)

A patch file could not be read.

PatchChecksumMismatch

A patch on disk does not hash to what the plan declared.

filename: String

Patch whose bytes disagree with the declared checksum.

expected: String

Checksum the plan declared.

actual: String

Checksum of the bytes on disk.

Robot(String)

A robot tree could not be parsed.

Solve(String)

No dependency selection satisfies a profile.

RefusePipOverlay

The foreign root is a compiled scientific package the robot does not ship. A PythonBundle pip overlay is the wrong install.

name: String

Package that must come from the scientific stack.

version: String

Version the foreign source asked for.

OverlayExtraNeedsVersion

A leftover PyPI dependency is not in the robot and has no exact version, so it cannot become an exts_list entry.

name: String

Missing leftover package.

requirement: String

Constraint that could not be lowered to a version.

Emit(String)

The easyconfig could not be rendered.

Io(PathBuf, std::io::Error)

A bundle file could not be written.

Json(PathBuf, serde_json::Error)

A bundle file could not be serialized.

UnsafePathSegment

A name that reaches the filesystem could escape the bundle root.

kind: String

What the segment names, e.g. package or profile.

value: String

The segment as given.

reason: String

Why it was refused.

OverlayCollision

Two packages claim the same overlay destination.

path: String

Overlay destination two packages both claim.

reason: String

What already claimed it.

OverlayPathOutsideBundle

A resolved overlay path lies outside the bundle root.

path: PathBuf

Destination that escaped the root.

root: PathBuf

Root it had to stay within.

Structs and Unions

struct BumpPackageRequest

Everything needed to carry an existing easyconfig to a new generation.

source: PathBuf

The easyconfig being bumped.

toolchain: Toolchain

Toolchain generation to move to.

version: Option<String>

New package version. None keeps the source version and changes only the toolchain.

source_checksum: Option<String>

SHA-256 of the new source tarball. Absent leaves the existing checksum in place and marks it stale, because a version change invalidates it.

easyconfig_roots: Vec<PathBuf>

Robot trees searched for dependency providers.

hierarchy_fixture: Option<PathBuf>

Toolchain hierarchy to resolve against, when not derived from the tree.

overrides: HashMap<String, String>

Dependency name to version, pinning what the solve may pick.

stack_policy: StackPolicy

Policy governing the dependency solve.

strict_patches: bool

Fail the bump when a patch’s applicability to the new version cannot be decided from tree evidence, instead of carrying it with a flag.

struct NewPackageRequest

Everything needed to turn a foreign recipe into an installable bundle.

source: PathBuf

Foreign recipe to ingest: a conda-forge meta.yaml or a spack package.py.

format: Option<ForeignFormat>

Force a format instead of detecting it from the file.

toolchain: Toolchain

Toolchain generation the emitted recipes target.

source_checksums: Vec<String>

Positional SHA-256 overrides, one for every canonical source artifact.

package_layers: Vec<PackageConfigLayer>

Configuration layers applied over the extracted plan, in order.

package_index: std::collections::BTreeMap<String, crate::ecosystem::IndexEntry>

Package name to version, for leftovers whose own metadata states no version. A dependency written as a bare name is normal in CRAN and on PyPI, and an exts_list entry still needs one concrete version.

easyconfig_roots: Vec<PathBuf>

Robot trees searched for dependency providers. At least one is required; an empty list is rejected rather than solved against nothing.

stack_policy: StackPolicy

Policy governing the dependency solve.

struct PackageBundle

A planned package, in memory and not yet written anywhere.

locks and easyconfigs are both empty for an inspection-only run, which is how a caller tells the two apart.

plan: PackagePlan

Canonical plan after ingest and every configuration layer.

sbom: Value

Planned CycloneDX SBOM. Records intent, not an installed filesystem.

locks: Vec<ProfileLock>

One dependency lock per profile.

easyconfigs: Vec<EmittedEasyconfig>

Emitted easyconfigs, one per profile.

struct WrittenPackageBundle

Where a written bundle landed, so a caller can report or verify the paths.

manifest: PathBuf

The package plan as written.

sbom: PathBuf

The planned SBOM as written.

locks: Vec<PathBuf>

One lock file per profile.

easyconfigs: Vec<PathBuf>

Emitted easyconfigs, inside the robot-shaped overlay.

patches: Vec<PathBuf>

Patch files copied next to the recipes that apply them.