Resolver — resolver.py¶
Turns a root manifest plus the set of available core versions into the chosen version(s) per package. Pure module (no I/O): the available versions are passed in, so the solve is deterministic and fully unit-testable.
- Source: src/hdlpkg/resolver.py
- Import:
from hdlpkg import resolve, Resolution
Purpose¶
Given what a core depends on (constraints) and what versions exist, pick the version(s) of each package that satisfy every accumulated constraint. The result is what the lockfile records and what install/gen build on.
API¶
def resolve(
root: Manifest,
available: Mapping[PackageRef, Sequence[Manifest]],
policy: ConflictPolicy | None = None,
) -> Resolution
root— the top-level manifest whose[dependencies]drive the solve.available— for each package, the manifests of the versions a registry/cache offers. Manifests (not bare versions) so each candidate's own[dependencies]and declared version scheme can be followed. In practice this map is built byavailable_from_registry.policy— how to treat an incompatible conflict; defaults to the root manifest's[resolution] on-conflict(fail_on_conflictif unset). See below.
Resolution is a frozen dataclass:
| Member | Description |
|---|---|
packages |
tuple[Vlnv, ...] — every selected VLNV (usually one per package) |
vlnvs (property) |
the selected VLNVs sorted by string (deterministic order) |
by_ref (property) |
dict[PackageRef, tuple[Vlnv, ...]] — selections grouped by package |
warnings |
tuple[str, ...] — any policy-driven compromise (a use_latest collapse, an isolated coexistence) |
Algorithm & guarantees¶
- Compatibility unification (Cargo-style): dependents whose ranges fall in the
same compatibility group — same major for SemVer (see
compatibility_group) — always unify to the newest version satisfying them all. A diamond on^1.0+^1.1collapses to one1.1.x. - Conflict policy: only a genuinely incompatible conflict (two SemVer majors, or
two distinct exact pins of an
opaque-scheme package) is governed by theConflictPolicy: fail_on_conflict(default) — raiseResolutionError.use_latest— collapse to the newest of the conflicting versions (single copy), prune orphans, and record awarning.isolate_namespaces— keep every incompatible version in the resolve/lock/tree (multi-version bookkeeping).genthen name-mangles coexisting SystemVerilog/VHDL packages so they build together (module/entity coexistence is refused).- Scheme-aware: a package's
[package].schemechooses how its versions group —semver(by major),calver(by year),monotonic(one shared group), oropaque(each exact pin its own group; dependents must pin exactly). See versioning. - Newest-compatible, transitive, backtracking (a newest-first choice that makes a transitive constraint unsatisfiable falls back to older versions), and pre-release-aware (the version rule). The search is keyed per (package, compatibility group) node, so two incompatible majors are independent nodes that each resolve to one version. Can be swapped for a SAT/CDCL solver later without changing the public contract.
Errors¶
ResolutionError if no assignment satisfies all constraints, or an incompatible
conflict is hit under fail_on_conflict. The message names the offending package, its
constraints, and the versions on offer (or the conflicting versions).
Example¶
from hdlpkg import Manifest, resolve
from hdlpkg.registry import LocalDirectoryRegistry, available_from_registry
root = Manifest.from_path("examples/uart/ip.toml")
registry = LocalDirectoryRegistry([Path("examples")])
resolution = resolve(root, available_from_registry(registry, root))
for vlnv in resolution.vlnvs:
print(vlnv) # acme:common:fifo:1.0.0
for warning in resolution.warnings:
print("warning:", warning)
hdlpkg tree prints this resolution as a graph; see the CLI page.