mcpplibs.mwrap.boost@1.92.0· English · 简体中文 Consumer guide: usage.md
A module wrapper: Boost 1.92.0's headers stay untouched upstream code, and a C++23 module layer makes them importable while preserving the upstream API. The wrapper does not re-implement Boost, does not relocate its entities into a new namespace, and does not link a second Boost implementation.
- 116 library module interfaces + the root module + the macro mapping module.
- 22 pure include-only libraries and 2 compiled include-only features (
log,unit-test-framework): consumed by header, listed in usage.md. - 11 libraries need external toolchains/SDKs and are not declared.
boost-m/
├── mcpp.toml package identity, features, targets (generated blocks)
├── build.mcpp root-closure partition generator
├── upstream/boost/ pinned official Boost tree (pruned) + SOURCE.md
├── upstream/boost-module/ retained pre-mwrap wrapper licence/README/history
├── patches/ vendored adaptations of upstream/boost (apply_patches.py)
├── port/ wrapper TUs (system/thread/leaf extras)
├── port/include/mwrap/boost/ <mwrap/boost/macros.hpp> include entry
├── src/ hand-maintained module facades, root module, macros module
├── generated/exports/ export lists (.inc) and edges (.deps) — generator output only
├── generated/meta/ api-surface.json, skipped.txt, lists/maps build.mcpp reads
├── templates/ basic (default) and lib templates
├── tests/ one standalone program per file
├── examples/ basic, git-dep, profiles consumers
├── tools/ import, generation (+ export_policy.toml), audit, packaging, CI
└── docs/
Nothing wrapper-side lives under upstream/; nothing upstream-side lives under
src/ or port/.
Each src/<lib>.cppm follows one shape:
module; // global module fragment
#include <boost/optional.hpp> // the exact headers the export list came from
#include <boost/optional/optional_io.hpp>
export module mcpplibs.mwrap.boost.optional;
export import mcpplibs.mwrap.boost.core;
export import mcpplibs.mwrap.boost.type_traits;
#include "../generated/exports/optional.inc" // export namespace boost { using ...; }- The facade is hand-maintained; the generator never writes it. Its GMF include
set is exactly the bundle translation unit the export list was computed from
(the library's public include DAG, minus headers that do not compile on the
snapshot platform), and the
.incfile carries the generatedexport namespace boost { using ...; }list.gen_exports.py --checkverifies both halves agree: the GMF equals the bundle (plus anyfacade-includesthe policy declares), and theexport importblock covers every.depsedge, because the.incleaves out what an earlier module owns. An edge the facade cannot take (range does not import regex: abi-tag mismatch under gcc) is declared in the policy's[<lib>.facade]omit-importswith a reason. - One entity has one export owner. A dependency that another target library owns
becomes an
export importedge in the primary module, recorded in<lib>.depsas the complete module name. - Cross-library type references, friend operators, ADL helpers and constants that a patch gave external linkage are part of the audit; an unexportable entity is documented in usage.md with a raw-header alternative, never silently pruned.
src/version.cppmis a thin entry re-exportingmcpplibs.mwrap.boost.macros; the mapped constants live only insrc/macros.cppm.
import mcpplibs.mwrap.boost; must re-export exactly the modules of the active
feature set. A committed file cannot express that, and a build-time rewrite of a
committed file cannot be safe when several consumers build the same checkout.
The interface root src/boost.cppm is therefore fixed:
export module mcpplibs.mwrap.boost;
export import :closure;and build.mcpp writes the partition :closure into MCPP_OUT_DIR
(target/.build-mcpp/out/<configuration>/<package>/boost_closure.cppm), then
selects it with mcpp::source() using the absolute path. The output directory is
per configuration — target, profile, toolchain and feature set — so two builds
with different features cannot overwrite each other's closure, and the package
root stays read-only. The file is written only when its content changes, so an
unchanged feature set does not rebuild the module.
build.mcpp also validates the profile features: one profile per axis, and an
arch-gated profile only on its architecture. Both failures are diagnosed before
the module graph is scanned.
Known aggregate limits are reported, not hidden: under GCC the closure leaves out
mcpplibs.mwrap.boost.sort (BMI ICE when an aggregate re-exports it) and warns;
a clang build of the whole closure can hit the source-location limit.
The principle: the compiler computes the public surface, the generator only lists it, and nothing edits the result. The upstream headers are the single source of truth; the generated files are reproducible from them byte for byte.
python3 tools/import_boost.py --tarball boost_1_92_0.tar.gz # repopulates upstream/boost/
python3 tools/apply_patches.py # vendored adaptations (patches/*.patch)
python3 tools/gen_exports.py # generated/exports/*, api-surface.json, skipped.txt
python3 tools/gen_features.py # mcpp.toml blocks + generated/meta/*.lst
python3 tools/gen_exports.py --check # byte-compare, writes nothing
python3 tools/gen_features.py --check
python3 tools/gen_audit.py # linkage audit (AST, target/gen/audit)
python3 tools/gen_audit.py --macros # macro-surface inventoryFor each library one bundle TU includes the facade's GMF headers and is compiled once:
clang++ -std=c++23 --target=x86_64-w64-mingw32 --sysroot=<pinned MinGW> \
-fsyntax-only -Xclang -ast-dump=json -Xclang -ast-dump-filter=boost bundle.cppThe same invocation is the syntax gate: when it fails, the GMF headers named by
the diagnostics (platform-exclusive alternatives such as the pthread/win32 thread
backends) are pruned and the bundle is recompiled; the pruned set is recorded in
api-surface.json. -ast-dump-filter=boost keeps the namespace boost
subtrees only. The dump of an instantiation-heavy library is still several GB,
so tools/ast_json.py reads it as a stream into a flat declaration table:
- clang prints a location's file only when it differs from the previously
printed location, so the reader carries that state across every location in
output order — including the ones it drops — and never trusts a single
loc.filein isolation; - implicit template instantiations (every declaration of a
*TemplateDeclafter its pattern),UsingShadowDeclcopies, comments, source ranges and record definition data are dropped while streaming; references to a dropped instantiation resolve to its template; - references found in a declaration's subtree (declaration references, lookups, type-node declarations, written types) are attributed to the namespace-scope entity that owns it.
Only the clang++ driver pinned in .xlings.json (llvm) is needed — no
libclang, no Python bindings, no LLVM release archive.
- Candidates: namespace-scope
boost::entities with external linkage whose definition lives in a header the library owns (boost/<lib>/**,boost/<lib>.hpp) or in no target library's headers ("shared"). Linkage is read from the AST: anonymous namespaces,static, non-inline const variables and ItaniumL-marked manglings are internal; typedef names are exportable members. Compiler-implicit declarations, friend-only declarations, explicit and partial specializations (reachable through the primary template), deduction guides and class members are not candidates. - Using-injections: namespace-scope
using X::y;/using namespace X;that give entities their public spelling (ADL barriers,detailre-exports). - Signature closure: entities named by the candidates' signatures (parameters, results, bases, fields, typedef targets — with typedefs looked through), so the API's own types are exported with it.
- Ownership: libraries are processed dependencies-first; the first library
to claim an entity owns it, later ones get an
export importedge to the owner in<lib>.deps. Pass 2 walks the bodies of every exported entity and adds the edges a consumer instantiating them needs (cycle-closing and transitive edges skipped). - Output is sorted, carries no paths or timestamps, and uses LF; a name
shadowed inside the export block (
boost::hana::ext::boost) is spelled with a leading::.
The snapshot is one platform flavor (MinGW x86_64, libstdc++). Every human
decision about the surface is declared in tools/export_policy.toml with a
written reason and applied by the generator, never patched into its output:
| Rule | Effect |
|---|---|
[[<lib>.guard]] |
wrap the matching exports in the upstream header's own condition (#if defined(_WIN32) ...), optional #else lines |
[[<lib>.drop]] |
never export the matching names / namespaces |
[[<lib>.extra]] |
verbatim declarations (the gcc extern template vtable workarounds) |
add |
names no collector reaches (body-only or ADL-only references) |
gmf, defines, facade-includes |
the bundle's include set and macros; facade-only includes |
inline-namespaces |
export a macro-selected inline namespace through its parent |
[<lib>.deps] |
module edge corrections; edges of the hand-written special modules |
generated/meta/skipped.txt lists every name a rule guards, drops or adds, so
the boundary is reviewable in one file, and generated/meta/api-surface.json
records the generator identity (clang release, target, defines) and per-library
GMF, export count and edges. A diff after a compiler update therefore shows as a
generator change, not as an unexplained API change.
gen_exports.py --checkregenerates into a scratch copy and compares every generated file byte for byte, then verifies each facade's GMF against the bundle; it never writes the tree. CI runs it on Linux, macOS and Windows.import_boost.pyis the single upstream import: URL, archive SHA-256, kept entries, pruned directories/files and the CRLF-normalized files are recorded inupstream/boost/SOURCE.md. The releaseREADME.mdandLICENSE_1_0.txtare copied byte-for-byte;.gitattributesdisables text conversion underupstream/**.apply_patches.pyreplayspatches/*.patchontoupstream/boost/only (forward/reverse idempotent,patch -p1fallback without Git); see patches/README.md. It never touches generated files or facades.gen_features.pysplices two marked blocks inmcpp.tomland writesgenerated/meta/{libs.json,features.lst,modules.lst,profiles.lst}. It is byte- and mtime-idempotent. Feature names are kebab-case;implieslists feature names (never internal keys), derived from each interface'sexport importlines plus the generated body edges.modules.lstis the feature → module mapbuild.mcppreads;all-modulesexpands to the 117 module entries.gen_audit.pyreports internal-linkage functions, anonymous-namespace entities and explicit specializations per library from the same AST; its report is reviewed with the committed tree, not treated as "not public API".
Generation needs the clang++ pinned in .xlings.json and the pinned MinGW
sysroot (tools/fetch_mingw_sysroot.py); set CLANG_NO_DEFAULT_CONFIG=1 when
the xlings LLVM package installs a host config file. Consumers need neither:
they use the committed output.
One feature per library, additive across dependency edges. A feature's sources
are its interface plus, for compiled libraries, its upstream src/** TU globs.
log's and unit-test-framework's TUs are feature-gated; the rest also appear
in the base source list so mcpp test sees the full surface while mcpp build
compiles only active features.
mcpp build mode drops every feature-listed source and re-adds the active ones;
mcpp test keeps the base list and excludes only feature-provided sources. That
pair of rules is why the base list is generated from the union and why the
Boost.Test framework TUs are deliberately feature-only (the two Boost.Test forms
must never link together).
The module TUs, the library TUs and raw-header consumers share one configuration:
BOOST_FILESYSTEM_VERSION=3,BOOST_THREAD_VERSION=2— the baseline the exports were generated against, pinned in[build].defines;BOOST_ALL_NO_LIB,_MT,WIN32_LEAN_AND_MEAN,SECURITY_WIN32;- per-glob
BOOST_THREAD_BUILD_LIBonupstream/boost/libs/thread/src/**; port/includeandupstream/boostpublic; log/atomic private include dirs are listed inprivate_include_dirs;-wfor vendored header noise;-Wno-invalid-specializationunder clang (poly_collection specializesstd::is_void);- the profile's
NDEBUGstate reaches the headers, so release-only declaration differences follow upstream: Boost.uBLAS's tensorBOOST_UBLAS_INLINEordering is corrected bypatches/numeric.patch(it is ill-formed when the macro expands toinline), and the checked-build-onlysame_impl_exexport is guarded by thenumericrule oftools/export_policy.toml.
The same upstream/boost tree feeds the interfaces, the library TUs and raw
header consumption. One Boost implementation per program: there is no
compat.boost dependency, and a second copy must not be added.
- Wrapper contribution (modules, port, tools, templates, tests, docs):
Apache-2.0,
LICENSE+NOTICE(mcpp-community). - Boost upstream under
upstream/boost/: BSL-1.0, its ownREADME.mdandLICENSE_1_0.txt, hashed inupstream/documents.json. - Retained pre-mwrap wrapper contribution under
upstream/boost-module/: BSL-1.0,SOURCE.mdrecords repository, commits and old identity.
runtime.deploy ships all three under distinct licenses/boost/...
destinations, so equal basenames cannot overwrite one another; a consumer bundle
keeps byte-identical notices.
- Native build +
mcpp teston Linux x86_64 (GCC 16.1, LLVM), Linux arm64 (GCC), macOS arm64 (LLVM), Windows x86_64 (clang-msvc). tools/ci/verify_generated.py: patch replay,gen_exports.py --check,gen_features.py --checkand the audits on a clean tree on all three host platforms, thengit statusmust be clean; a second build on the same configuration records the incremental TU count and mtimes.tools/check_mwrap_release.py: identity, version scheme, tag, licence split and retained-document bytes.tools/ci/test_template.py: installs an isolatedMCPP_HOMEfixture, runs a realmcpp new, parses the rendered manifest (complete selector, version, features), then builds and runs the project — no sed rendering.tools/package-source.sh:boost-m-<version>.tar.gzfrom the tracked tree, hash next to the archive, compressed with a bounded thread count. The release workflow publishes that asset for the immutablemwrap-<version>tag only.
Heavy builds stay below 80% of the machine's effective CPU/memory: set
MCPP_JOBS/-j, avoid parallel full-tree builds, and keep AST generation
single-threaded per library. The vendored tree is 400+ MB; a local fixture copy
hardlinks it when the filesystem allows.