Skip to content

lsp-mcpp v1: compiler-agnostic C++ modules language server - #1

Open
Sunrisepeak wants to merge 93 commits into
mainfrom
feat/lsp-mcpp-v1
Open

Sunrisepeak wants to merge 93 commits into
mainfrom
feat/lsp-mcpp-v1

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Implements .agents/docs/2026-09-13-cxx-modules-unified-lsp-design.md end to end, and then .agents/docs/2026-09-14-lsp-mcpp-v1-usable-plan.md (W1–W11) on top of it: a compiler-agnostic C++ modules language server written entirely in C++23 modules, its specifications, its conformance suite, the payload it ships with, and the VS Code extension. The execution record of the usable plan (results, deviations, measurements) is its section 10.

What is in this PR

Area Content
src/ The server: platform layer over openkal, JSON-RPC and the LSP 3.18 protocol (generated), S1–S4 models, S1 argument structuring (a level 2 database completed to level 3), lexical module scanner, LSP glob patterns, compile databases, toolchain probing and discovery (Visual Studio included), GNU and MSVC normalizers, engine database planner with module hints, mcpp (S2 single-document envelopes) and CMake (build databases) providers, syntactic module index, engine interface and clangd capability table, parallel module preparation, one project model and clangd per workspace root, payload integrity, CLI
os/ Per-target lspmcpp.os packages selected by cfg dependencies; platform code branches with if constexpr
specs/ S1 build database IDE profile 0.2.0, S2 discovery 0.2.0, S3 LSP extensions v1, S4 semantic kit v1, JSON Schemas, validated examples; every requirement of S1–S4 carries a rule identifier (144) mapped to its evidence in conformance/traceability.json
conformance/ 32 fixtures run through lsp-mcpp-conformance: the MSVC family with import std without a developer environment (cl.exe, clang-cl, clang++ for the MSVC ABI), mcpp's own emit build-database with gcc, llvm and msvc, simulated mcpp answers for what a real mcpp cannot be made to do on demand, CMake with and without build databases, multi-root, contexts, watching and reloading, payload corruption, clean machines, startup timing, self-hosting
tests/, testing/ 19 unit-test programs on a small lspmcpp.testing harness
packaging/ Payload lock (clangd 23.1.0, llvm-project 23.1.0, llvm-mingw 20260826), clangd trimming, lsp-mcpp-kit assembly, payload layout, integrity and verification, xlings split
editors/vscode/ "C++ Modules" (mcpp-community.lsp-mcpp): language status item, 4 commands, 4 settings, conflict prompt, Command Line Tools prompt; end-to-end tests of the packaged VSIX
.github/workflows/ CI on every commit (below), nightly (startup timing medians, self-hosting), release

About 100 source files (.cppm interfaces and .cpp implementations), no header files, no preprocessor directives. JSON and command-line parsing come from mcpp-index modules (nlohmann.json, mcpplibs.cmdline).

Ecosystem changes this needed

Made under the rule recorded as D25: openkal defects are fixed upstream, requirements are only recorded. Each version requirement in this ecosystem is exact, so every fix travels up through a release of each package that pins it.

Defect (design §12.9) Fix Index
K1: _GNU_SOURCE missing from libc++'s __config_site for macOS and Windows; header-wrapping modules such as nlohmann.json did not build mcpplibs/openkal-llvm-runtime#17 (0.9.2) mcpp-index#414
K6: musl's C++ pthread_t was 32 bits on Windows; every std::thread faulted when joined mcpplibs/openkal-musl#31 (0.13.2), mcpplibs/openkal-llvm-runtime#18 (0.9.3) mcpp-index#415, #416
K7: release builds for Windows received shortened arguments (the optimizer's wcslen into 32-bit musl) mcpplibs/openkal-windows#20 (0.7.1) mcpp-index#417
K9: a detached thread's exit overran musl's 256-byte shared stack; on macOS the server crashed when its input reader ended mcpplibs/openkal-musl#32 (0.13.3), mcpplibs/openkal-llvm-runtime#19 (0.9.4) mcpp-index#419, #420
K10: channel ends were inheritable on Windows; a child whose input was a channel never saw its end mcpplibs/openkal-windows#21 (0.7.2) mcpp-index#417
K11: Windows arguments lost their backslashes, every element was quoted so cmd.exe could not read its switches, and directories carried the \\?\ prefix cmd.exe refuses mcpplibs/openkal-windows#22 (0.7.3) mcpp-index#418
K12: Windows environment values came back with /, so %ComSpec% pointed cmd.exe at its own switches and CMake, Ninja and the mcpp launcher failed from a child mcpplibs/openkal-windows#23 (0.7.4) mcpp-index#421
K13: every macOS release program faulted before main; the syscall wrapper declared x1, which the kernel overwrites, an input mcpplibs/openkal-macos#20 (0.9.1), mcpplibs/openkal-musl#33 (0.13.4), mcpplibs/openkal-llvm-runtime#20 (0.9.5) mcpp-index#421, #422, #423
K14: Windows programs allocating 131,052 bytes or more wrote past their mapping; musl's mallocng uses an individually mapped allocation up to its last page, and the mmap emulation mapped only the bytes asked for mcpplibs/openkal-musl#34 (0.13.5), mcpplibs/openkal-llvm-runtime#21 (0.9.6) mcpp-index#424, #425

Each release was mirrored to GitCode and its archive hash checked against two GitHub downloads. lsp-mcpp builds on openkal-llvm-runtime = "0.9.6"; the Windows and macOS payloads carry release servers cross-built on Linux.

For mcpp itself, emit build-database was proposed in mcpp-community/mcpp#636, implemented by mcpp in mcpp-community/mcpp#639 and released as 2026.9.15.1, which CI, nightly and release now pin. As mcpp's maintainers settled there, mcpp writes S1 level 2 and leaves level 3 to the S1 library (lspmcpp.spec.options structures the arguments; the options it derives only restate them, so the engine still compiles the arguments); visible-sets lists every other set, because mcpp resolves imports over one flat module graph; and sets are per package, plus <package>:test and mcpp:std. S1 gains informative text on both points.

Both sides of that contract are checked against the released mcpp on every host (usable plan 10.6). The conformance job passes the envelopes mcpp prints for the host's mcpp fixtures to specs/tools/validate.py (S2 and S1 schemas, S1 semantics, the #636 contract), and the fixtures then have the server consume them: level 3, a test that imports the package's module across sets, an unchanged workspace, and mcpp-watch for reloading, a stale model on a broken mcpp.toml and its recovery. The simulated fixtures replay recorded output of that mcpp. The loop found and fixed: standard library units' local-arguments naming their source, a protocol query on every reload (0.3 s through the xlings launcher), and a bare failure when a project's .xlings.json pins an mcpp that is not installed.

The clangd 23.1.0 align_val_t regression on the MSVC STL is fixed upstream in clang 23.1.1 (llvm/llvm-project#218152) and recorded for the ecosystem as mcpp-community/mcpp#640. For now the payload stays on clangd/clangd's 23.1.0 with aligned allocation turned off in MSVC STL contexts: clangd/clangd publishes no 23.1.1, and LLVM's own 23.1.1 Linux clangd needs glibc 2.34 (#2).

Recorded without a fix: MinGW mm_malloc.h (K2), boost.ut crashes on Windows and macOS hosts (K5), libc++ wide strings over 32-bit musl wcslen (K8), two openkal requirements (K3, K4), and the deferred items listed in #2.

Verification

Run 34883539883 on the head commit 8f75899 passed all 18 jobs:

Job Hosts Content
specifications ubuntu-24.04 Schemas, examples, simulated producer data and mcpp's contract, rule traceability (144 rules, none pending)
build and unit tests ubuntu-24.04, macos-14, windows-2022 19 programs in the dev and the release profile
cross-build from Linux x86_64-windows-gnu, aarch64-macos Release server and runner, uploaded for the payloads
payload linux-x64, win32-x64, darwin-arm64 Trimmed clangd 23.1.0, lsp-mcpp-kit, integrity manifest, verified layout
conformance mcpp's own build databases against the specifications; Linux 19 fixture runs, macOS 5, Windows 15; cold and warm startup with SC4 Windows fixtures run the server without a developer environment
clean machine Linux container without a compiler, Windows with Visual Studio hidden, macOS without Command Line Tools and Xcode Conformance fixture and the packaged extension's end-to-end suite
VS Code end to end linux-x64, darwin-arm64, win32-x64 Packaged VSIX, UI counters, workspace unchanged, conflict handling

The nightly workflow ran once on a throwaway branch (34837141351, all jobs passed): startup timing medians over three rounds (first navigation cold/warm: Linux 5.5/2.0 s, macOS 4.4/1.9 s, Windows 8.5/2.9 s) and self-hosting on the mcpp and lsp-mcpp repositories. Its job with the latest mcpp ran the same way (34841510727, mcpp 2026.9.14.3, passed). With mcpp 2026.9.15.1 pinned, the nightly ran again on this branch (34882966882, all 25 jobs passed): lsp-mcpp self-hosted at level 3 on Linux and macOS through mcpp's own build database, the mcpp repository through the --configure-only fallback of the mcpp 2026.9.14.1 its .xlings.json asks for, and the latest-mcpp job with mcpp-watch added.

Follow-ups that were recorded rather than done are tracked in #2.

…d LSP 3.18 protocol, S1 spec model, specs and VS Code client

- mcpp package with three executables sharing src/ modules; lspmcpp.os per target
  selected by cfg path dependencies and consumed with if constexpr
- platform: process (spawn, pipes, bounded wait), env, fs, dirs, stdio, task
  channels over openkal; unit tests start the test binary itself as the child
- JSON from nlohmann.json, command line from mcpplibs.cmdline, tests with boost.ut
  (all mcpp-index modular packages); openkal-llvm-runtime 0.9.2 carries the
  _GNU_SOURCE fix that lets them cross-build for macOS and Windows
- lsp: framing, connection to a child peer, protocol constants generated from
  the LSP 3.18.0 meta model by lsp-mcpp-lspgen
- spec: S1 database model, P3286 module metadata, module resolution order
- specs/: S1-S4 in English with JSON Schemas and validated examples
- editors/vscode: thin client with language status item, 4 commands, 4 settings
- design doc: D22-D25, ecosystem library reuse (12.8), openkal issue policy and log (12.9)
…ery, normalizers, engine plans, project model

- project.scan: lexical module/import scanner with UTF-16 ranges; conditional
  declarations are reported as uncertain
- project.compdb: compile_commands.json in both forms, POSIX and Windows
  command splitting, response files
- toolchain.probe/discover: GCC, MinGW GCC, Clang (libc++ or libstdc++), Apple
  clang, cl.exe, clang-cl; a persistent probe cache; compiler discovery on PATH,
  mcpp and xlings stores, Homebrew LLVM and Visual Studio
- normalize.gnu/msvc: design 14.4 rules P1-P7 and kit arguments
- normalize.plan: context selection, resolvability check that leaves out
  interfaces whose imports cannot resolve (E13), ambiguity, std units once
- project: detection, inference from sources, mcpp and CMake providers, and a
  model loader that always falls back to inference
…ver core; module index

boost.ut 2.3.1 does not compile with clang 22.1.8 on a Windows host (frontend
crash in code generation, dev and release profiles), so the unit tests use a
small named-module harness from a path dev-dependency (testing/). Recorded as
K5 in the design doc's ecosystem issue log.
…onformance runner

- index.modules: module-name definition (interfaces, partitions, implementation
  units, std from manifests), hover, import completion, outline, workspace
  symbols, unresolved/ambiguous/partition diagnostics, S3 graph and moduleInfo
- engine.clangd: pinned clangd with modules support and dirty headers
- server.session: one event loop; documents replayed into the engine after the
  first plan is written; requests deferred until then; watchdog, crash restart
  with backoff, database rewrite and restart on model or context change;
  cxxModules/status and the S3 requests
- server.cli: serve, check (model, plan, module diagnostics and clangd --check),
  model (S1, compile_commands.json or the engine database), version
- conformance runner and the inferred fixture: all 16 checks pass locally
  against clangd 23.1.0 in about 5 seconds
- platform.task: copy-initialize channel values; braces wrapped JSON in an array
…xtures; libc++ from include paths; units with unresolvable imports stay out of the engine database
…mports, discovery order, timers, layout, K5 on macOS)
…ule edits reach importers

VS Code writes file:///c%3A/...; clangd and the engine database use
file:///C:/..., and clangd matches dirty buffers to module sources by exact
path. Diagnostics come back under the client's URI. Verified with the Windows
server, Windows clangd 23.1.0 and the MinGW libc++ kit under Wine: C7 passes.
…atabases; fast start from the last model

- load_mcpp runs `mcpp emit build-database --format jsonl` (S2) and uses the S1
  document it names; without that producer it keeps the compile database path
- enrich_database completes any producer's S1 document (CMake, mcpp, a
  configured file): probes compilers, scans units without a role
- the session saves model.json per workspace and restores the module index from
  it before the new model loads (design 13.1)
… end to end; release workflow; payload tooling

- packaging/: payload lock (clangd 23.1.0, llvm-project 23.1.0 sources,
  llvm-mingw 20260826, all sha256-verified), clangd trimming, lsp-mcpp-kit
  assembly (libc++ configured from source for linux-x64 and darwin-arm64,
  extracted from llvm-mingw for win32-x64), payload assembly and verification
- ci.yml: payload job per platform (Windows server cross-built on Linux),
  conformance fixtures per host against the payload, VS Code e2e and VSIX per
  platform; a composite action installs xlings, mcpp and LLVM
- release.yml: reuses ci.yml, then publishes VSIX and payloads with checksums;
  Marketplace, Open VSX and xlings-res steps run only with credentials
…v profile; xlings packaging

- server CLI accepts --stdio and --clientProcessId, which vscode-languageclient
  and other clients append; the VS Code end-to-end jobs failed on it
- payload jobs build with the dev profile: optimized builds over openkal-windows
  and openkal-macos misbehave at startup (argv truncated on Windows, a segfault on
  macOS; recorded as K7 until root-caused)
- the server uses the payload it sits in, and a kit installed by xlings
- packaging/xlings: xim-pkgindex descriptor templates for lsp-mcpp and
  lsp-mcpp-kit; xlings_artifacts.py splits payloads into xlings-res archives and
  renders the descriptors; release.yml publishes them
- design: K6 corrected (the runtime pins musl exactly; runtime 0.9.3 follows),
  K7 recorded, distribution rows updated
…e; verification rejects such pairs

The Linux kernel headers carry pairs such as xt_mark.h and xt_MARK.h. A VSIX
refuses them and case-insensitive file systems fold them into one, so the kit
builder keeps the all-lowercase name and the payload check fails on any pair.
…ecutable

VS Code 1.110 renamed Contents/MacOS/Electron to Code and later removed the
compatibility link, so test-electron 2.5 failed to spawn it on macos-14.
3.1 reads the bundle's executable and needs Node 22.
…wering

After the server exits, or lets two requests in a row reach their deadline, the
remaining checks fail at once with the reason instead of each waiting out its
own timeout.
musl's C++ pthread_t was thirty-two bits on Windows, so every std::thread there
faulted when joined (mcpplibs/openkal-musl#31, mcpplibs/openkal-llvm-runtime#18).
…tem's spelling

The command interpreter scans its whole command line for switches, so an
argv[0] of C:/Windows/System32/cmd.exe read /cmd.exe as /c and ran md.exe.
Tests: every argument arrives unaltered, including backslashes, quotes and empty
ones; on Windows the command interpreter runs in the work directory.
VS Code listens on a socket inside the user data directory, and macOS limits a
socket path to 104 bytes; the default under .vscode-test in a CI checkout is
longer and failed with listen EINVAL.
…he openkal releases that fix them

packaging/README.md describes the payload layout, the lock, the build steps and
host requirements, what verification checks, measured sizes and the release
split for xlings. packaging/kits/README.md describes each platform's kit, its
kit.json as built, and how the server applies a kit. The design's issue log
records the detached-thread exit overflow (openkal-musl#32), the inheritable
channel ends (openkal-windows#21) and the Windows argument and directory
handling (openkal-windows#22).
…ws 0.7.3)

A detached thread's exit no longer overruns musl's shared stack, which crashed
the server on macOS when its input reader ended (openkal-musl#32). On Windows a
child whose input is a channel sees its end (openkal-windows#21), and a started
program receives its arguments and directory as given (openkal-windows#22).
…mporters through symbolic links

mcpp names sources by their resolved path, and a macOS workspace under /var is
/private/var to it. The document, the database and clangd's matching of an
unsaved buffer then used different names for one file: the module was reported
ambiguous and completion never saw the edit (C7 on macos-14). The workspace
root, open documents and database sources now use the name with symbolic links
followed; clangd receives that name, and locations in open documents return to
the client under the client's own URI. CI runs mcpp-llvm through a symbolic
link on Linux.
The clean-machine jobs package the platform VSIX and run the main suite with the
machine as the conformance fixture saw it: in a node:22-bookworm-slim container
with no compiler (as root, so VS Code gets --no-sandbox), and on Windows with
Visual Studio hidden and only system directories and node on PATH (usable plan
W5). LSP_MCPP_E2E_ONLY selects test files for machines where only some apply.
A Windows workspace root's own path always contains ':' right after its
drive letter; the previous encoding split the synthetic request id on the
first ':', truncating the root key and misrouting the client's eventual
response to no root at all. Caught by a new round-trip unit test.
Per-root state moved from session.cpp into server/workspace.cpp; the canonical
cache directory (76109ec) and std-only preparation (0000330) are applied there.
CI fixture lists combine W4's and W9's; watch-polling runs without dynamic
registration through run_fixture.
…s; macOS clean machine

On macOS /usr/bin/clang++, c++ and xcrun are shims that open the system's
"install developer tools" dialog when no developer directory exists. Discovery
probed them and the SDK re-check ran xcrun every 30 s, so a Mac without the
Command Line Tools would have been asked again and again. Whether tools are
present is now read from the file system (DEVELOPER_DIR, the xcode-select link,
the Command Line Tools and Xcode directories); without them discovery skips
/usr/bin and the SDK lookup does not run xcrun. The extension's one question is
the only one.

Clean machine on macOS (usable plan W5, U7): Command Line Tools and Xcode hidden,
fixture inferred-no-sdk (degraded, sdk-missing with the install command, a std
user answered at once, module-level features), and the extension's sdk-missing
suite (asked once, not again after a restart). The runner's status check matches
an issue's command; check kind `responds` passes on any answer in time.
The payload integrity work (70e3bd2, W9.4) raised payload-version to 2 when it
added "files"; the extension still accepted only 1 and refused every payload
the packaging now assembles, so each VS Code end-to-end job failed before its
first test. Versions 1 and 2 are accepted; the extension reads neither's files.
… (S2-5-1, S2-5-9); multi-root fixes

S2 5 asks a consumer to watch every entry of a database's watch list and to keep
using the last database that loaded, telling the user it may be stale, when
discovery fails. Both were the last pending S2 rules.

- base.glob: LSP glob patterns (*, **, ?, {a,b}, [...], [!...]), case following
  the file system.
- A model's watch entries are registered with the client (relative patterns when
  it supports them, one registration replaced by the next model's) or read by the
  polling worker; files a new model brings into the watch start from what they
  are rather than being reported as created. A change to one loads the model again.
- A reload whose model is unchanged (the usual answer to a saved source mcpp
  watches) swaps the model and stops there: no re-reading of every source, no
  replan. A replan that leaves the module graph as it was no longer closes prime
  units or restarts preparation in flight.
- A project whose producer answered before and now fails keeps its model; the
  status is degraded with model-stale, naming the producer's reason, until it
  answers again. ProjectModel.detected tells such a failure from a project that
  is no longer that kind of project.
- An mcpp that can emit a build database and fails now reports its own
  diagnostic instead of falling back to build --configure-only, which wrote into
  the project and replaced the reason with "did not produce compile_commands.json".

Multi-root (W9.1), failing on macOS and Windows CI since the merge:
- cxxModules/status names a root by the URI the client sent, not the canonical
  path (/var vs /private/var, RUNNER~1), so per-folder status matches.
- workspace/didChangeWatchedFiles entries reach the root that owns them: a Json
  brace-initialized from a Json became a one-element array, left every change
  without a path, and sent all of them to the first root.

Conformance: the runner acts as an editor's file watcher for the watchers a
server registers, keeps each root's status history, and write-file can wait for a
reload (optionally of one folder) or copy content from another file; status
checks match a part of an issue's message. New fixtures mcpp-emit-watch (also run
@polling) and mcpp-emit-broken; multi-root checks a reload in its second folder.
The mock's build leaves a compile_commands.json, so an unwanted configure shows.
Traceability: 137 rules, none pending.
…erver that offers module requests

- specs: S3's seven requirements carry identifiers and evidence; validate.py traces S1 to S4,
  144 rules, none pending. S3 states that project.root is the folder's URI as the client sent it,
  and lists issue codes module-build-failed and model-stale.
- server: cxxModules/status changes that keep the state are coalesced into one notification
  every 250 ms (S3-4-2); a new state still goes out at once.
- extension: cxxModules/contexts, setContext and graph are sent only when the server declared
  experimental.cxxModules (S3-3-2).
- conformance: a settled status that does not match yet is looked at again for up to three seconds.
- ci: the Linux clean-machine container step times each phase and bounds it, and installs its
  packages without fsync (shared-mime-info's update-mime-database stalled the step for the whole
  job twice); the apt log is printed when installation fails.
… preparation progresses

An interactive request (definition, hover, completion) is answered without clangd after 10 s.
On a large project the modules a newly opened file imports take longer than that, so the first
go-to-definition returned nothing and had to be repeated. Now, when the deadline passes while
the file awaits its modules and preparation finished a module within the last 10 s, the request
waits another 5 s, up to the ordinary request timeout (60 s). A request with no sign of progress
is still answered at 10 s.

mcpp repository (171 modules, 32 threads, dev build): the first declaration answered once at
26.2 s with no request timing out, where each earlier variant timed out once or twice before an
answer at 33-35 s.
…usable plan W2.3)

For a workspace without a build system, Windows prefers Visual Studio's MSVC when its toolset has
the std module (D27). A toolset without one (MSVC 14.29, for instance) was skipped silently; the
status now carries a notice, msvc-without-std-module, naming the toolset. A notice reduces no
feature, so the state and severity stay as they are. The extension shows the first notice in the
language status item's hover when there is no issue to show; until now it showed no notice at
all, producer-writes-project included.
…iting files keep their cores

Probing where warm starts spend their time (verbose clangd logs on the three CI hosts) showed
prime units competing with the file a person opened: each `import M;` unit runs clangd's own
prerequisite pass, scanning std.cppm and validating every cached BMI again, and holds one of
clangd's workers (-j, physical cores) while it does. On a 2-core machine the opened file waited
for a worker, then repeated the scan.

- A module whose BMI clangd's persistent cache already holds, finished and at least as new as its
  source, completes at once without a unit, as a partition does (Primer::start_ready's `built`).
  The cache is read once per plan, when a module first becomes ready.
- While files wait for their modules, preparation leaves each of them a core (hardware threads
  count as half a core except on macOS); with none waiting it uses every thread as before.

timing fixture, cold and warm first navigation (median of 5 warm starts), i9-13900K pinned:
  2 cores:  2.51 -> 2.35-2.49 s cold, 1.07 -> 0.80 s warm (no preparation at all: 2.15 / 0.80)
  4 cores:  2.14 -> 2.12 s cold, 0.81 -> 0.81 s warm
  1 core, 2 threads (clangd -j=1): 4.43 -> 4.09 s cold, 1.81 -> 1.00 s warm
mcpp repository (171 modules, 32 threads): cold 25.6-26.2 -> 24.9-25.7 s (no preparation: 38.0 s),
warm 5.5-5.6 -> 4.9-5.0 s.
…string that outlives its views

completion-contains with "insert" split the document with base::split_lines(text_of(file)):
text_of returns a temporary, so the line views dangled and the inserted text was assembled from
freed memory. It failed intermittently, most often on large files: in self-lsp-mcpp the text sent
to the server held a NUL and was cut short, clangd answered completion for the garbled buffer, and
the check retried until its timeout (minutes in the nightly measurement). Four cold runs now pass
C6 in 0.1-0.2 s.
…quests

With no file waiting for its modules, preparation could open as many prime units as there are
hardware threads, more than clangd's workers (-j, physical cores), so a file opened or a request
made meanwhile queued behind units that take seconds each. Preparation now leaves one core for
requests besides one for each waiting file.

timing fixture (2 / 4 cores): 2.41 / 2.14 s cold, 0.81 / 0.81 s warm, as before.
mcpp repository (32 threads): 26.3 s cold (24.9-26.2 s before, within run-to-run spread), 4.9 s warm.
…sign

Usable plan section 10: the result of every work item with its evidence, where implementation
departed from the plan and why, what was added beyond it (S2-5-1/S2-5-9, S3 traceability,
multi-root fixes, waiting requests, module preparation policy, the runner's completion check,
the mcpp#636 contract notes), measurements on the development machine, the CI hosts (nightly
medians) and the two self-hosting repositories, and the upstream changes. Design section 1.3
records the measured values beside each target, section 15.1 the preparation rules the
measurements led to, 12.3 and 13.1 the watch of what a producer names.
A nightly job installs the newest mcpp the index has (MCPP_VERSION=latest, which the install
script now accepts) and runs mcpp-gcc and mcpp-llvm with the payload CI built, so a producer
change shows up before the pinned version moves.
mcpp writes level 2 and leaves level 3 to the S1 library (mcpp-community/mcpp#636).
lspmcpp.spec.options structures GCC/Clang and cl.exe/clang-cl arguments into
SemanticOptions: what only a build needs (outputs, dependency files, optimization,
debug information, warnings, BMI locations) is dropped, and what has no structured form
stays under raw-semantic-arguments in order. A set's options come from its
baseline-arguments, or from the arguments all its units share; a unit's delta from its
local-arguments, or from what it adds to the set's.

Databases a producer wrote are completed when the model loads. The options this adds are
marked derived: they restate the arguments, so the engine plan keeps compiling the
arguments (S1 9 rule 1 concerns the options a producer states).
…d on

mcpp's answers on mcpp-community/mcpp#636: level 2 only; one set per package plus
<package>:test and mcpp:std rather than per target; and visible-sets listing every other
set, since the build resolves imports over one flat module graph.

- mcpp-emit, mcpp-emit-package-std and mcpp-emit-watch carry that shape without
  ide.options; mcpp-emit gains a test that imports the package's module across sets
  (C10-test), and its status still asserts level 3, now reached through the S1 library.
- validate.py checks the contract on every simulated database and on the S2 envelope
  example, which is reshaped the same way.
- S1 sections 7, 9 and 11.1 gain informative text on per-package sets with flat
  visibility and on consumer-side completion; the level 3 example names a neutral
  generator. No normative change.
- Design doc and usable plan record the answers, the S1 library, mcpp#640, and the
  short-term decision to keep the clangd/clangd 23.1.0 payload with the aligned
  allocation workaround.
…ocal-arguments

mcpp 2026.9.15.1 writes a standard library unit's local-arguments as what follows the set's
baseline, `--precompile <source> -o <bmi>`, so complete_options put the source into the unit's
raw-semantic-arguments. Inputs are now removed from local-arguments the way they are from the
unit's arguments. A toolchain whose stdlib names no module-metadata (mcpp describes std as
units of mcpp:std) is also exported without an empty one.
A project's .xlings.json selects the mcpp that runs inside it; when that version is not
installed, xlings answers every command in its place and runs nothing. The status then said
only "mcpp did not produce compile_commands.json". It now carries what mcpp, or xlings in its
place, printed: "mcpp could not describe the project: <explanation>" (mcpp-no-database).

producer-writes-project is given only when the configure-only fallback ran and wrote into the
project, and names the mcpp version and that mcpp 2026.9.15.1 added emit build-database. The
std-module.json path (W8) stays for such older mcpp versions.

lsp-mcpp-mock-mcpp gains {"unavailable": "..."} for this case (fixture mcpp-emit-unavailable)
and declares the effects mcpp 2026.9.15.1 declares.
…database

mcpp implemented mcpp-community/mcpp#636 in #639 and released it as 2026.9.15.1. CI, nightly
and release now pin it, and both sides of the contract are checked against it (usable plan 10.6):

- The producer: the conformance job records what `mcpp emit build-database --format json`
  prints for each of the host's mcpp fixtures and passes the envelopes to
  specs/tools/validate.py (S2 envelope and S1 schemas, S1 semantics, the #636 contract).
- The consumer: mcpp-gcc, mcpp-llvm, mcpp-msvc and mcpp-llvm-msvc assert level 3, a test that
  imports the package's module across sets, and an unchanged workspace; multi-root's mcpp root
  asserts level 3.
- mcpp-watch (all hosts, and @polling on Linux) runs S2 5 against mcpp itself: a new module
  reloads the model, a broken mcpp.toml leaves it stale with MCPP_BUILD_DATABASE_PLAN_FAILED, the
  repair makes it fresh.
- The simulated fixtures' mcpp-mock.json files are recordings of mcpp 2026.9.15.1's output;
  package-std's std units now sit in a std cache directory that does not exist on CI.
- Nightly: mcpp-watch joins the latest-mcpp job; self-mcpp installs the mcpp its commit's
  .xlings.json asks for, which xlings runs inside that repository.
Every reload a watched input caused ran `mcpp --protocol-version` before emit build-database,
0.3 s through the xlings launcher. A yes is now kept per mcpp executable and root until emit
build-database fails; a no is not, so an mcpp upgraded as producer-writes-project suggests is
picked up without a restart. The launcher's path does not change when the mcpp behind it does,
so the executable's path, size and time (usable plan W3) cannot be the key.
A project whose module units are all .cppm and a project that separates interfaces (.cppm)
from implementations (.cpp) are the two shapes lsp-mcpp has to serve; neither had a fixture on
every commit, only the nightly self-hosting runs covered them.

- mcpp-split: interface units and an interface partition in .cppm, two implementation units
  (`module hello.greet;`) in .cpp and an implementation partition. Declarations and definitions
  are reached from importers and tests, implementation units navigate into partitions, complete
  module-internal names and hover, edits reach importers, and the workspace stays unchanged.
  mcpp-split-gcc (Linux) and mcpp-split-msvc (Windows) are the same project with GCC 16 and
  msvc@system.
- mcpp-all-cppm: a primary interface re-exporting two partitions and a second module, with the
  implementations inside the interfaces; navigation into partitions and through re-exports,
  completion through the re-exported module, edit propagation.

Both build and run with mcpp 2026.9.15.1 on LLVM 22.1.8 and GCC 16.1.0, and passed five
consecutive local rounds (15 runs, no failure). mcpp's own envelopes for them are validated by
the conformance job's specifications step like the other mcpp fixtures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant