Repository navigation
emit build-database: a rule's inputs are emitted as translation units, and its generated outputs point at a directory that is never filled #724
Description
mcpp-language-server reads a project's model from mcpp emit build-database --format json. On a project built with mcpp:plugins' rules-qt, the S1 document describes the rule's inputs and outputs in a way an IDE cannot use, so clangd reports errors on every file that includes a generated header. The emit command is following SPEC-005 R2.1 and R2.5 (it writes nothing into the project and runs no action); this is a request to describe what the rules generate, not to generate it.
Seen with mcpp 2026.9.26.2 and 2026.9.27.1, mcpp:plugins 0.15.2 (features = ["rules-qt"]), gcc 16.1.0, Qt 6.11.1 on x86_64 Linux.
The project
[build]
sources = ["src/*.cpp", "ui/*.ui", "res/*.qrc", "i18n/*.ts"]build.mcpp calls mcpp::rules::qt::compile(...), and src/main.cpp does #include "ui_mainwindow.h" before import std;.
1. Rule inputs are emitted as C++ translation units
The document has a translation unit for each device source the rule claims, with a compiler command:
{
"source": "<root>/ui/mainwindow.ui",
"arguments": ["<gcc>/bin/g++", "...", "-c", "<root>/ui/mainwindow.ui", "-o", ".../obj/mainwindow.ui.o"],
"ide": { "role": "non-module" }
}The same happens for res/demo.qrc and i18n/qt_demo_zh_CN.ts. These files are compiled by the rule's actions (uic, rcc, lrelease), never by the C++ driver. clangd treats them as linker inputs ('linker' input unused, Indexing ... failed: Couldn't build compiler invocation), and its module scan logs a failure for each of them.
Ask: a source that a rule claims as an action input is not a translation unit of the set. Leave it out, or at least don't give it a compiler command.
2. Generated outputs point at a directory that is never filled
The rule declares every generation as a structured action (rules/qt.cppm in 0.15.2, around line 549):
a.id = "qt:uic:mainwindow"; // "qt:uic:" + stem
a.role = mcpp::roles::source;
a.arg(uic).arg(in).arg("-o").arg(out).input(in).output(out).submit(); // out = <gen>/ui_mainwindow.h
...
mcpp::include_dir(generic(gen).c_str());So the plan knows which file is generated, by which action, from which input, and with which command. The S1 document keeps only the include directory:
baseline-argumentshas-I$MCPP_HOME/cache/build-database/<key>/target/.build-mcpp/out/qt.- That directory holds only
moc_counter.cppandqrc_demo.cpp, both 0 bytes. It has noui_mainwindow.h. Per R2.5 no action runs underemit, so the files never appear there. - The translation units for
moc_counter.cppandqrc_demo.cpppoint at those 0-byte files. - The project's own
target/.build-mcpp/out/qt/has the realui_mainwindow.h,moc_counter.cppandqrc_demo.cppfrom the lastmcpp build.
Effect: src/main.cpp fails with fatal error: 'ui_mainwindow.h' file not found, so clangd cannot scan it for its imports, and the file has no semantics at all. To check that nothing else was wrong, I rewrote the document by hand: the private out/qt replaced by the project's own target/.build-mcpp/out/qt, and the three entries from §1 removed. With that, clangd --check src/main.cpp exits 0 with no diagnostics.
Ask (any of):
- Describe the generated outputs. For each generated file or directory a set uses, say that it is generated, by which action (
qt:uic:mainwindow), from which inputs (ui/mainwindow.ui), and with which command, e.g. a set-levelide.generatedlist. The shape is yours to choose. Then an IDE can say "ui_mainwindow.his generated byqt:uic:mainwindow; build once", instead of reporting a missing header. - Say where a build put them. When
mcpp buildhas already materialized an output under the project'starget/, give that path next to the planned one. A reader can then use it read-only. Without it, a consumer has to hard-code how$MCPP_HOME/cache/build-database/<key>/target/...maps onto<root>/target/..., which is mcpp's implementation detail. - (Optional, a change to R2.5.) A mode that runs only code generators a rule declares side-effect free (uic, moc and rcc read their input and write one output) into the private output directory. That would cover a project that was never built. The rule would opt in per action; everything else stays as R2.5 says today.
Also seen while reproducing
These may deserve their own issues.
- A build-program failure is reported as unclaimed device sources. On a fresh copy of the project,
emit(2026.9.26.2 and 2026.9.27.1, with and withoutMCPP_OFFLINE) fails withMCPP_BUILD_DATABASE_PLAN_FAILED: device sources that no action compiles: i18n/qt_demo_zh_CN.ts, res/demo.qrc, ui/mainwindow.ui ... import the rule package that claims these extensions. The package does import and call the rule. The private directory has the build program's objects (mcpp.o,mcpp.rules.qt.o, ...) but nobuild.mcpp.bin. On the same machinemcpp buildfails linking the build program (ld: cannot find crt1.o, an environment problem here). So the real failure was the build program not linking, and the message sends the user to fix something that isn't broken. The original project plans fine only because its build program is cached (build.mcpp up to date (cached)). emitwrites into the project. On every fresh copy,emitcreated<root>/.mcpp/.xlings.json(the[xlings.workspace]file:"deps": ["xim:qt-base@6.11.1"]), while the envelope'seffectslisted onlyread-project. This goes against R2.1 and R2.6.
Related
- emit build-database for IDEs: one failing member loses the whole database, and build programs cannot tell a plan pass #699 (partial answers for IDEs), emit build-database builds host tools in full, so planning can cost a whole build of a tool package #707 (emit no longer builds host tools), A rule program is synthesised for a package with no device sources, contrary to the condition its comment states #715 (rules reach a package only through the extensions they claim).
Activity
mcpp-language-server 0.0.6: go-to-definition reaches the implementati…
T3 (#724 W3/W4/W5): a device source is not a compile unit; a failed b…
W10 (#724): the S1 document names what a rule generates (S1 0.3.0)
spec: S1 0.3.0, generated files and the path the build writes them to
spec: S1 0.3.0, generated files and the path the build writes them to (…
2026.9.28.1: eight reports (#717, #718, #720, #722-#726), one downloa…
speak-agent commentedon Sep 27, 2026
Implemented in 2026.9.28.1 (#727), with S1 profile 0.3.0 (Sunrisepeak/mcpp-language-server#28).
compile_commands.json, or as a deadcxx_objectedge (SPEC-005 R3.7).MCPP_BUILD_DATABASE_PROGRAM_FAILED(R5.2).emit build-databasewrites nothing into the project. This holds for a project with[xlings]payloads too (R2.1).ide.generated. Every set of a package lists the files and directories that package's build program generates. Each entry gives the path the document names, thebuild-pathamcpp buildof the same configuration writes, itskind, and the generating step (id,inputs,arguments,work-directory). This is SPEC-005 R3.12 and S1 §7.2. A consumer can therefore tell a header not yet built from a missing one.Verification of the published release. A fresh SubOS sandbox (
xlings subos use <name> --sandbox) with the CN mirror configured for both xlings and mcpp installedmcpp@2026.9.28.1through the index and ran one script over the published binary:13 ok, 0 failed(the Windows-only items are reported as not run). The same script in a second sandbox againstmcpp@2026.9.27.1reads5 ok, 8 failed, so each assertion below measures this change rather than the setup it runs in.ide.generatedentrymcpp buildwrites the header at the statedbuild-pathe2e 808, 809, 815 and 817 carry the four items in the suite.