feat(deps): transitive dependency walker + version-conflict detection#17
Merged
Sunrisepeak merged 1 commit intomainfrom May 9, 2026
Merged
feat(deps): transitive dependency walker + version-conflict detection#17Sunrisepeak merged 1 commit intomainfrom
Sunrisepeak merged 1 commit intomainfrom
Conversation
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.
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
Closes the gap that forced consumers of
mcpplibs.llmapito alsoexplicitly list
mbedtlsbecause its transitivetinyhttps → mbedtlschain 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_dirspropagate to the main compile baseline; its own[dependencies]are queued for the walk.[dev-dependencies]areseeded only from the main manifest (private to each package's own
test runs).
Conflict policy
Same
(ns, name)resolved to two different exact versions → errorlisting 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[dependencies.mcpplibs] llmapi = "0.2.4"now builds and linkslibllmapi.a(with mbedtls + tinyhttps auto-pulled) without theconsumer needing to know the dep graph.