Skip to content

Commit 74bb0af

Browse files
committed
fix: preserve hermetic clangd configuration
1 parent 15c5c1c commit 74bb0af

11 files changed

Lines changed: 353 additions & 15 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# 更新日志
22

3+
## 0.2.4
4+
5+
- 修复 hermetic mcpp 编译数据库被插件追加 `--query-driver` 后导致的 clangd 标准库和
6+
模块误诊断;仅在编译命令未自带完整 sysroot、标准库和 no-default-config 配置时补充
7+
query driver。
8+
- 模块检查优先选择项目源码,避免误选 `.mcpp` 依赖缓存或 `target` 生成源码,改善补全、
9+
跳转和诊断稳定性。
10+
-`build.mcpp` 作为独立的语法高亮语言处理,不再让 clangd 把 mcpp 构建脚本当作普通
11+
C++ 翻译单元。
12+
313
## 0.2.3
414

515
- 为精确文件名 `build.mcpp` 增加 C++ 语言关联,复用 VS Code 内置 C++ 高亮和现有模块

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
把 mcpp 工程、C++ 模块语法和官方 clangd 扩展接入 VS Code。
88

9-
当前版本为 `0.2.3`。扩展负责工程发现、clangd 配置、模块状态检查以及常用
9+
当前版本为 `0.2.4`。扩展负责工程发现、clangd 配置、模块状态检查以及常用
1010
mcpp CLI 操作;它不实现新的 C++ 语言服务器,也不替代 mcpp 的构建逻辑。
1111

1212
> 当前完整的模块语义能力只支持 LLVM/Clang 工具链。GCC 和 MSVC 工程仍可使用
@@ -36,7 +36,7 @@ mcpp CLI 操作;它不实现新的 C++ 语言服务器,也不替代 mcpp 的
3636
VSIX,然后在 VS Code 中执行 **Extensions: Install from VSIX...**,或者运行:
3737

3838
```sh
39-
code --install-extension /path/to/mcpp-vscode-0.2.3.vsix
39+
code --install-extension /path/to/mcpp-vscode-0.2.4.vsix
4040
```
4141

4242
安装后确认当前 VS Code profile 中同时存在 `mcpp-community.mcpp-vscode`
@@ -83,14 +83,20 @@ xlings 安装。
8383

8484
- 将精确文件名 `mcpp.toml` 识别为独立的 mcpp TOML 语言,内置表、键、字符串、数字、
8585
布尔、日期时间和注释高亮。
86-
-`build.mcpp``.cppm``.ixx``.mpp``.ccm` 关联到 VS Code 内置 `cpp` 语言。
87-
-`source.cpp` 注入模块语法规则。
86+
- 将精确文件名 `build.mcpp` 识别为独立的 mcpp build language,并复用 C++ TextMate
87+
grammar;`.cppm``.ixx``.mpp``.ccm` 继续关联到 VS Code 内置 `cpp` 语言。
88+
- 向 C++ 源文件注入模块语法规则。
8889
- 覆盖 `module``export module``import``export import`、模块名和模块分区。
8990
- 支持尚未输入分号的编辑中间态。
9091

9192
语法高亮只负责词法着色。错误模块名、不可见声明等红色诊断来自语言服务器,不能由
9293
TextMate 语法规则提供。
9394

95+
`build.mcpp` 是由 mcpp 宿主执行的构建脚本,不是普通 C++ 翻译单元。当前扩展只为它
96+
提供 C++ 风格的语法高亮和编辑器括号/注释规则,不启动 clangd 语义诊断;因此
97+
`import mcpp;` 不会再被误报为缺少 C++ 模块。真正的 mcpp API 补全需要 mcpp 核心
98+
未来生成宿主 helper 的 CDB 和 PCM 映射。
99+
94100
### LLVM 与 clangd 集成
95101

96102
扩展读取 mcpp 生成的 `compile_commands.json`,然后:
@@ -99,7 +105,8 @@ TextMate 语法规则提供。
99105
2. 从显式设置、编译器目录、匹配的 xlings `llvm-tools` 目录和 `PATH` 中查找 clangd。
100106
3. 执行编译器和 clangd 的版本命令,比较 LLVM 版本与 revision。
101107
4. 保留用户已有的 `clangd.arguments`,展开 `${workspaceFolder}`
102-
`${workspaceRoot}`,并添加精确的 `--query-driver`
108+
`${workspaceRoot}`;只有在 CDB 未提供完整 sysroot、标准库和 no-default-config
109+
参数时,才添加精确的 `--query-driver`
103110
5. 根据 CDB 中是否已有显式 PCM 映射以及 `mcpp.modulesSupport` 设置,决定是否增加
104111
clangd 实验模块参数。
105112
6. 必要时重启官方 clangd 扩展,并执行一次最长 60 秒的 `clangd --check`
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Clangd Hermetic CDB and build.mcpp Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Remove false clangd diagnostics from hermetic mcpp projects, keep module checks on project sources, and prevent clangd from treating `build.mcpp` as an ordinary C++ translation unit.
6+
7+
**Architecture:** Keep clangd's query-driver discovery for ordinary or cross-toolchain CDBs, but skip it when the selected mcpp command already declares a no-default-config sysroot and standard library. Give `build.mcpp` its own language id backed by the C++ TextMate grammar so syntax highlighting remains while the official clangd extension no longer claims the document. Rank real project sources above `.mcpp` dependency-cache and `target` commands for the extension's direct module check.
8+
9+
**Tech Stack:** TypeScript 5.9, VS Code extension manifests and TextMate grammars, Node.js built-in test runner, clangd 22.1.8, VSCE.
10+
11+
---
12+
13+
### Task 1: Preserve hermetic compilation commands
14+
15+
**Files:**
16+
- Modify: `src/analysis.ts`
17+
- Modify: `src/extension.ts`
18+
- Test: `test/analysis.test.ts`
19+
20+
- [x] **Step 1: Write the failing hermetic CDB argument tests**
21+
22+
Add tests that pass the selected CDB arguments through `buildClangdArguments`. A command containing `--no-default-config`, `-nostdinc++`, an explicit `-isystem.../include/c++/v1`, and `--sysroot=...` must remove a previously managed `--query-driver`; an ordinary command must still receive the selected compiler as its query driver.
23+
24+
- [x] **Step 2: Run the analysis test and verify RED**
25+
26+
Run: `npm run compile && node --test dist/test/analysis.test.js`
27+
28+
Expected: the hermetic test fails because the current implementation always appends `--query-driver`.
29+
30+
- [x] **Step 3: Implement the minimum query-driver policy**
31+
32+
Add `compilationArguments?: readonly string[]` to `ClangdArgumentOptions`. Treat a command as self-contained only when all four hermetic markers are present, remove managed query-driver arguments, and append the selected compiler only for non-self-contained commands. Pass `context.analysis.arguments` from both the workspace configuration path and direct `clangd --check` path in `src/extension.ts`.
33+
34+
- [x] **Step 4: Run the focused tests and verify GREEN**
35+
36+
Run: `npm run compile && node --test dist/test/analysis.test.js`
37+
38+
Expected: all analysis tests pass.
39+
40+
### Task 2: Select a project-owned module check source
41+
42+
**Files:**
43+
- Modify: `src/analysis.ts`
44+
- Test: `test/analysis.test.ts`
45+
46+
- [x] **Step 1: Write the failing dependency-cache ranking test**
47+
48+
Create a CDB fixture whose first LLVM command points to `/work/app/.mcpp/.../transaction.cpp` with PCM flags and whose later command points to `/work/app/src/main.cpp` with the same flags. Assert that `analyzeCompilationDatabase` selects `/work/app/src/main.cpp`.
49+
50+
- [x] **Step 2: Run the focused test and verify RED**
51+
52+
Run: `npm run compile && node --test dist/test/analysis.test.js`
53+
54+
Expected: the new test selects the `.mcpp` command and fails.
55+
56+
- [x] **Step 3: Implement project-source ranking**
57+
58+
Keep the existing in-project and module-interface scoring, but add a higher score for paths below the command directory whose first component is neither `.mcpp` nor `target`. Preserve the existing fallback when a CDB contains only dependency or generated commands.
59+
60+
- [x] **Step 4: Run the focused tests and verify GREEN**
61+
62+
Run: `npm run compile && node --test dist/test/analysis.test.js`
63+
64+
Expected: all analysis tests pass and the selected UChat check source is a member source file.
65+
66+
### Task 3: Make build.mcpp syntax-only
67+
68+
**Files:**
69+
- Modify: `package.json`
70+
- Create: `syntaxes/mcpp-build.tmLanguage.json`
71+
- Create: `syntaxes/mcpp-build-language-configuration.json`
72+
- Modify: `test/artifacts.test.ts`
73+
- Modify: `README.md`
74+
75+
- [x] **Step 1: Write the failing manifest and grammar tests**
76+
77+
Assert that the manifest declares exact filename language `mcpp-build`, that `files.associations` no longer maps `build.mcpp` to `cpp`, and that the new grammar maps `mcpp-build` to `source.mcpp-build` and includes the external `source.cpp` grammar. Keep the assertion that no broad `*.mcpp` association exists.
78+
79+
- [x] **Step 2: Run the artifact tests and verify RED**
80+
81+
Run: `npm run compile && node --test dist/test/artifacts.test.js`
82+
83+
Expected: the manifest still maps `build.mcpp` to `cpp`, so the new assertions fail.
84+
85+
- [x] **Step 3: Implement the custom language**
86+
87+
Register `mcpp-build` with `filenames: ["build.mcpp"]`, a C++-style language configuration, and a TextMate grammar whose root scope is `source.mcpp-build` and whose patterns include `source.cpp`. Remove only the exact `build.mcpp -> cpp` default; keep `.cppm`, `.ixx`, `.mpp`, and `.ccm` associated with `cpp`. Add `onLanguage:mcpp-build` activation.
88+
89+
- [x] **Step 4: Document the semantic boundary**
90+
91+
Update README wording so `build.mcpp` is described as C++-style syntax highlighting without clangd diagnostics, while real C++ module files continue to use clangd. Explain that semantic support for `import mcpp;` requires a future mcpp-generated host-helper CDB and PCM mapping.
92+
93+
- [x] **Step 5: Run the artifact tests and verify GREEN**
94+
95+
Run: `npm run compile && node --test dist/test/artifacts.test.js`
96+
97+
Expected: all artifact tests pass.
98+
99+
### Task 4: Version, full verification, package, install, and tag
100+
101+
**Files:**
102+
- Modify: `package.json`
103+
- Modify: `package-lock.json`
104+
- Modify: `CHANGELOG.md`
105+
106+
- [x] **Step 1: Bump the extension version**
107+
108+
Run: `npm version 0.2.4 --no-git-tag-version`
109+
110+
Add a `0.2.4` changelog entry covering hermetic query-driver handling, project-source checks, and syntax-only `build.mcpp` handling.
111+
112+
- [x] **Step 2: Run the full regression suite**
113+
114+
Run: `npm test`
115+
116+
Expected: all tests pass with zero failures.
117+
118+
- [x] **Step 3: Reproduce the original UChat clangd symptom**
119+
120+
Run matching clangd 22.1.8 against each UChat `RedisMgr.cpp` using its member CDB and the arguments produced by the fixed policy. Confirm there are no `template_instantiate_undefined`, `ovl_no_viable_function_in_call`, SDK type, or module ODR diagnostics.
121+
122+
- [x] **Step 4: Package and inspect the VSIX**
123+
124+
Run: `npm run package`
125+
126+
Expected: `mcpp-vscode-0.2.4.vsix` is created. Inspect its manifest and archive contents to confirm the new language grammar and configuration are included.
127+
128+
- [x] **Step 5: Commit only the repair files**
129+
130+
Stage the plan, source, focused tests, manifest, lockfile, grammar, README, and changelog files. Do not stage pre-existing modified or untracked design documents. Commit as `fix: preserve hermetic clangd configuration`.
131+
132+
- [x] **Step 6: Create the local release tag**
133+
134+
Create annotated tag `v0.2.4` on the verified repair commit. Do not push it or create a GitHub Release.
135+
136+
- [x] **Step 7: Install and verify the packaged extension**
137+
138+
Run: `code --install-extension /Users/cltx/projects/mcpp/mcpp-vscode/mcpp-vscode-0.2.4.vsix --force`
139+
140+
Then run `code --list-extensions --show-versions` and verify `mcpp-community.mcpp-vscode@0.2.4`. A VS Code window reload is required before the new language registration and clangd arguments take effect.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "mcpp-vscode",
33
"displayName": "mcpp",
44
"description": "mcpp 与 C++ 模块的 VS Code 集成",
5-
"version": "0.2.3",
5+
"version": "0.2.4",
66
"publisher": "mcpp-community",
77
"license": "Apache-2.0",
88
"icon": "images/logo.png",
@@ -23,6 +23,7 @@
2323
"activationEvents": [
2424
"workspaceContains:mcpp.toml",
2525
"onLanguage:cpp",
26+
"onLanguage:mcpp-build",
2627
"onCommand:mcpp.configureClangd",
2728
"onCommand:mcpp.refreshCompilationDatabase",
2829
"onCommand:mcpp.checkModuleSupport",
@@ -133,6 +134,17 @@
133134
}
134135
},
135136
"languages": [
137+
{
138+
"id": "mcpp-build",
139+
"aliases": [
140+
"mcpp build script",
141+
"build.mcpp"
142+
],
143+
"filenames": [
144+
"build.mcpp"
145+
],
146+
"configuration": "./syntaxes/mcpp-build-language-configuration.json"
147+
},
136148
{
137149
"id": "mcpp-toml",
138150
"aliases": [
@@ -146,14 +158,18 @@
146158
],
147159
"configurationDefaults": {
148160
"files.associations": {
149-
"build.mcpp": "cpp",
150161
"*.ccm": "cpp",
151162
"*.cppm": "cpp",
152163
"*.ixx": "cpp",
153164
"*.mpp": "cpp"
154165
}
155166
},
156167
"grammars": [
168+
{
169+
"language": "mcpp-build",
170+
"scopeName": "source.mcpp-build",
171+
"path": "./syntaxes/mcpp-build.tmLanguage.json"
172+
},
157173
{
158174
"language": "mcpp-toml",
159175
"scopeName": "source.toml.mcpp",

src/analysis.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface ToolIdentityComparison {
3535

3636
export interface ClangdArgumentOptions {
3737
compilerPath: string;
38+
compilationArguments?: readonly string[];
3839
modulesSupport: ModulesSupportMode;
3940
clangdIdentity?: ToolIdentity;
4041
platform: NodeJS.Platform;
@@ -201,6 +202,7 @@ export function analyzeCompilationDatabase(contents: string): CompilationDatabas
201202
const moduleInterface = /\.(?:cppm|ixx|mpp|ccm)$/i.test(sourceFile);
202203
const inProject = candidate.directory !== undefined && isWithinDirectory(candidate.directory, sourceFile);
203204
return (inProject ? 200 : 0)
205+
+ (candidate.directory !== undefined && isProjectSource(candidate.directory, sourceFile) ? 200 : 0)
204206
+ (moduleInterface ? 100 : 0)
205207
+ (candidate.hasPrebuiltModules ? 10 : 0);
206208
};
@@ -217,6 +219,18 @@ function isWithinDirectory(directory: string, file: string): boolean {
217219
|| (relative !== ".." && !relative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative));
218220
}
219221

222+
function isProjectSource(directory: string, file: string): boolean {
223+
if (!isWithinDirectory(directory, file)) {
224+
return false;
225+
}
226+
const windows = /^[A-Za-z]:[\\/]/.test(directory) || directory.includes("\\")
227+
|| /^[A-Za-z]:[\\/]/.test(file) || file.includes("\\");
228+
const pathApi = windows ? path.win32 : path.posix;
229+
const relative = pathApi.relative(pathApi.resolve(directory), pathApi.resolve(file));
230+
const firstComponent = relative.split(pathApi.sep)[0];
231+
return firstComponent !== ".mcpp" && firstComponent !== "target";
232+
}
233+
220234
function removeManagedArguments(arguments_: readonly string[]): string[] {
221235
const result: string[] = [];
222236

@@ -238,6 +252,34 @@ function removeManagedArguments(arguments_: readonly string[]): string[] {
238252
return result;
239253
}
240254

255+
function hasValueFlag(arguments_: readonly string[], flag: string): boolean {
256+
return arguments_.some((argument, index) => (
257+
(argument === flag && index + 1 < arguments_.length)
258+
|| argument.startsWith(`${flag}=`)
259+
));
260+
}
261+
262+
function hasExplicitLibcxxPath(arguments_: readonly string[]): boolean {
263+
return arguments_.some((argument, index) => {
264+
const value = argument === "-isystem"
265+
? arguments_[index + 1] ?? ""
266+
: argument.startsWith("-isystem")
267+
? argument.slice("-isystem".length)
268+
: "";
269+
return /[\\/]include[\\/]c\+\+[\\/]v1(?:[\\/]|$)/.test(value);
270+
});
271+
}
272+
273+
function isHermeticClangCommand(arguments_: readonly string[] | undefined): boolean {
274+
if (arguments_ === undefined) {
275+
return false;
276+
}
277+
return arguments_.includes("--no-default-config")
278+
&& arguments_.includes("-nostdinc++")
279+
&& hasExplicitLibcxxPath(arguments_)
280+
&& hasValueFlag(arguments_, "--sysroot");
281+
}
282+
241283
function expandWorkspaceVariables(argument: string, workspaceFolder?: string): string {
242284
if (workspaceFolder === undefined) {
243285
return argument;
@@ -276,7 +318,9 @@ export function buildClangdArguments(
276318
): string[] {
277319
const result = removeManagedArguments(existingArguments)
278320
.map((argument) => expandWorkspaceVariables(argument, options.workspaceFolder));
279-
result.push(`--query-driver=${options.compilerPath}`);
321+
if (!isHermeticClangCommand(options.compilationArguments)) {
322+
result.push(`--query-driver=${options.compilerPath}`);
323+
}
280324

281325
if (shouldEnableExperimentalModules(options)) {
282326
result.push("--experimental-modules-support");

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ async function configureClangd(
310310
clangd.path,
311311
{
312312
compilerPath: context.analysis.compilerPath,
313+
compilationArguments: context.analysis.arguments,
313314
modulesSupport,
314315
clangdIdentity: clangd.version.identity,
315316
platform: process.platform,
@@ -522,6 +523,7 @@ async function runModuleSupportCheck(
522523
officialClangdConfiguration(context.project).get<string[]>("arguments", []),
523524
{
524525
compilerPath: context.analysis.compilerPath,
526+
compilationArguments: context.analysis.arguments,
525527
modulesSupport: projectConfiguration(context.project)
526528
.get<ModulesSupportMode>("modulesSupport", "auto"),
527529
clangdIdentity: clangd.version.identity,

0 commit comments

Comments
 (0)