feat: namespace-aware dependencies (xpkg-style)#2
Merged
Conversation
Reuses the xpkg V1 spec's `namespace` field as a first-class dimension of
the dependency model. Resolves the "must quote keys with dots" friction in
mcpp.toml by adopting TOML-native subtables.
User-facing surface:
[dependencies] # → (mcpp, gtest) default ns, no quotes
gtest = "1.15.2"
[dependencies.mcpplibs] # → (mcpplibs, cmdline) / (mcpplibs, templates)
cmdline = "0.0.2"
templates = { version = "0.0.1" }
[dependencies] # legacy quoted-dotted form still parsed
"acme.foo" = "1.0.0" # → (acme, foo) + future deprecation
CLI parses `<ns>:<name>@<ver>` (xpkg/xim convention):
mcpp add gtest@1.15.2 → flat default ns
mcpp add mcpplibs:cmdline@0.0.2 → [dependencies.mcpplibs] subtable
mcpp add acme.util@2.0.0 → same (legacy dotted accepted)
`mcpp remove` parses the same forms and prunes the subtable header when no
entries remain.
Internals:
* `DependencySpec` gains `namespace_` + `shortName`. Map key stays the
composite `<ns>.<name>` for non-default ns (so existing fetcher /
lockfile lookup-by-key keeps working) and bare `<name>` for the default
ns (so the common case stays untouched).
* `synthesize_from_xpkg_lua` mirrors the same split for `deps = { ... }`
inside the `mcpp = {}` xpkg segment.
* `kDefaultNamespace = "mcpp"` published from `mcpp::manifest`.
* Path-dep name-mismatch check now compares the dep's *short* name
against the resolved package's `[package].name`, so a path dep can be
pulled in under any namespace label.
Coverage:
* 5 new unit tests in `test_manifest.cpp` exercising flat / subtable /
legacy dotted / inline-spec-coexistence / xpkg-segment dotted forms.
* `tests/e2e/12_add_command.sh` rewritten to assert no-quote default-ns
output, namespaced subtable header, multi-pkg under same ns, legacy
`<ns>.<name>` input acceptance, and bad input rejection.
* `tests/e2e/23_remove_update.sh` updated for unquoted output.
* New `tests/e2e/27_namespace_dependencies.sh` builds & runs a path
dep via the namespaced subtable form, the legacy quoted form, and the
flat default-ns form — all three resolve to the same package and link.
Follow-up: `.agents/docs/2026-05-08-package-index-config.md` sketches
the next layer (per-namespace index repository + commit pinning) so
private / mirrored indices and reproducible-build SHA pinning land on
top of this foundation.
Index descriptors in the current mcpp-index repo embed the namespace in the package's `[package].name` string (e.g. `name = "mcpplibs.cmdline"`) rather than using the xpkg-spec separate `namespace = "mcpplibs"; name = "cmdline"` form. Until the index is migrated, accept both: * the new short form (`name == "cmdline"`) * the legacy composite (`name == "mcpplibs.cmdline"`) Restores the self-host smoke step in CI which builds mcpp using the freshly-built mcpp; that path resolves `mcpplibs.cmdline` from the released index repo, hits the legacy descriptor, and previously failed with `mismatch with declared name 'cmdline'`.
Replace the "branch = '' = no lock" wording in §5.1 (which contradicted
§3.3's "unlocked → resolve → write back" rule and would have preserved
today's non-determinism). Realign on:
* `mcpp.lock` always pins a concrete commit sha, even when `mcpp.toml`
has no `[indices]` section.
* Empty rev/tag/branch only means "I didn't pick explicitly", which on
first build resolves to the remote default branch's HEAD and is then
locked.
* Subsequent builds read the locked sha offline.
* `mcpp index update` / `mcpp update` are the only commands that
rewrite the locked sha; build / run / test / pack never do.
Also adds a today-vs-design comparison table to §5.1 and a stricter
flow description to §3.3.
Add §4.5 making explicit the two-layer guarantee that an earlier reading
of the doc was conflating:
L1 — package-publish immutability (policy, future mcpp-index CI gate).
Once `<ns>:<name>@<ver>` is published, its url / sha256 / namespace
fields are frozen; only new versions may be added; deletions go
through an explicit yank flow.
L2 — index-snapshot lock (mechanism, mcpp.lock).
Each project pins the index commit sha it was last built against;
build/run/test/pack always read that sha offline. Force-push or
accidental policy violations on the upstream index never reach a
project until it explicitly runs `mcpp index update`.
L1 is the trust layer, L2 is the failsafe. Together they give CI /
team-wide reproducibility even when the index repo is just a normal git
repository (no server-side immutability the way crates.io enforces).
Also adjust the cargo / npm comparison table in Appendix A to surface
both layers, and rewrite the "core difference" paragraph to explain why
mcpp keeps L2 even after L1 is in place (third-party indices, force-push
recovery, audit window).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reuses the xpkg V1 spec's
namespacefield as a first-class dimension of thedependency model. Resolves the "must quote keys with dots" friction in
mcpp.tomlby switching to TOML-native subtables, while keeping the legacydotted form parseable for backward compatibility.
User-facing surface
CLI accepts
<ns>:<name>@<ver>(xpkg/xim convention):mcpp removeparses the same forms and prunes the subtable header whenno entries remain.
What changed under the hood
DependencySpecgainsnamespace_+shortName. Map key stays thecomposite
<ns>.<name>for non-default ns (existing fetcher / lockfilepaths keep working) and bare
<name>for default ns.synthesize_from_xpkg_luamirrors the same split fordeps = { ... }inside the
mcpp = {}xpkg segment.kDefaultNamespace = "mcpp"published frommcpp::manifest.against the resolved package's
[package].name, so a path dep canbe pulled in under any namespace label.
Coverage
inline-spec-coexistence / xpkg-segment dotted forms.
tests/e2e/12_add_command.shrewritten: no-quote default-ns output,namespaced subtable header, multi-pkg under same ns, legacy input
acceptance, bad input rejection.
tests/e2e/23_remove_update.shupdated for unquoted output.tests/e2e/27_namespace_dependencies.shbuilds & runs a pathdep via the namespaced subtable form, the legacy quoted form, and
the flat default-ns form — all three resolve and link.
Follow-up
.agents/docs/2026-05-08-package-index-config.mdsketches the nextlayer (per-namespace index repository + commit-sha pinning) so private /
mirrored indices and reproducible-build SHA pinning land on top of this
foundation. Spec only; not implemented in this PR.
Test plan
mcpp build(worktree)mcpp test— 9 unit binaries pass (incl. 5 new manifest tests)tests/e2e/12_add_command.sh(rewritten)tests/e2e/23_remove_update.sh(adapted)tests/e2e/26_c_language_support.sh(regression check)tests/e2e/27_namespace_dependencies.sh(new)(
28_target_static,30_pack_modes) or environment-only andreproduce on
mainunchanged.