Date: 2026-06-02
Branch: codex/dotted-dependency-analysis
Status: locally verified on feature branch; PR/CI/release still pending.
This document evaluates how mcpp should support dotted dependency selectors
such as:
[dependencies]
cmdline = "0.0.1"
capi.lua = "0.0.3"
compat.gtest = "1.15.2"
imgui.core = "0.0.1"
imgui.backend.glfw_opengl3 = "0.0.1"The goal is not only to make one manifest example pass. The goal is a coherent dependency model for a module-oriented package manager while preserving current manifests, xpkg compatibility, workspace behavior, and CLI ergonomics.
Local verification environment is a separate concern. If the local shell picks
an old mcpp, switch it explicitly through xlings:
xlings use mcpp 0.0.42That fixes local tool selection. It does not define the dependency selector architecture.
- A single
[dependencies]table can express both package namespaces and multi-level package/module names. mcpplibsmay be omitted by users as a priority alias, but it is not the only root namespace.- Explicit namespaces such as
compatstill work. - Multi-level names such as
imgui.backend.glfw_opengl3are first-class. - Root dependencies, dev/build dependencies, workspace dependencies, xpkg
mcpp.deps, and CLI add/remove must share the same selector rules. - Existing manifests and quoted legacy dotted keys remain accepted.
- The resolver must be deterministic in candidate ordering. Package lookup may then select the first available candidate from local/index metadata.
The current implementation is close in some places but is not yet one architecture:
src/libs/toml.cppmalready parses unquoted TOML dotted keys into nested tables. This is why[dependencies] compat.gtest = "..."can be distinguished from a quoted flat key.src/libs/toml.cppmalso tracks explicit tables. That distinction matters:[dependencies.imgui] core = "..."is not the same user intent as[dependencies] imgui.core = "...".src/manifest.cppmhas namespace-aware logic for part of root dependency parsing, but other dependency surfaces still split strings differently.- Workspace dependency parsing and synthesized xpkg
mcpp.depsparsing do not currently share the same selector model. src/pm/compat.cppmalready owns package lookup compatibility and index/file naming fallback logic. It is a reasonable temporary home for compatibility helpers, but canonical selector parsing should be conceptually separate from legacy fallback behavior.src/pm/package_fetcher.cppmandsrc/pm/resolver.cppmalready consume a structured package namespace plus package name. The missing piece is upstream normalization before dependencies reach those layers.src/pm/commands.cppmstill needs the same user selector rules for add/remove so CLI behavior matches manifest behavior.
Use one canonical dependency selector resolver at manifest/command ingestion time. The resolver should produce ordered package coordinate candidates:
candidate(namespace + shortName)[] + stableMapKey
The important rule is that mcpplibs is an optional prefix with priority, not a
forced root. imgui, compat, mcpplibs, and custom index namespaces are
peer roots. For selector a.b, lookup should first try the omitted-mcpplibs
candidate, then fall back to the literal peer-root candidate.
This keeps the interpretation explicit and ordered. The resolver itself should not perform network work; package lookup can resolve the ordered candidates against already available index metadata.
Omitted mcpplibs priority:
| selector | candidate priority | stable map key |
|---|---|---|
cmdline |
mcpplibs/cmdline |
cmdline |
capi.lua |
mcpplibs.capi/lua, then capi/lua |
capi.lua |
imgui.core |
mcpplibs.imgui/core, then imgui/core |
imgui.core |
imgui.backend.glfw_opengl3 |
mcpplibs.imgui.backend/glfw_opengl3, then imgui.backend/glfw_opengl3 |
imgui.backend.glfw_opengl3 |
Fully explicit prefix and peer-root fallback:
| selector | candidate priority | stable map key |
|---|---|---|
mcpplibs.capi.lua |
mcpplibs.capi/lua |
mcpplibs.capi.lua |
compat.gtest |
mcpplibs.compat/gtest, then compat/gtest |
compat.gtest |
Explicit subtable:
[dependencies.compat]
gtest = "1.15.2"This remains a direct explicit namespace form and should resolve to
compat/gtest, not to an omitted-mcpplibs candidate. Explicit subtables are the
clearest form for non-default or custom index namespaces when ambiguity matters.
Peer namespace roots include:
mcpplibscompatimguiand future module family roots if they exist as package indexes- explicit names declared in
[indices] - explicit dependency table roots such as
[dependencies.acme]
Unquoted dotted selectors in the single [dependencies] table do not create a
new namespace unconditionally. They create an ordered lookup:
a.b.c -> mcpplibs.a.b/c, then a.b/c
This is the key rule: mcpplibs has priority because it may be omitted, but
a.b remains a valid peer namespace fallback.
For custom index names, the deterministic rule should be:
- If an explicit subtable is used, treat the subtable name as the root.
- If a single-table dotted selector is used, try the omitted-mcpplibs candidate first, then the literal peer-root candidate.
- If users need to bypass priority matching, they can use an explicit subtable
such as
[dependencies.compat]or a fully explicit selector such asmcpplibs.<name>....
This avoids unordered probing while still giving users an escape hatch.
Recommended API shape:
struct DependencySelectorContext {
std::unordered_set<std::string> explicitNamespaceRoots;
std::string defaultNamespace = "mcpplibs";
};
struct DependencySelector {
std::vector<PackageCoordinate> candidates;
std::string stableMapKey;
bool explicitRoot = false;
};
DependencySelector resolve_dependency_selector(
std::span<const std::string> segments,
const DependencySelectorContext& context);The important design point is not the exact C++ names. The important point is
that all dependency entry points call one resolver instead of each splitting on
. independently.
Apply the resolver at the edges:
src/manifest.cppm: root dependencies, dev-dependencies, build-dependencies.src/manifest.cppm: workspace dependencies.src/manifest.cppm: synthesized dependencies from Lua xpkgmcpp.deps.src/pm/commands.cppm:mcpp add,mcpp remove, and related CLI parsing.- Documentation examples in
docs/05-mcpp-toml.md.
After candidate generation, package fetch/build/resolution should select the first available structured package coordinate. That keeps user-facing selector syntax out of lower build layers while preserving the requested priority matching behavior.
Keep all existing supported forms:
[dependencies] cmdline = "..."[dependencies.mcpplibs] cmdline = "..."[dependencies.compat] gtest = "..."- quoted
"mcpplibs.cmdline" = "..." - quoted legacy dotted keys where currently accepted
- path/git inline specs
- visibility/use requirements from the existing dependency model
For one-segment omitted-mcpplibs dependencies, keep the stable map key as the
old bare key (cmdline) to avoid unnecessary lockfile or update churn. For
multi-segment selectors, preserving user spelling such as imgui.core is more
appropriate than rewriting everything to mcpplibs.imgui.core, because the
selector is an ordered match rather than a forced namespace rewrite.
Quoted flat dotted keys should remain compatibility input, not the primary new syntax. The canonical user-facing syntax should be unquoted TOML dotted keys or explicit dependency subtables.
Recommended. It gives one deterministic rule set and keeps compatibility logic at the ingestion boundary.
Rejected. It may fix the first failing test but preserves divergent behavior between root deps, workspace deps, xpkg deps, and CLI commands.
Rejected. Arbitrary probing that changes priority based on current index contents is fragile. Ordered candidate lookup is different: the selector has a fixed priority list, and package resolution selects the first candidate that is available.
Unit tests:
- Selector matrix in the PM layer.
- Root dependency dotted selectors.
- Dev/build dependency dotted selectors.
- Workspace dependency dotted selectors.
- Synthesized xpkg
mcpp.depsdotted selectors. - CLI add/remove selector normalization.
- Quoted legacy dotted key compatibility.
- Explicit custom index namespace behavior.
End-to-end tests:
- A local-index package using
compat.gtest. - A local-index package using
imgui.core. - A local-index package using
imgui.backend.glfw_opengl3.
Before running local tests, pin the shell to the intended mcpp version:
xlings use mcpp 0.0.42
mcpp test- 2026-06-02: Added
mcpp.pm.dependency_selectorwith ordered candidates. - 2026-06-02: Extended
DependencySpecwith ordered candidate coordinates. - 2026-06-02: Updated root dependencies, dev/build dependencies, workspace
dependencies, and xpkg
mcpp.depssynthesis to preserve dotted selector spelling while recording candidate priority. - 2026-06-02: Updated dependency resolution to select the first candidate whose
strict canonical xpkg.lua entry exists. This avoids legacy fallback lookup
accidentally treating
compat.gtestasmcpplibs.compat/gtest. - 2026-06-02: Added unit coverage for selector candidates and manifest parsing.
- 2026-06-02: Added e2e coverage for
imgui.corefalling back frommcpplibs.imgui/coreto peer-rootimgui/core. - 2026-06-02: Updated
mcpp addso dotted input stays in the single[dependencies]table;ns:nameremains the explicit subtable syntax. - 2026-06-02: Full unit tests and targeted local-index/preinstall e2e checks pass locally.
- 2026-06-02: Bumped the pending release version to
0.0.43and added the changelog entry for dotted dependency selectors. - 2026-06-02: CI exposed that
--versionalso uses the hardcodedMCPP_VERSIONinsrc/toolchain/fingerprint.cppm; synchronized it to0.0.43.
Current local checks:
xlings use mcpp 0.0.42
mcpp test -- --gtest_filter='DependencySelector.*:Manifest.DependenciesDottedSelectorPreservesUserKeyAndCandidates:Manifest.DependenciesNamespacedSubtableNestedDottedKeyIsCanonical:SynthesizeFromXpkgLua.DepsDottedSelectorsUseManifestRules:Manifest.WorkspaceDependenciesUseDottedSelectorRules'
mcpp build
MCPP=target/.../bin/mcpp bash tests/e2e/62_dotted_dependency_selector_priority.sh
MCPP=target/.../bin/mcpp bash tests/e2e/12_add_command.sh
MCPP=target/.../bin/mcpp bash tests/e2e/52_local_path_namespaced_index.sh
MCPP=target/.../bin/mcpp bash tests/e2e/58_preinstall_mcpp_deps_for_hooks.sh- Declared custom
[indices]roots do not turn single-table dotted selectors into explicit roots. Single-table selectors still use omitted-mcpplibs priority: trymcpplibs.<path>first, then<peer-root>.<path>. - The selector resolver lives in
src/pm/dependency_selector.cppm.compat.cppmremains focused on legacy dotted-key and xpkg filename compatibility. - CLI add/remove preserve user spelling for dotted selectors. Explicit
namespace subtables remain available through
ns:name.