|
| 1 | +# Module-First Usage Requirements Architecture |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +The ImGui module experiment exposed two separate issues in mcpp: |
| 6 | + |
| 7 | +1. C++ compile flags computed `include_flags` but did not place them in the |
| 8 | + final `cxxflags` string. That is a correctness bug in flag assembly. |
| 9 | +2. The current dependency include model is too coarse for a module-first |
| 10 | + package ecosystem. `build.include_dirs` is used both to build a package and |
| 11 | + as the package's consumer-visible include surface. |
| 12 | + |
| 13 | +For traditional header packages the symmetric behavior is convenient. For |
| 14 | +module packages it is too broad: a package can include upstream headers |
| 15 | +internally while exposing only `import package.module;` to consumers. |
| 16 | + |
| 17 | +This document is the live target for the current PR. The architecture should |
| 18 | +not be a thin wrapper around `BuildConfig.includeDirs`; it should make package |
| 19 | +usage requirements explicit in the resolved package graph and move consumers |
| 20 | +onto that graph incrementally inside this PR. |
| 21 | + |
| 22 | +## Design Goals |
| 23 | + |
| 24 | +- Keep existing package descriptors working during migration. |
| 25 | +- Treat modules as the primary public surface for module packages. |
| 26 | +- Distinguish package-private build requirements from consumer-visible usage |
| 27 | + requirements. |
| 28 | +- Make scanner, P1689 scan, compile commands, and real compile rules consume |
| 29 | + the same resolved usage model. |
| 30 | +- Keep link/runtime propagation separate from header/include propagation. |
| 31 | +- Avoid forcing users of module packages to see or declare implementation |
| 32 | + headers. |
| 33 | + |
| 34 | +## Terms |
| 35 | + |
| 36 | +- Private build requirements: |
| 37 | + - Requirements needed to compile a package's own sources. |
| 38 | + - Example: `mcpplibs.imgui` wrappers need Dear ImGui backend headers |
| 39 | + internally. |
| 40 | +- Public usage requirements: |
| 41 | + - Requirements that consumers inherit when depending on the package. |
| 42 | + - Example: `compat.glfw` exports `GLFW/glfw3.h` as a traditional header |
| 43 | + surface. |
| 44 | +- Link requirements: |
| 45 | + - Libraries and linker flags that follow objects into final link units. |
| 46 | + - These are independent from whether headers are public. |
| 47 | + |
| 48 | +## Target Data Model |
| 49 | + |
| 50 | +The resolved build graph should own usage requirements as first-class data |
| 51 | +rather than mutating manifests during dependency resolution: |
| 52 | + |
| 53 | +```cpp |
| 54 | +enum class Visibility { |
| 55 | + Private, |
| 56 | + Public, |
| 57 | + Interface, |
| 58 | +}; |
| 59 | + |
| 60 | +struct UsageRequirements { |
| 61 | + std::vector<std::filesystem::path> includeDirs; |
| 62 | + std::vector<std::string> cflags; |
| 63 | + std::vector<std::string> cxxflags; |
| 64 | + std::vector<std::string> ldflags; |
| 65 | + std::vector<std::string> modules; |
| 66 | +}; |
| 67 | + |
| 68 | +struct PackageNode { |
| 69 | + Manifest manifest; |
| 70 | + UsageRequirements privateBuild; |
| 71 | + UsageRequirements publicUsage; |
| 72 | + UsageRequirements linkUsage; |
| 73 | +}; |
| 74 | + |
| 75 | +struct DependencyEdge { |
| 76 | + PackageId from; |
| 77 | + PackageId to; |
| 78 | + Visibility visibility; |
| 79 | +}; |
| 80 | +``` |
| 81 | +
|
| 82 | +The important property is that `Manifest` remains the parsed package |
| 83 | +description. Resolved requirements live in the package graph and build plan, |
| 84 | +not by appending dependency state back into the manifest. |
| 85 | +
|
| 86 | +## Dependency Visibility Semantics |
| 87 | +
|
| 88 | +Use CMake-like visibility: |
| 89 | +
|
| 90 | +- `private` |
| 91 | + - The dependency is needed to compile/link the package itself. |
| 92 | + - Its usage requirements are not propagated to consumers. |
| 93 | +- `public` |
| 94 | + - The dependency is needed to compile/link the package itself. |
| 95 | + - Its public usage requirements are propagated to consumers. |
| 96 | +- `interface` |
| 97 | + - The package itself does not compile/link against the dependency. |
| 98 | + - Consumers inherit the dependency's public usage requirements. |
| 99 | +
|
| 100 | +Future manifest surface: |
| 101 | +
|
| 102 | +```toml |
| 103 | +[dependencies.compat] |
| 104 | +imgui = { version = "1.92.8", visibility = "private" } |
| 105 | +glfw = { version = "3.4", visibility = "private" } |
| 106 | +``` |
| 107 | + |
| 108 | +Compatibility rule: old dependency declarations default to `public`, matching |
| 109 | +current mcpp behavior. |
| 110 | + |
| 111 | +## Build Graph Behavior |
| 112 | + |
| 113 | +Resolution should build a package graph: |
| 114 | + |
| 115 | +1. Parse manifests without rewriting them. |
| 116 | +2. Create `PackageNode` entries for root and dependencies. |
| 117 | +3. Create `DependencyEdge` entries with resolved visibility. |
| 118 | +4. Compute each node's effective private build requirements from: |
| 119 | + - the package's own build requirements, |
| 120 | + - private/public dependency requirements, |
| 121 | + - platform/toolchain requirements. |
| 122 | +5. Compute each node's public usage requirements from: |
| 123 | + - the package's declared public/interface requirements, |
| 124 | + - public/interface dependency requirements. |
| 125 | +6. Compute link closure independently from include/header closure. |
| 126 | + |
| 127 | +Scanner, P1689 scan, compile commands, and Ninja compile rules should all derive |
| 128 | +from each compile unit's resolved private build requirements. A module import |
| 129 | +edge should require BMI/object/link availability, not implementation header |
| 130 | +visibility. |
| 131 | + |
| 132 | +## ImGui Implications |
| 133 | + |
| 134 | +`imgui-private` should model upstream headers as private implementation |
| 135 | +requirements: |
| 136 | + |
| 137 | +- `compat.imgui`: private build/link requirement for wrappers and backend |
| 138 | + implementation files. |
| 139 | +- `compat.glfw`: private build/link requirement for GLFW backend modules. |
| 140 | +- `compat.opengl`: private build requirement for OpenGL backend headers. |
| 141 | + |
| 142 | +Consumers of the module package should only need: |
| 143 | + |
| 144 | +```cpp |
| 145 | +import imgui.core; |
| 146 | +import imgui.backend.glfw_opengl3; |
| 147 | +``` |
| 148 | + |
| 149 | +Consumers that want to write direct GLFW/OpenGL header code should declare |
| 150 | +`compat.glfw` or `compat.opengl` themselves. |
| 151 | + |
| 152 | +## Current PR Plan |
| 153 | + |
| 154 | +1. Parse dependency visibility: |
| 155 | + - Add `visibility = "private" | "public" | "interface"` to dependency |
| 156 | + specs. |
| 157 | + - Default omitted visibility to `public`, preserving existing packages. |
| 158 | +2. Introduce resolved usage graph data: |
| 159 | + - Add `UsageRequirements` and dependency edge visibility to the package |
| 160 | + graph surface currently represented by `PackageRoot`. |
| 161 | + - Keep parsed `Manifest` immutable during dependency resolution for include |
| 162 | + propagation. |
| 163 | +3. Compute usage requirements during dependency resolution: |
| 164 | + - Map a package's legacy `build.include_dirs` to its own private build |
| 165 | + requirements and public usage requirements. |
| 166 | + - For `private` and `public` edges, make dependency public usage visible to |
| 167 | + the dependent package's private build. |
| 168 | + - For `public` and `interface` edges, propagate dependency public usage to |
| 169 | + the dependent package's public usage. |
| 170 | +4. Make build consumers use resolved usage: |
| 171 | + - Regex scanner, P1689 scanner, compile commands, and Ninja compile rules |
| 172 | + should read the same per-package resolved private build include dirs. |
| 173 | + - Keep link flag propagation in the existing path for this PR; link usage is |
| 174 | + a separate axis in the model and should be migrated after include usage is |
| 175 | + proven by tests. |
| 176 | + - Keep `build.cflags` and `build.cxxflags` package-private in this PR; |
| 177 | + public compile definitions/options need a dedicated manifest surface |
| 178 | + before they should propagate to consumers. |
| 179 | +5. Preserve the already discovered fixes: |
| 180 | + - Keep the C++ `include_flags` assembly regression fix. |
| 181 | + - Keep stale unmarked xpkg install recovery. |
| 182 | +6. Release path: |
| 183 | + - Open PR, wait for CI, and admin squash merge after approval. |
| 184 | + - Do not trigger the `0.0.43` release from this PR. |
| 185 | + - Defer version bump / release / package-index / xlings-res rollout until |
| 186 | + the next requested scheme is implemented and released together. |
| 187 | + |
| 188 | +## Current PR Scope |
| 189 | + |
| 190 | +Implement: |
| 191 | + |
| 192 | +- The C++ `include_flags` assembly fix. |
| 193 | +- Focused tests for the C++ include flag behavior. |
| 194 | +- The stale unmarked xpkg install recovery already discovered during ImGui |
| 195 | + validation. |
| 196 | +- Dependency visibility parsing and validation. |
| 197 | +- Resolved include usage requirements for `PackageRoot`. |
| 198 | +- Dependency-edge include propagation without mutating parsed manifests. |
| 199 | +- Scanner/build-plan consumption of resolved include usage. |
| 200 | +- PR CI and admin squash merge. |
| 201 | + |
| 202 | +Leave for follow-up after this PR: |
| 203 | + |
| 204 | +- Full `linkUsage` migration away from `Manifest::buildConfig.ldflags` |
| 205 | + mutation. |
| 206 | +- `0.0.43` version bump and release trigger. |
| 207 | +- package-index / xlings-res rollout for the released mcpp version. |
| 208 | +- Generated module wrapper surface expansion for ImGui. |
| 209 | +- mcpp-index publication of ImGui after the module package has a public source |
| 210 | + repository and the required mcpp release is available. |
| 211 | + |
| 212 | +## Live Status |
| 213 | + |
| 214 | +- Done: dependency specs carry a `visibility` field with parser validation for |
| 215 | + TOML descriptors. |
| 216 | +- Done: C++ `include_flags` assembly fix and unit coverage. |
| 217 | +- Done: stale unmarked xpkg cache recovery path and e2e coverage. |
| 218 | +- Done: resolved include usage graph and scanner/build-plan consumers. |
| 219 | +- Done: targeted unit/e2e coverage for dependency visibility propagation. |
| 220 | +- Done: compatibility checks for existing public transitive include behavior. |
| 221 | +- Done: local verification with `mcpp test` and full e2e suite. |
| 222 | +- Pending: PR, CI, and admin squash merge. |
| 223 | +- Deferred by user decision: `0.0.43` version bump, release trigger, |
| 224 | + xim-pkgindex update, and xlings-res rollout. |
| 225 | + |
| 226 | +## Local Verification |
| 227 | + |
| 228 | +- `mcpp build`: passed. |
| 229 | +- `mcpp test`: passed (`18 passed; 0 failed`). |
| 230 | +- `tests/e2e/run_all.sh`: passed (`65 passed, 0 failed, 0 skipped`). |
| 231 | +- Targeted checks: |
| 232 | + - `tests/e2e/31_transitive_deps.sh`: passed. |
| 233 | + - `tests/e2e/50_package_owned_build_flags.sh`: passed. |
| 234 | + - `tests/e2e/60_stale_xpkg_cache_reinstall.sh`: passed. |
| 235 | + - `tests/e2e/61_dependency_visibility.sh`: passed. |
0 commit comments