Reader: a contributor who will build and change mcpp itself.
The question this chapter answers: how is mcpp built from source, how are its own tests organised, and what does a contribution have to satisfy.
Not here: how a user builds their own project, which is 01 — Getting Started, and how a release is cut, which is 92 — Releasing mcpp.
mcpp is self-hosting — mcpp builds mcpp from source using mcpp itself. Any environment that already has a working mcpp binary can build from source.
Follow 01 — Getting Started to install a working copy of mcpp, then clone the repository:
git clone https://github.com/mcpp-community/mcpp
cd mcppmcpp build # compile the current source with the existing mcpp → ./target/.../bin/mcpp
mcpp run -- --version # run the artifact you just built
mcpp test # build and run C++ tests discovered under tests/**/*.cpp (including tests/unit)mcpp test does not run the shell end-to-end suite under tests/e2e/; run
those separately against the freshly built binary.
The first build automatically fetches the default toolchain; see 20 — Toolchain Management for details.
To produce a fully static binary identical to a release (the path taken by release.yml):
mcpp build --target x86_64-linux-musl
# → target/x86_64-linux-musl/.../bin/mcpp is a fully static ELFmcpp is a workspace. modules/ holds nine packages that the engine imports;
src/ is the engine itself.
modules/ workspace members, imported by src/
├── manifest/ manifest and descriptor parsing
├── platform/ operating-system abstraction
├── toolchain-model/ triples, dialects, fingerprints, the link model
├── buildmcpp/ the build.mcpp contract: protocol, directives, provisions
├── source-kind/ source-file role classification
├── versioning/ this binary's version, and SemVer requirements
├── dyndep/ ninja dyndep emission
├── libs/ vendored text-format parsers
└── log/ leveled logging
src/
├── main.cpp entry point
├── cli.cppm cli/ command dispatch and the commands
├── build/ build orchestration and the ninja backend
├── modgraph/ P1689 module scanning and the dependency graph
├── pm/ the resolver and the package-management commands
├── toolchain/ detection, fingerprinting, the std module
├── pack/ publish/ mcpp pack, mcpp publish and xpkg generation
├── fetcher/ fallback/ download, installation, fallback resolution
├── bmi_cache/ the cross-project BMI cache
├── runtime/ xlings/ the runtime contract and the xlings bridge
├── freestanding/ bare-metal targets, link line and runner
└── scaffold/ `mcpp new` and templates
tests/
├── unit/ 108 C++ tests, discovered by `mcpp test`
└── e2e/ 370 shell scripts against a real binary
Two layers, and they answer different questions.
Unit tests (tests/unit/, 108 files) are C++ programs mcpp test
discovers. They exercise a module's contract with no binary and no filesystem
state.
End-to-end tests (tests/e2e/NN_<name>.sh, 370 files) run a real mcpp
binary against a real project. run_all.sh is the CI entry point. mcpp test
does not run them.
MCPP=<fresh-mcpp-binary> bash tests/e2e/02_new_build_run.shA capability gate decides which run. The first lines of an e2e script declare what it needs, and a runner without that capability skips it:
#!/usr/bin/env bash
# requires: elf gccgcc (80 scripts), elf, unix-shell, llvm, jq and fresh-sandbox are
the ones in use. A script that requires a capability no CI job provides never
runs anywhere, and its greenness means nothing — check that some job supplies
what a new script asks for.
The most transferable rule in this repository, and the one that fails silently when it is skipped:
After writing a check, remove the fix and run it once. A check that has never been seen to fail is not known to measure anything.
Three shapes it catches, all of which have shipped here at least once:
| shape | form |
|---|---|
| the criterion never runs | a test gated on a capability no job provides |
| the criterion cannot fail | a substring search satisfied by any wording |
| the criterion measures the wrong object | a fixture whose directory accumulates across runs, so the search answers about an earlier build |
State the denominator too. "Every host in the table was scanned" is a check; "the scan found nothing" is not, because an empty enumeration also finds nothing.
.github/tools/ holds eighteen scripts. Four are worth knowing before a first
PR:
| script | the condition it refuses |
|---|---|
check_docs_style.sh |
question headings, second person in a reference chapter, a 简体中文 page whose heading structure has fallen behind |
check_docs_structure.sh |
a chapter citing a design record, a docs/NN-*.md path that does not resolve, a translation missing a table |
check_version_pins.sh |
a version written in one place and not the others |
check_modules_wiring.sh |
a workspace member wired into some of the three places that must know about it and not the others |
Closed syntax, open vocabulary: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values.
- mcpp only defines mechanisms (feature union/closure, capability require/provide/override, profile→compiler flags, platform→triple); the keys and shapes are fixed. Domain vocabulary such as feature names, capability names, and backend names appears only in values, never in mcpp's code.
- Package-custom toml keys are not supported: key legitimacy must not depend on "first parsing the target package," otherwise the manifest loses static parseability (a prerequisite for lockfiles/LSP/auditing). A package's extension point = open value domains within fixed mechanisms.
- Package-level knobs all converge into features; for sugar keys (such as
backend=) to enter the core syntax, they must satisfy: ① domain-neutral (a cross-ecosystem general pattern) ② 1:1 desugaring with zero new parsing semantics. - A key that duplicates an answer another section already gives is not admitted.
Two places to state one fact is two places that can disagree, and the failure
is silent — whichever reader loses the race is simply wrong. Library packaging
(12) is the worked example: it added zero
manifest keys, because what to pack is
[targets.<n>].kind, which interface to publish is[lib]plus the module graph, which headers are public is[build].include_dirs, and the per-artifact evidence is[[runtime.artifacts]]. - A field that describes what a generated package IS (rather than what a build
should DO) belongs on
[[runtime.artifacts]]— see §2.11.provenancebeginning withmcpp-packis what marks a directory as one, and mcpp refuses tobuildinside it.
File issues at github.com/mcpp-community/mcpp/issues, ideally including the following:
- The full output of
mcpp self env - The full output of the failing command (
MCPP_LOG_LEVEL=debuggives more detail) - Your operating system, distribution, and glibc version (check with
ldd --version)
mcpp is in early iteration and its interfaces may change. Before submitting a PR, please note:
- For changes touching the CLI or the
mcpp.tomlschema, open an issue first to align on direction. - Keep each PR focused on a single change; write commit titles in English imperative form (
fix: .../feat: ...). - For behavior changes or test documentation, run
mcpp testand the relevant E2E scripts against a fresh binary before submitting. For documentation-only changes, recheck the examples and links; usegh pr checks <pr-number>for the PR's actual required checks.
- Community forum
- Chat group QQ: 1067245099
- mcpp-index — the default package index
- mcpplibs — the companion collection of modular C++ libraries