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-arguments has -I$MCPP_HOME/cache/build-database/<key>/target/.build-mcpp/out/qt.
- That directory holds only
moc_counter.cpp and qrc_demo.cpp, both 0 bytes. It has no ui_mainwindow.h. Per R2.5 no action runs under emit, so the files never appear there.
- The translation units for
moc_counter.cpp and qrc_demo.cpp point at those 0-byte files.
- The project's own
target/.build-mcpp/out/qt/ has the real ui_mainwindow.h, moc_counter.cpp and qrc_demo.cpp from the last mcpp 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-level ide.generated list. The shape is yours to choose. Then an IDE can say "ui_mainwindow.h is generated by qt:uic:mainwindow; build once", instead of reporting a missing header.
- Say where a build put them. When
mcpp build has already materialized an output under the project's target/, 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 without MCPP_OFFLINE) fails with MCPP_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 no build.mcpp.bin. On the same machine mcpp build fails 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)).
emit writes into the project. On every fresh copy, emit created <root>/.mcpp/.xlings.json (the [xlings.workspace] file: "deps": ["xim:[email protected]"]), while the envelope's effects listed only read-project. This goes against R2.1 and R2.6.
Related
mcpp-language-server reads a project's model from
mcpp emit build-database --format json. On a project built withmcpp: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:plugins0.15.2 (features = ["rules-qt"]), gcc 16.1.0, Qt 6.11.1 on x86_64 Linux.The project
build.mcppcallsmcpp::rules::qt::compile(...), andsrc/main.cppdoes#include "ui_mainwindow.h"beforeimport 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.qrcandi18n/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.cppmin 0.15.2, around line 549):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.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.moc_counter.cppandqrc_demo.cpppoint at those 0-byte files.target/.build-mcpp/out/qt/has the realui_mainwindow.h,moc_counter.cppandqrc_demo.cppfrom the lastmcpp build.Effect:
src/main.cppfails withfatal 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 privateout/qtreplaced by the project's owntarget/.build-mcpp/out/qt, and the three entries from §1 removed. With that,clangd --check src/main.cppexits 0 with no diagnostics.Ask (any of):
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.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.Also seen while reproducing
These may deserve their own issues.
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:[email protected]"]), while the envelope'seffectslisted onlyread-project. This goes against R2.1 and R2.6.Related