2026.9.26.2: one compile database per configuration, emit plans every member, prepare actions and runtime search directories, DLLs beside a Windows program, link flags as words - #702
Merged
Conversation
…ory, joining `[runtime] library_dirs` `mcpp::runtime_library_dir(dir)` (`mcpp:runtime-library-dir=`, protocol 12) is the build-program form of `[runtime] library_dirs`: a launch-time search directory for a dependency a build.mcpp discovers (a vcpkg prefix's `bin/`, a Qt SDK's `bin/`) rather than one an author can write into a fixed TOML array. It joins the SAME field the manifest key populates (`RuntimeConfig::libraryDirs`), so every existing consumer of that field sees a directive-declared entry without any of them changing: `mcpp run`'s loader path, `mcpp pack`'s closure search, and the ELF/Mach-O `-Wl,-rpath` rendering. Persisted like `deploy`/`warning`/`pack-format` (non-empty cache tag, `kCacheEpoch` not bumped, same reasoning), and the root package's own build.mcpp needed the same manifest-snapshot mirror `deploy` already has in prepare.cppm, or `resolve_runtime_contract` never sees a directive-sourced entry. Adds unit coverage in test_build_directives.cpp and an e2e script (779) that links an executable against a prebuilt shared library only the directive's RUNPATH makes loadable, checks the build.mcpp cache-hit replay, and checks `mcpp pack --format dir`'s closure staging. Docs: docs/30-build-mcpp.md and docs/zh/30-build-mcpp.md gain the directive, the typed API entry and a worked section; docs/04-mcpp-toml.md and its zh counterpart point `[runtime] library_dirs` at the build-program form.
…-007 draft 0.1
SPEC-004 §8's element reading extends to ldflags and the link directives: each element is read into words, and each word reaches the linker quoted for the host and escaped for ninja. A $ORIGIN in [build] ldflags, in mcpp::link_flag or in a dependency's ldflags reaches the program's run path instead of /../lib; a search directory whose path holds a space is one argument. Dependency flags propagate word by word, the link directives spell engine-built paths as one word, the build-program cache epoch moves to 3, and the first plan names an element whose words differ from what 2026.9.26.1 passed.
A PE image has no run path, so a DLL in a runtime search directory served mcpp run and mcpp pack and not a program started by hand. A PE program whose plan has runtime search directories now gets an edge after its link, mcpp place-dlls, which reads the program's import closure with mcpp pack's read_closure and system rule and publishes each DLL it resolves in those directories beside the program. Its depfile names the DLLs it placed, so a DLL replaced in its directory is placed again. Deployed DLLs become order-only inputs of the link, since the linker reads the import library.
… is a warning #699 item 1 (E1). `emit build-database --workspace` stopped its whole member loop at the first planning failure, discarding the sets of every member that had already planned; `mcpp build --workspace` never had this defect (continue-on-failure). The loop now plans every selected member on its own: a member whose planning fails contributes no set and one `error` diagnostic (`MCPP_BUILD_DATABASE_PLAN_FAILED` or `MCPP_OFFLINE_DOWNLOAD_REQUIRED`, the refusal code read right where that member's planning ends so an earlier member's recovery cannot relabel a later one's reason) whose `path` names its `mcpp.toml`, relative to the workspace root. `data` is present when at least one member planned; a workspace in which every member fails still omits it, since S2 has no partial outcome. The exit status is 1 whenever any diagnostic is an error, independent of `--format`. #699 item 2 (E2). Under `emit` (`plan_only`), a host tool that fails to build no longer costs the requesting member's plan: it is a warning, `MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT`, naming the tool, its package and the first line of the failure. The tool build itself and its `check` actions are unchanged, and the requesting build program still receives the path the tool would have been published at (the store key already fixes it before the build runs). `mcpp build` is unaffected: the same failure still ends the build. #699 item 2 (E3). Under `emit`, a package whose build program fails (does not compile, exits non-zero, times out, or is refused) is described without that program's directives instead of costing its member: the manifest's own configuration, the toolchain, the module graph and the standard-library units are described as usual, with one error, `MCPP_BUILD_DATABASE_PROGRAM_FAILED`, whose `path` names its `build.mcpp`. Nothing from a failed run was ever applied (`run_build_program` returns before parsing directives on every failure path); a later failure that follows from the gap fails the member under E1's rule. Both `run_build_program` sites (a dependency's and the root's) carry the same branch, gated on `overrides.plan_only`. `PlanNote` gains a severity (default warning, so every existing note is unchanged) and a `path` (absolute when recorded, rewritten to the workspace-relative form `render()` uses for everything else). `emit`'s per-member diagnostics and `build_database::render`'s note handling and `watch` assembly (a failed member's `mcpp.toml` and `build.mcpp`, when present, join `watch` exactly as a planned member's do) change together. Design: .agents/docs/2026-09-26-compile-database-and-issue-699-design.md §4.
docs/50 §8 and its zh counterpart now describe the shape #699's fix gives `mcpp.build-database`: `emit` plans every selected member on its own, a failed member's diagnostic carries `path`, `data` is present whenever one member planned and absent only when none did, and the exit status is 1 whenever any diagnostic is an error even though `data` is present. The two new codes join the table: `MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT` (warning) and `MCPP_BUILD_DATABASE_PROGRAM_FAILED` (error). SPEC-005 R2.5 states the host-tool warning and that its `check` actions and `mcpp build`'s own failure are unchanged; R5.2 states member containment, the per-member `path`, the program-failure description and the exit-code rule. Header, version line and R3.7/R3.8/R4.1 are untouched (T1's).
787: a workspace member whose planning fails (an unresolvable dependency, not a build program) does not cost its sibling's set; the failed member's `path` and its containment in `watch`; a workspace in which every member fails omits `data`; the exit code is 1 with and without `--format`. 788: a host tool whose build carries a failing blocking `check` (the e2e 315 fixture) is a warning under `emit`, not a lost plan; `mcpp build` still fails on the same check. 789: a build program that exits 1, and one that does not compile, are each described without their directives, `path` naming `build.mcpp`, and nothing a failed run printed before exiting reaches the described unit (the exit-code check precedes directive parsing). A third case, a library target with no sources of its own relying entirely on directives, shows the member failing under E1 when the missing directives leave nothing to link. All three fail on the released 2026.9.26.1 (measured) and pass on this branch's build.
… standard-library units Implements T1 (W1, W2, W3) of the 2026-09-26 compile-database design record (.agents/docs/2026-09-26-compile-database-and-issue-699-design.md), against issues #397, #677, #699 and the xmake-comparison report it triages. W1 (design §3.2). compile_commands.json is now two files with one rule each. The CONFIGURATION's database, target/<triple>/<fingerprint>/compile_commands.json, holds the fresh plan's entries merged with the entries it already had whose `file`, resolved against `directory`, the fresh plan lacks and which still exist -- unchanged in spirit from before, but now scoped to one output directory instead of the project root, and keyed by the resolved path rather than the raw string. The ROOT file is a copy of that database: replaced whole, never merged, left untouched when byte-identical. When the replaced root file held entries mcpp did not write (another tool's, or a stale mix of toolchains), one warning states how many; an entry is mcpp's own when its `output`, resolved against `directory`, lies under this project's `target/` tree or under the mcpp home (the standard-library units' shared cache). The fast path (execute.cppm, try_fast_build and try_fast_run) publishes the root file through the same function the full path uses, reading the configuration database already on disk, so a deleted root file returns on the next build without a plan (C1). `emit build-database` still writes neither file. The scaffold's .gitignore lists compile_commands.json. W2 (design §3.3, §3.4). `directory` is the output directory the compiler actually runs in, for every project unit and every toolchain, matching the JSON Compilation Database format, S1-8-2 and what CMake, ninja and xmake already write (C3); before this, GCC's importers failed when replayed from `directory` and wrote gcm.cache/ into the project root. A unit that provides a module also carries its dialect's language flag (BmiTraits:: moduleInterfaceLangFlag) immediately before `-c`, so a reader does not infer it from the extension the way clang's driver cannot for `.ixx` (C4); the MSVC form stays out until a Windows measurement of clang-cl-mode clangd answers whether it accepts `/interface`. W3 (design §3.5). The standard-library units a build compiles are recovered once, in prepare.cppm, from the same derivation `ensure_built` and `describe_std_module` both read, and carried on the plan (BuildPlan::stdModuleUnits) rather than recovered again by each renderer: compile_commands.json, `emit --spec compile-commands` and the S1 document (mcpp.build.build_database) now render the identical record for these units, which follows S1-12-1 and could not previously disagree because it did not exist in the JSON-format documents at all (D5a). The S1 document's `provides` for `std`/`std.compat` now names the BMI path in the shared std cache instead of an empty string (D5b), and `ide.toolchains.<id>.build-id` carries the compiler's build identity -- the same value already computed as one field of the toolchain fingerprint (the driver's normalized --version banner, or a hash of the driver binary) -- stable across two runs of one toolchain. Project modules keep "" in `provides` under `emit`, unaffected. `mcpp.build.plan` gains `recover_invocation`/`split_command_words`, moved down from `mcpp.build.build_database` (which re-exports them under their established names for its own tests) so both `mcpp.build.compile_commands` and `mcpp.build.build_database` -- neither of which may import the other -- can render from the same recovered record without a dependency cycle. Tests: unit (test_compile_commands.cpp: the within-configuration merge resolves `file` against `directory`; a project unit's `directory` is the output directory; the language flag before `-c` for an interface unit and its absence for an implementation unit and for MSVC); e2e 781-786 (new, against 2026.9.26.1: 781, 784 and 785 fail as designed); e2e 211 and 47 updated for `directory` now naming the output directory; e2e 688 updated for the standard-library units now appearing in both the S1 document's compile- commands export and the real build's compile_commands.json.
…rary entries docs/01 (and its zh counterpart) now state what C6 left undocumented: the root compile_commands.json is a copy of the current configuration's own database under target/<triple>/<fingerprint>/, replaced whole rather than merged, a symlink at the root is followed, a replace that discarded another writer's entries is reported, and a build that imports `std` lists the standard-library units alongside the project's own. SPEC-005 (docs/specs/build-database.md): R3.7 states that `work-directory` is the directory the compiler runs in (not the project root) and that a unit providing a module carries its interface language flag before `-c`; R3.8 states that the standard-library units' `provides` names the BMI path in the shared std cache and that `ide.toolchains.<id>.build-id` carries the compiler's build identity; R4.1 states that the compile-commands document includes the standard-library units, per S1-12-1. The header, version line and change log are left for the pull request's integration commit, which also carries R2.5 and R5.2 (T2's).
…rface flag and the standard-library units (#699 report)
…05 v1.3 and SPEC-007
…put (mcpp#701, mcpp#702) Re-implements #701/#702's two prior commits to design .agents/docs/2026-09-26-compile-database-and-issue-699-design.md §5.4, and adds P, the `prepare` role the design's O1 asks for. One protocol bump (12) and one cache-format epoch bump (3) cover all three. R1'. `mcpp::runtime_search_dir(dir)` (`mcpp:runtime-search-dir=`, protocol 12) replaces the branch's `runtime_library_dir`: a launch-time search directory a build.mcpp discovers (a vcpkg prefix's `bin/`, a Qt SDK's `bin/`, a directory a `prepare` action populates) rather than one an author writes into a fixed TOML array. It lands DIRECTLY on `LinkIntent::runtimeSearchDirs` -- the field `runtime_search_dirs` itself populates -- not the retiring `RuntimeConfig::libraryDirs` the branch had joined by mistake, so every existing reader (the plan merge, RUNPATH/rpath, `mcpp run`'s loader path, `mcpp pack`'s closure search) sees a directive-declared directory exactly as it sees the manifest key's, and a dependency's declaration reaches its consumer's executable through the same merge. The directory need not exist when the program runs. R2. After a `check` or `prepare` command succeeds, the engine creates every declared stamp that is missing and sets the modification time of every existing one to the present, whether or not the command wrote it; on failure it writes nothing. This replaces the branch's before/after time comparison with R2's simple form: a stamp feeds no compile or link edge, so touching one the command already wrote changes no build. P. The `prepare` role (`mcpp::roles::prepare`, alongside `source`, `check`, `object`, `artifact`) is for construction whose file names are unknown when the build program runs -- installing a vcpkg manifest or a CMake subproject into a prefix, unpacking an SDK. Outputs: one or more stamps, written by the engine through the same wrapper as `check`, and one declared directory (`mcpp::action::output_dir`) the command populates. When the command succeeds and that directory does not exist, the engine writes no stamp and fails the edge, naming the directory. Every compile edge of the declaring package waits for it, as a blocking `check` does today; so does every LINK edge in the plan, because `plan.linkIntent` -- and therefore the `$ldflags` every link rule interpolates -- is one value merged from every package's LinkGlobal directives, read alike by the declaring package's own link and by every package that depends on it. The #534 self-check (`check_action_ordering`) is extended to assert this for links, the same way it already does for compiles. Progress lines label it `PREPARE`. An unknown role string is refused, naming the value and the five roles, where it used to be read as `source` silently -- the cache-format epoch moves to 3 so a stale entry from before this guard existed is re-validated rather than replayed two different ways by two code paths. A build program whose `rerun_if_changed`/`rerun_if_changed_glob` names a path inside a `prepare` directory (of any package in the graph) is warned, naming both paths: that directory is populated at build time, after configuration has already run.
…1, mcpp#702) 779: rewritten for runtime_search_dir (renamed from ...runtime_library_dir): RUNPATH in build.ninja, mcpp run, a build.mcpp cache-hit replay, mcpp pack, and a dependency's declaration reaching a consumer's executable through the same LinkGlobal merge. 790: a prepare action's command writes a header and a shared library into a directory whose contents build.mcpp never names; a unit that includes the header compiles and a program linked against the library runs through runtime_search_dir on the first build; the second build runs nothing; after an input of the action changes, it runs once and then not again. 791: a prepare action whose command creates nothing fails naming its declared directory. The stamp is declared beside that directory, not inside it -- ninja creates a declared output's own parent directory before running any edge, so a stamp nested inside the directory would make it "exist" regardless of what the command did. 792: a build program whose rerun_if_changed or rerun_if_changed_glob names a path inside a prepare directory is warned, naming both paths; a re-run input outside any such directory is the silent control. 793: an unknown action role is refused, naming the value and the five roles; each real role, including a real object action, is the control. 794/795: the R1' Windows leg -- a DLL in a declared runtime search directory is found by `mcpp run` through PATH and placed beside the packed program by `mcpp pack`. 794 requires windows and runs on Windows CI; this host cannot run it. 795 exercises the identical property through mingw-cross + wine, which this host can and does run, giving this round a passing reading before release.
…e prepare role and the stamp rule (mcpp#701, mcpp#702) docs/30, and zh: a new section states the three kinds of work a build.mcpp or the rules it imports do (SPEC-007 §1) -- configuration depends only on declared inputs, construction is never done while build.mcpp runs, an incomplete environment is a warning, and one behaviour serves planning and building alike. `dep_bin` gains the paragraph on a host tool that fails to build under `emit build-database`: naming it still configures correctly, calling it while configuring does not. The directive tables and the typed API table rename runtime-library-dir/runtime_library_dir to runtime-search-dir/runtime_search_dir and correct what it joins; the worked section is rewritten to match, with the Qt example fixed to pass `lib/` to link_search and `bin/` to runtime_search_dir. The action role table gains `prepare`, spelled with the `mcpp::roles` constants; a new section states its contract (output_dir, the post-condition, the ordering, the R1.3 warning). The stamp-rule paragraph is rewritten for R2's simple form. docs/31, and zh: the role table gains `prepare` and points at docs/30's fuller contract and at SPEC-007. docs/04, and zh §2.11: `runtime_search_dirs` (not the retiring `library_dirs`) is named as the field the build-program form joins.
…le constants (#701, #702) Conflicts resolved: the epoch-3 comment carries both reasons (#703, #702); a link edge's one order-only list carries the deployed DLLs and the prepare phonies; the runtime-search-dir residue mirror lands in T2's restructured root block. The prepare post-condition asks for a populated directory rather than an existing one, because ninja creates a stamp's parent before the command runs; e2e 791 checks a stamp beside and inside the directory.
…t's copies resolve again from their directories A prepare action rewrites files ninja does not know as outputs, so the DLL it replaced was placed one build late; the placement edge now takes the graph's prepare stamps as implicit inputs. A copy placed beside the program was then resolved in the program's directory before the runtime search directories and shadowed the newer version; the stamp records the names placed, and read_closure resolves those names outside the program's directory (notInFirstDir).
… the DLL placement after a PE link
…w-cross job runs 796
…dex, SPEC-007 in the docs index
…atform, python3 in the hermetic llvm step The unit tests compare a native-spelled directory as a path and JSON-escape a Windows path; e2e 781 and 782 build with the machine's default toolchain, and 781 asks for no plan only where the fast path exists (ELF); e2e 211 derives the sibling fixture from the entry's own file, which mcpp spells; the hermetic container gains python3 for the compile-database scripts.
…spellings of a Windows temporary directory share
This was referenced Sep 26, 2026
Closed
Sunrisepeak
pushed a commit
that referenced
this pull request
Sep 26, 2026
…adings (#706) The design record gains §12.3 and §12.4: - mcpp 2026.9.26.2 (#702, c109fdd), released and mirrored to GitCode from a CN host, and indexed through xim-pkgindex#880; - the fresh-install matrix, green except the xcode-27 legs of #669; - the sandbox check of the published artefacts (20 ok) and the same script against 2026.9.26.1, which fails exactly the 16 checks the release changes; - mcpp-index#470 and its full sweep, every workspace shard green; - S2 0.3.0 in mcppls 0.0.5 (#26); - what remains open. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
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
Implements the design record
.agents/docs/2026-09-26-compile-database-and-issue-699-design.md(decisions D1 to D12) as mcpp 2026.9.26.2. The pull request carries the whole round: the compile database,emit build-databasefor workspaces, the build-program surfaces #701 asked for in the form SPEC-007 states, the placement of a Windows program's DLLs, and #703.The compile database (design §3)
target/<triple>/<fingerprint>/compile_commands.json, merged only within that configuration; the rootcompile_commands.jsonis a copy, replaced whole and never merged, restored by the next build (fast path included) when deleted, with one warning when it held entries mcpp did not write.directoryand S1work-directoryname the output directory, where the compiler runs (JSON Compilation Database, S1-8-2); GCC entries replay fromdirectorywithout writinggcm.cache/into the source tree.-c(GCC and Clang dialects; MSVC waits for a Windows measurement).std(S1-12-1); S1providesof those units names the std cache BMI, and toolchains carrybuild-id.mcpp newignorescompile_commands.json.emit build-database(#699, design §4)pathnaming itsmcpp.toml, anddatais present when any member planned.emit, a host tool that does not build is the warningMCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT; a failing build program costs only its own directives, with the errorMCPP_BUILD_DATABASE_PROGRAM_FAILED.Build programs (#701, design §5.4, SPEC-007)
mcpp::runtime_search_dir(dir)(mcpp:runtime-search-dir=, protocol 12) joinsLinkIntent::runtimeSearchDirs, the field[runtime] runtime_search_dirsfeeds;runtime_library_dirand the legacylibrary_dirsfield are not extended.preparerole, witha.output_dir(dir): construction whose file names are unknown when the program runs. The edge fails when the directory holds no file after success; compile edges of the declaring package and every link edge wait for it; progress lines sayPREPARE; a rerun input inside apreparedirectory is warned.mcpp::roles::{source, check, object, artifact, prepare}; unknown role strings are refused with the list (they were read assource).checkorpreparesucceeds, every stamp is newer than every input.Windows DLL placement (design §5.4 W, SPEC-007 R4.3)
mcpp place-dlls, which places the DLLs it imports from those directories beside it, withmcpp pack's closure walk and system rule. A replaced DLL is placed again in the same build; deployed DLLs are order-only inputs of the link.Link flags as words (#703)
ldflagsand the link directives:$ORIGINreaches the run path instead of/../lib, dependency flags propagate word by word, a search directory with a space is one argument, and the first plan names an element whose words changed.Specifications and documents
docs/specs/build-plugins.md); docs/01, 04, 30, 31, 50 and their zh pairs; CHANGELOG. Build-program cache epoch 3.Closes #699
Closes #701
Closes #703
Test plan
mcpp test: 129 test binaries pass;mcpp test -p buildmcpp,-p manifestpass.mingw-cross+winelegs (796, 797) run locally; CI runs them in the mingw-cross job with asserted lines.