| git.druid.rocks | index | druid520 | mp | docs/ | reference/ | pms-roadmap.btft |
docs/reference/pms-roadmap.btft
[table class="topnav"]
[tr]
[td class="logotab"]mp[e]
[td][see name="index"]index[e][e]
[td][see name="guide/index"]guide[e][e]
[td][see name="reference/index"]reference[e][e]
[e]
[e]
[h1]PMS parity roadmap[e]
mp's own dependency-atom language (name, slot, version comparison, %tag providers, soft/hard conflicts) covers the common cases cleanly, but Portage's Package Manager Specification (PMS) added several mechanisms over 20+ years for harder problems mp doesn't have equivalents for yet. This page is a concrete, source-grounded design note for each gap -- what exactly is missing, what it would take to close it, and what it touches -- written so a future session can pick one up without re-deriving the architecture from scratch. Not a promise of when; a map of the terrain.
each section below was scoped by actually reading the relevant mp source, not by assumption -- where a gap turned out bigger (or smaller) than it first looked, that's noted explicitly.
[h2]repository-qualified atoms (pkg::reponame) -- DONE[e]
[code]pkg::reponame[e] (mplib::version::parse_dep_token, always last in the atom grammar) restricts resolution to exactly that configured repo. Implemented WITHOUT the all_ports() dedup rework this section originally called for -- turned out unnecessary: [code]find_portdir[e]'s new third arg does an independent, uncached, repo-scoped scan ([code]_ports_in_root[e] + a shared [code]_resolve_in_list[e] helper factored out of find_portdir's own existing logic) instead, leaving all_ports()'s own cross-repo dedup (and every command built on it) completely untouched. CUSTOM_PORTS_DIR still applies unconditionally either way. [code]install_pkg[e] threads it through as the real consumption path; fetch.pl/info.pl/verify.pl/conflicts.pl/use.pl too, for CLI consistency.
known, documented limitation: [code]parse_dep_token[e] has no "/" in its grammar at all (a cat/name-shaped token is one opaque unparsed literal, pre-existing behavior) -- so "cat/name::repo" isn't reachable from a real dependency atom today even though find_portdir itself supports it when called directly. Test coverage: test/suites/multirepo.sh, test/suites/version.sh's own deptoken matrix.
[h2]USE-conditional dependency atoms -- DONE (narrower than PMS, deliberately)[e]
[code]name[flag,-flag,...][e] (mplib::version::parse_dep_token, between the version group and [code]::repo[e]) requires the dependency itself be built with (or without) specific USE flags. Deliberately NOT PMS's fuller [code][flag=][e]/[code][flag?][e] forms (match/conditional-on the consumer's own USE state) -- those need the consuming port's own enabled_use() state threaded through pure string parsing, which parse_dep_token has no access to; plain require-enabled/require-disabled covers the common case cleanly.
Enforcement (mplib::resolve): [code]install_pkg[e]'s own [code]apply_use_requirement[e] persists a per-package USE= override BEFORE resolve_slot runs (so a pkg_slot_use port's own composition reflects it correctly, not one cycle late); [code]resolve_dep[e]'s own already-installed branch fails outright on a mismatch, since there's no way to retroactively change what an existing binary was built with. A second, contradictory requirement on the same canon+flag within one process fails immediately via a tracking hash -- though its real reach turned out narrower than first assumed: mp resolves pkg_deps= sequentially, so two ordinary consumers naming the same not-yet-installed target almost always have the first one complete (and $db populated) before the second is even looked at, meaning that case actually surfaces via resolve_dep's own "already installed, wrong state" path instead, with a different message. The tracking hash's own real value is a narrower window: a requirement recorded by an attempt that itself never reached $db (a soft dependency that failed and was skipped, most plausibly).
the genuinely open design question this section originally flagged -- constraint propagation across contradictory requirements -- was resolved with a deliberately SIMPLER answer than PMS's own autounmask-style resolution: fail immediately and clearly, naming what disagrees, rather than computing and prompting a config change across the whole graph. A real, considered "different is fine, this is cleaner" choice, not a shortcut.
also fixed along the way: [code]set_pkgconf[e] (mplib::config) only ever wrote pkgconf.conf to disk, never updated [code]$CFG->{pconf}[e] in memory -- invisible until this feature needed a same-process read-back (resolve_slot, immediately after applying a requirement) and got stale data. Fixed at the source, not worked around locally.
Test coverage: test/suites/version.sh's own USE-bracket deptoken cases, test/suites/resolution.sh's three end-to-end scenarios (forces flag before build, sequential conflicting requirements, already-installed wrong state).
[h2]slot operators (:=) / subslot rebuild cascades -- DONE (different shape than PMS, deliberately)[e]
Portage: [code]cat/pkg:=[e] records the provider's SLOT/SUBSLOT at build time and flags the dependent for rebuild automatically the moment the subslot changes -- the mechanism that makes every C++ package rebuild when gcc bumps libstdc++'s ABI.
mp's answer: a leading [code]@[e] sigil on a [code]pkg_deps=[e] token (mplib::resolve's own [code]resolve_dep[e]/[code]resolve_dep_name[e], stripped the same way as the existing [code]?[e] soft marker, either order, either alone or combined) rather than a [code]:=[e] suffix on the atom grammar itself -- mp's slot syntax already means something different (an exact SLOT pin), and layering this onto a separate sigil keeps the two independent instead of risking collision in an already-dense regex. A new, independent [code]pkg_subslot=[e] field (pkg.conf's generic KEY=value metadata capture handles it with zero parser changes) lets ABI bump without the visible slot changing, exactly as PMS intends.
Mechanism: [code]record_subslot_dep[e] persists, in a new root-scoped [code]subslots.db[e] (same "consumer\tdep\tvalue" shape as the existing linkdeps.db), exactly what [code]pkg_subslot[e] the resolved target declared at the moment a [code]@[e]-marked dependency resolved -- called from all four of [code]resolve_dep[e]'s successful non-soft resolution points (a pinned %tag, a picked %tag, an already-installed match, a freshly-installed one). [code]subslot_changed_targets[e] (mplib::resolve) is the other half: every currently-installed consumer whose recorded subslot no longer matches its dependency's CURRENT [code]pkg_subslot[e]. update.pl's own dependency-graph-building pass folds this in right after its own explicit request resolves, so a narrow, explicit "mp update gcc" pulls in every C++ consumer that tracked gcc's subslot, without the caller needing to name them -- additive only: a request resolving to nothing never starts an unrelated cascade on its own.
real bugs caught by testing before landing, all the same shape -- a raw pkg_deps= token processor written before the [code]@[e] sigil existed, only ever stripping the OLDER [code]?[e] marker: [code]resolve_dep_name[e] (used by update.pl's own ordering pass, why.pl, tree.pl, prune.pl) and [code]resolved_slotted_deps[e] (mpx's own buildlink -I/-L/rpath wiring) both needed the identical one-line fix. The more consequential instance: [code]install.pl[e]/[code]reinstall.pl[e]'s pre-flight satisfiability planner ([code]plan_resolution[e]/[code]_plan_seq[e]/[code]_plan_dep[e]/[code]_plan_soft_dep[e]) parses every token independently of resolve_dep and had never been taught either sigil beyond [code]?[e] at all -- a [code]"@gcc"[e] dependency failed outright with "port @gcc not found" before any real resolution even began, caught by actually running the end-to-end scenario, not just the isolated-function tests the earlier three sections relied on. Fixed at both [code]_plan_dep[e]'s own entry (stripping [code]@[e] before the existing [code]?[e] check, either order, mirroring resolve_dep's own while-loop) and, separately, [code]_plan_soft_dep[e]'s own entry (a second, independent call path into the same token grammar that [code]_plan_dep[e]'s own fix does not cover).
Test coverage: test/suites/resolution.sh's test_subslot_dep_recorded_on_install (subslots.db gets the right line after install) and test_subslot_change_triggers_dependent_rebuild (an explicit, narrow "mp update" on just the provider still rebuilds the dependent, verified via a version-bumped port's own fresh marker file, not just "still present").
[h2]transactional blocker resolution -- DONE (deliberately conservative)[e]
[code]check_static_conflicts[e] (mplib::resolve) was already solid for hard ([code]!![e]) conflicts -- aborts/backtracks provider choice, unchanged. Soft ([code]![e]) conflicts now get real resolution via two new functions: [code]soft_conflict_targets[e] (the pairs check_static_conflicts itself discovers but only ever note()s) and [code]reachable_needed_set[e] (the exact transitive-runtime-dependency-closure algorithm mp prune already used to decide "safe to remove" -- extracted out of prune.pl, which used to duplicate it inline, into a shared function prune.pl now calls too, with an optional tick callback so its own spinner keeps animating).
install_pkg_body: a soft conflict with an installed package NOTHING needs at all gets auto-removed as part of the new install. One still needed -- explicitly requested, or reachable as someone else's dependency -- is left alone exactly as before, NEVER escalated to a hard failure: a soft conflict between two packages the user deliberately chose to have both installed is exactly what "soft" already meant. This is deliberately NOT Portage's own full transactional model (which would enforce mutual exclusion even against a requested package, computing a forced removal order) -- confirmed against the existing test_soft_conflict_warns scenario before settling on this split: an earlier draft that DID hard-fail on "still needed" was caught and corrected specifically because it would have turned that previously-succeeding, already-tested case into a failure. A conservative, narrower improvement (clean up genuine orphans automatically) rather than a wholesale behavior change.
Test coverage: test/suites/resolution.sh's test_soft_conflict_auto_removes_unneeded and test_soft_conflict_leaves_transitively_needed_alone, alongside the pre-existing test_soft_conflict_warns (unchanged, still passing).
[h2]what mp already got right, for contrast[e]
worth remembering while reading the above: mp's stage-ordering in mplib::version (a/b/pre/rc < release) already matches PMS's own documented order correctly, and its [code]~[e] operator's prefix-match semantics ([code]~1.2[e] = "any 1.2.x") are a deliberate, DOCUMENTED design choice (see [see name="reference/mp-conf-example"]mp.conf.example[e]), not an accidental PMS divergence to "fix" -- confirmed before touching anything, specifically to avoid breaking that contract. Not everything different from PMS is a gap; some of it is just a different, equally-considered answer to the same problem.