ADR 010 Planning an installation - nekrozis/qtogo GitHub Wiki

ADR-010: Planning an installation

Status: Accepted (2026-10-09).

Context

  • A request names a host, a target, a version and — usually — an architecture, and sometimes a set of modules. Answering it means turning a repository's metadata into a definite list of archives to fetch and a destination for each. This record fixes how that list is built, because the plan is what plan install-qt --json publishes and what install-qt will execute — a contract, not an internal step.
  • The repository's Updates.xml is already read by repository.ParseUpdatesXML into PackageUpdate values: a name, a version carrying a build stamp, download file names, and an Operations block. internal/discovery already walks a target to the leaf holding that document. What was missing is the layer between them.
  • The reference tool is the compatibility target, so its behaviour is the default unless there is a reason to differ. Its selection, URL and destination rules were read directly (aqt/archives.py), and one earlier note in this project's plan — that an older package's destination is derived from its version and architecture — turned out to be wrong.

Decision

  1. Selection is by package name and architecture; the metadata's dependency fields are not followed. A component is chosen when its name is one the request spells — the base package qt.qt<major>.<token>.<arch> (or the qt.<token>.<arch> form), and, per requested module, the candidate names a module can take — and the architecture appears in that name. <token> is the version directory's own spelling (680, 6110, 59), which is what the repository used to address the version.

    Dependencies and AutoDependOn are parsed but not consumed. The reference tool reads them, even carries a closure helper, and never calls it in the install path; it selects by name and architecture, and where one component truly needs another (a phone or wasm target needing a desktop one) it uses a bespoke rule rather than these fields. Following it keeps the installed set observable-compatible; a naive closure would instead pull in documentation, examples and tools the user never asked for, which is worse than a gap.

    If a concrete case appears where a component is genuinely required, is neither the base nor a requested module, and is missed by name matching — an incomplete or broken install — then a narrow closure over Dependencies alone (not AutoDependOn, which is a different, selection-time semantic) is implemented and recorded as a difference. Until such a case is shown, there is no closure.

  2. An archive's URL is the leaf, the package's name, and the package's own version stamp followed by the archive's file name. In turn: <leafPath>/<package name>/<package version><archive file name>, where the version is the metadata's <Version> verbatim (6.8.0-0-202410030750) — the package's identity, not the release the request named.

  3. Where an archive lands is decided per package, from its own Extract operations, and a package with none extracts to the install root. The Extract arguments come in pairs — a destination and an archive name — and the destination has the @TargetDir@/ prefix removed; the archive names there are the package's downloads. With no Extract, the package's downloads have no destination of their own and land at the root.

    This is per package, never per version. The boundary is not a release number: one repository has packages with Extract and packages without, and the same version differs between hosts. And it is not a derivation: an archive that predates the Extract blocks carries its version and architecture inside itself, so extracting it at the root lands it correctly, and adding a derived <version>/<arch> would place it one level too deep. Archive.InstallPath is therefore either the explicit destination or the root — there is no third, conventional value (this corrects ADR-001).

  4. The plan has a fixed shape and a fixed order. It names the version, its directory token and the resolved architecture; the packages selected, base first and then the requested modules in the order they were asked for; and the archives, in package order and then in the order the package lists them (operation order where there are operations, download order otherwise). Each archive carries its file name, its package, its URL, and its destination — the root when there is none. The order is fixed so a plan is reproducible, and the destinations are explicit so install needs no further decision.

  5. A plan that cannot be built fails as "not found". A version the target does not offer, a base package the metadata does not carry, and a requested module with no matching package are all exit 3: the user asked for something the repository does not have. An architecture that was left out and cannot be narrowed to one is the same, and carries a suggestion to name it. Nothing is guessed: a component that is not there is an error, not a smaller plan.

  6. A module form joins the list only with a captured sample. The candidate names a module can take are the reference tool's minus the ones no sample shows — the Qt 6.8+ spellings and the bare qt.<token>.<module>.<arch> form. The consequence is that a module the reference tool resolves can be refused here, which is an open difference rather than a hidden one: it closes by adding the form with a fixture (ADR-003's rule). The all module keyword is likewise out until the module listing a target offers lands, since it needs that rule.

Consequences

  • The catalog is pure: it takes parsed packages, the leaf path and a request, and returns a plan. It reaches no network and no filesystem, so it is tested with literals and needs no fixtures.
  • The order of the repository layers — walk, read, plan — lives in internal/service, one step per layer; the catalog knows nothing of discovery.
  • A plan is deterministic and its destinations are explicit, which is what lets a dry run and a real install share the planner and be compared directly.
  • The no-closure decision carries a trigger: a case that shows it wrong is a bug to fix with a narrow Dependencies closure, not a reason to have built the general thing early.
  • Archive.InstallPath losing its "conventional" branch is a revision of ADR-001, which is stated there so neither record can drift from the other.

Alternatives considered

  • A transitive closure over Dependencies/AutoDependOn. Rejected for now: it is not what the reference does, and on the real metadata it would add tools and documentation a plain install does not produce — a difference that shows up as surprise, not as correctness. Kept as an evidence-triggered upgrade, not a default.
  • Deriving the destination from the version and architecture when there is no Extract. Rejected: the archives already carry that path internally, so a derived prefix double-nests them. Measured, not assumed.
  • Branching on the version to decide whether Extract should be present. Rejected: Extract is per package, and the same release differs across hosts, so a version test would be wrong for the very case it tries to describe.
  • Choosing a default architecture per host. Rejected: it invents a rule the reference does not have and the metadata already answers. An omitted architecture is resolved only when the choice is unambiguous; otherwise it is asked for.
⚠️ **GitHub.com Fallback** ⚠️