The ImGui module experiment exposed two separate issues in mcpp:
- C++ compile flags computed
include_flagsbut did not place them in the finalcxxflagsstring. That is a correctness bug in flag assembly. - The current dependency include model is too coarse for a module-first
package ecosystem.
build.include_dirsis used both to build a package and as the package's consumer-visible include surface.
For traditional header packages the symmetric behavior is convenient. For
module packages it is too broad: a package can include upstream headers
internally while exposing only import package.module; to consumers.
This document is the live target for the current PR. The architecture should
not be a thin wrapper around BuildConfig.includeDirs; it should make package
usage requirements explicit in the resolved package graph and move consumers
onto that graph incrementally inside this PR.
- Keep existing package descriptors working during migration.
- Treat modules as the primary public surface for module packages.
- Distinguish package-private build requirements from consumer-visible usage requirements.
- Make scanner, P1689 scan, compile commands, and real compile rules consume the same resolved usage model.
- Keep link/runtime propagation separate from header/include propagation.
- Avoid forcing users of module packages to see or declare implementation headers.
- Private build requirements:
- Requirements needed to compile a package's own sources.
- Example:
mcpplibs.imguiwrappers need Dear ImGui backend headers internally.
- Public usage requirements:
- Requirements that consumers inherit when depending on the package.
- Example:
compat.glfwexportsGLFW/glfw3.has a traditional header surface.
- Link requirements:
- Libraries and linker flags that follow objects into final link units.
- These are independent from whether headers are public.
The resolved build graph should own usage requirements as first-class data rather than mutating manifests during dependency resolution:
enum class Visibility {
Private,
Public,
Interface,
};
struct UsageRequirements {
std::vector<std::filesystem::path> includeDirs;
std::vector<std::string> cflags;
std::vector<std::string> cxxflags;
std::vector<std::string> ldflags;
std::vector<std::string> modules;
};
struct PackageNode {
Manifest manifest;
UsageRequirements privateBuild;
UsageRequirements publicUsage;
UsageRequirements linkUsage;
};
struct DependencyEdge {
PackageId from;
PackageId to;
Visibility visibility;
};The important property is that Manifest remains the parsed package
description. Resolved requirements live in the package graph and build plan,
not by appending dependency state back into the manifest.
Use CMake-like visibility:
private- The dependency is needed to compile/link the package itself.
- Its usage requirements are not propagated to consumers.
public- The dependency is needed to compile/link the package itself.
- Its public usage requirements are propagated to consumers.
interface- The package itself does not compile/link against the dependency.
- Consumers inherit the dependency's public usage requirements.
Future manifest surface:
[dependencies.compat]
imgui = { version = "1.92.8", visibility = "private" }
glfw = { version = "3.4", visibility = "private" }Compatibility rule: old dependency declarations default to public, matching
current mcpp behavior.
Resolution should build a package graph:
- Parse manifests without rewriting them.
- Create
PackageNodeentries for root and dependencies. - Create
DependencyEdgeentries with resolved visibility. - Compute each node's effective private build requirements from:
- the package's own build requirements,
- private/public dependency requirements,
- platform/toolchain requirements.
- Compute each node's public usage requirements from:
- the package's declared public/interface requirements,
- public/interface dependency requirements.
- Compute link closure independently from include/header closure.
Scanner, P1689 scan, compile commands, and Ninja compile rules should all derive from each compile unit's resolved private build requirements. A module import edge should require BMI/object/link availability, not implementation header visibility.
imgui-private should model upstream headers as private implementation
requirements:
compat.imgui: private build/link requirement for wrappers and backend implementation files.compat.glfw: private build/link requirement for GLFW backend modules.compat.opengl: private build requirement for OpenGL backend headers.
Consumers of the module package should only need:
import imgui.core;
import imgui.backend.glfw_opengl3;Consumers that want to write direct GLFW/OpenGL header code should declare
compat.glfw or compat.opengl themselves.
- Parse dependency visibility:
- Add
visibility = "private" | "public" | "interface"to dependency specs. - Default omitted visibility to
public, preserving existing packages.
- Add
- Introduce resolved usage graph data:
- Add
UsageRequirementsand dependency edge visibility to the package graph surface currently represented byPackageRoot. - Keep parsed
Manifestimmutable during dependency resolution for include propagation.
- Add
- Compute usage requirements during dependency resolution:
- Map a package's legacy
build.include_dirsto its own private build requirements and public usage requirements. - For
privateandpublicedges, make dependency public usage visible to the dependent package's private build. - For
publicandinterfaceedges, propagate dependency public usage to the dependent package's public usage.
- Map a package's legacy
- Make build consumers use resolved usage:
- Regex scanner, P1689 scanner, compile commands, and Ninja compile rules should read the same per-package resolved private build include dirs.
- Keep link flag propagation in the existing path for this PR; link usage is a separate axis in the model and should be migrated after include usage is proven by tests.
- Keep
build.cflagsandbuild.cxxflagspackage-private in this PR; public compile definitions/options need a dedicated manifest surface before they should propagate to consumers.
- Preserve the already discovered fixes:
- Keep the C++
include_flagsassembly regression fix. - Keep stale unmarked xpkg install recovery.
- Keep the C++
- Release path:
- Open PR, wait for CI, and admin squash merge after approval.
- Do not trigger the
0.0.43release from this PR. - Defer version bump / release / package-index / xlings-res rollout until the next requested scheme is implemented and released together.
Implement:
- The C++
include_flagsassembly fix. - Focused tests for the C++ include flag behavior.
- The stale unmarked xpkg install recovery already discovered during ImGui validation.
- Dependency visibility parsing and validation.
- Resolved include usage requirements for
PackageRoot. - Dependency-edge include propagation without mutating parsed manifests.
- Scanner/build-plan consumption of resolved include usage.
- PR CI and admin squash merge.
Leave for follow-up after this PR:
- Full
linkUsagemigration away fromManifest::buildConfig.ldflagsmutation. 0.0.43version bump and release trigger.- package-index / xlings-res rollout for the released mcpp version.
- Generated module wrapper surface expansion for ImGui.
- mcpp-index publication of ImGui after the module package has a public source repository and the required mcpp release is available.
- Done: dependency specs carry a
visibilityfield with parser validation for TOML descriptors. - Done: C++
include_flagsassembly fix and unit coverage. - Done: stale unmarked xpkg cache recovery path and e2e coverage.
- Done: resolved include usage graph and scanner/build-plan consumers.
- Done: targeted unit/e2e coverage for dependency visibility propagation.
- Done: compatibility checks for existing public transitive include behavior.
- Done: local verification with
mcpp testand full e2e suite. - Pending: PR, CI, and admin squash merge.
- Deferred by user decision:
0.0.43version bump, release trigger, xim-pkgindex update, and xlings-res rollout.
mcpp build: passed.mcpp test: passed (18 passed; 0 failed).tests/e2e/run_all.sh: passed (65 passed, 0 failed, 0 skipped).- Targeted checks:
tests/e2e/31_transitive_deps.sh: passed.tests/e2e/50_package_owned_build_flags.sh: passed.tests/e2e/60_stale_xpkg_cache_reinstall.sh: passed.tests/e2e/61_dependency_visibility.sh: passed.