Skip to content

feat: namespace-aware dependencies (xpkg-style)#2

Merged
Sunrisepeak merged 4 commits into
mainfrom
feat/namespace-support
May 8, 2026
Merged

feat: namespace-aware dependencies (xpkg-style)#2
Sunrisepeak merged 4 commits into
mainfrom
feat/namespace-support

Conversation

@Sunrisepeak
Copy link
Copy Markdown
Member

Summary

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 switching to TOML-native subtables, while keeping the legacy
dotted form parseable for backward compatibility.

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)

CLI accepts <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               # legacy dotted, normalised on write

mcpp remove parses the same forms and prunes the subtable header when
no entries remain.

What changed under the hood

  • DependencySpec gains namespace_ + shortName. Map key stays the
    composite <ns>.<name> for non-default ns (existing fetcher / lockfile
    paths keep working) and bare <name> for default ns.
  • 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 covering flat / subtable / legacy dotted /
    inline-spec-coexistence / xpkg-segment dotted forms.
  • tests/e2e/12_add_command.sh rewritten: no-quote default-ns output,
    namespaced subtable header, multi-pkg under same ns, legacy input
    acceptance, 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 and link.

Follow-up

.agents/docs/2026-05-08-package-index-config.md sketches the next
layer (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)
  • Full e2e suite locally: 30 pass, 3 fail — all 3 are pre-existing
    (28_target_static, 30_pack_modes) or environment-only and
    reproduce on main unchanged.
  • CI green

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).
@Sunrisepeak Sunrisepeak merged commit 25380b0 into main May 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant