Skip to content

feat(deps): transitive dependency walker + version-conflict detection#17

Merged
Sunrisepeak merged 1 commit intomainfrom
feat/transitive-deps
May 9, 2026
Merged

feat(deps): transitive dependency walker + version-conflict detection#17
Sunrisepeak merged 1 commit intomainfrom
feat/transitive-deps

Conversation

@Sunrisepeak
Copy link
Copy Markdown
Member

Summary

Closes the gap that forced consumers of mcpplibs.llmapi to also
explicitly list mbedtls because its transitive tinyhttps → mbedtls
chain wasn't walked.

What changes

The dep prepare phase becomes a worklist BFS over the full transitive
graph. Each unique (namespace, shortName) is fetched once; its
[build].include_dirs propagate to the main compile baseline; its own
[dependencies] are queued for the walk. [dev-dependencies] are
seeded only from the main manifest (private to each package's own
test runs).

Conflict policy

Same (ns, name) resolved to two different exact versions → error
listing both requesters. C++ modules require a single global module
name and ODR-respecting symbols, so multiple versions of the same
package can't coexist in one build (same constraint as cargo for
non-Rust targets, vcpkg, conan, etc.). Type-clash (a dep is path on
one path through the graph and version on another) is also rejected.

Verification

  • mcpp build (worktree)
  • mcpp test — 9/9 unit binaries pass
  • e2e regression: 02 / 09 / 12 / 23 / 26 / 27 / 31 (new) all pass
  • Real-world end-to-end: a fresh project that declares only
    [dependencies.mcpplibs] llmapi = "0.2.4" now builds and links
    libllmapi.a (with mbedtls + tinyhttps auto-pulled) without the
    consumer needing to know the dep graph.
  • Full CI green

Before this patch the dep loop only iterated `m->dependencies` once —
direct deps' `[build].include_dirs` propagated, but their own
`[dependencies]` didn't get fetched, didn't appear in the modgraph,
and their includes weren't visible while compiling them. Concretely:
a project depending on `mcpplibs.tinyhttps` would have to also
explicitly list `mbedtls = "3.6.1"`, otherwise tinyhttps's `tls.cppm`
failed to find `<mbedtls/ssl.h>`.

Replace the single-pass loop with a worklist algorithm:

  worklist  = seed from main manifest's [dependencies]
              (and [dev-dependencies] when --tests)
  resolved  = map<(ns, name), {version, requestedBy, source}>

  while worklist not empty:
    item = pop
    pin SemVer constraint to a concrete version (one shared lambda)
    key  = (item.ns, item.shortName)

    if resolved[key] exists:
       compare source kinds (path/git/version) — clash = error
       compare exact versions                  — clash = error
       same     → skip (already processed)

    fetch the dep, load its manifest, propagate its include_dirs,
    record into resolved[key] and packages

    for each child in dep_manifest.dependencies:
       worklist.push(child, requestedBy = this dep)

`[dev-dependencies]` are seeded from the main manifest only; transitive
walks intentionally skip them because dev-deps are private to the
package's own test runs, not part of its public ABI.

Conflict messages name both requesters so users can see the path
through the graph that produced the disagreement, e.g.

  dependency 'mcpp.D' has conflicting versions in the transitive graph:
    '1.0.0' requested by 'B@2.0'
    '2.0.0' requested by 'C@1.5'
  C++ modules require a single global version of each package.

(Single-version semantics is forced by C++ modules — module names + ODR
are global, multiple versions of the same package can't coexist in one
build. Same constraint as cargo for non-Rust targets, vcpkg, conan, etc.)

Storage detail: dep manifests are now held by `vector<unique_ptr>` so
PackageRoot's reference into the manifest stays stable when the
worklist appends new entries during the walk.

Coverage:
* `tests/e2e/31_transitive_deps.sh` — three-level path-dep chain
  (top → ch → gc): top declares only ch, gc's include_dirs reach ch's
  compile rule, link succeeds, runtime returns the right answer.
  Also exercises a duplicate-but-consistent dep reachable through two
  edges.
* Verified end-to-end against `mcpplibs.llmapi`: a fresh project that
  declares only `mcpplibs.llmapi = "0.2.4"` builds and links cleanly,
  with mbedtls + mcpplibs.tinyhttps pulled automatically by the
  walker.
@Sunrisepeak Sunrisepeak merged commit b835590 into main May 9, 2026
1 of 2 checks 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