Skip to content

Commit 9675ea9

Browse files
committed
refactor(build): extract flags + compile_commands into dedicated modules
Architecture redesign on top of PR #24's compile_commands.json feature: 1. `src/build/flags.cppm` (NEW) — shared compile/link flag computation. One definition consumed by both ninja backend and compile_commands emitter (and future backends). Eliminates the duplicated compute_cxxflags/compute_cflags functions. 2. `src/build/compile_commands.cppm` (NEW) — generates compile_commands.json using nlohmann::json (no manual string escaping). Uses `arguments` array format (clangd recommended). Writes to <projectRoot>/compile_commands.json for zero-config clangd. Incremental: skips write if content unchanged. 3. `src/libs/json.cppm` + `src/libs/json/json.hpp` (NEW) — nlohmann/json single-header library wrapped as C++23 module, copied from openxlings/xlings. MIT license. 4. `src/build/ninja_backend.cppm` — slimmed: imports flags.cppm, delegates compile_commands to its module, removes ~200 lines of duplicated flag computation. 5. `.agents/docs/2026-05-12-compile-commands-design.md` — design doc. 6. `.gitignore` — add compile_commands.json (generated). 7. `mcpp.toml` — add `include_dirs = ["src/libs/json"]` for json.hpp. All code formatted with .clang-format from PR #24.
1 parent 7359e9d commit 9675ea9

9 files changed

Lines changed: 26300 additions & 384 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# compile_commands.json 设计方案
2+
3+
> mcpp 0.0.9 — IDE 集成(clangd / VSCode / CLion)
4+
5+
## 1. 动机
6+
7+
C++ IDE 和语言服务器(clangd)需要 `compile_commands.json` 来理解编译参数,
8+
提供跳转、补全、诊断等功能。CMake/Meson/xmake 都有此能力,mcpp 需要补齐。
9+
10+
## 2. 模块文件划分
11+
12+
```
13+
src/build/
14+
plan.cppm ← BuildPlan 数据结构(不变)
15+
flags.cppm ← 🆕 共用 flag 计算逻辑
16+
compile_commands.cppm ← 🆕 compile_commands.json 生成
17+
ninja_backend.cppm ← ninja build.ninja 生成(瘦身,复用 flags)
18+
backend.cppm ← Backend 抽象接口(不变)
19+
```
20+
21+
### 2.1 `src/build/flags.cppm`
22+
23+
职责:从 BuildPlan 计算出所有编译/链接 flag。
24+
25+
```cpp
26+
export module mcpp.build.flags;
27+
import mcpp.build.plan;
28+
29+
export namespace mcpp::build {
30+
31+
struct CompileFlags {
32+
std::string cxx; // "-std=c++23 -fmodules -O2 -I... --sysroot=..."
33+
std::string cc; // "-std=c11 -O2 -I... --sysroot=..."
34+
std::string ld; // "-static -static-libstdc++ --sysroot=..."
35+
std::filesystem::path cxxBinary; // g++ 路径
36+
std::filesystem::path ccBinary; // gcc 路径(派生)
37+
std::filesystem::path arBinary; // ar 路径
38+
};
39+
40+
CompileFlags compute_flags(const BuildPlan& plan);
41+
42+
}
43+
```
44+
45+
从 ninja_backend.cppm 中提取:
46+
- include_dirs → `-I` 拼接
47+
- sysroot → `--sysroot=`
48+
- binutils → `-B`
49+
- opt_flag (`-O2` / `-Og`)
50+
- pic_flag、user_cxxflags/cflags
51+
- C 编译器路径推导 (`derive_c_compiler`)
52+
53+
**一处计算,多处消费**(ninja、compile_commands、未来后端)。
54+
55+
### 2.2 `src/build/compile_commands.cppm`
56+
57+
职责:生成标准 compile_commands.json。
58+
59+
```cpp
60+
export module mcpp.build.compile_commands;
61+
import mcpp.build.plan;
62+
import mcpp.build.flags;
63+
64+
export namespace mcpp::build {
65+
66+
std::string emit_compile_commands(const BuildPlan& plan,
67+
const CompileFlags& flags);
68+
69+
void write_compile_commands(const BuildPlan& plan,
70+
const CompileFlags& flags);
71+
72+
}
73+
```
74+
75+
-`arguments` 数组格式(clangd 推荐,避免 shell 转义问题)
76+
- 写到 `<projectRoot>/compile_commands.json`(clangd 默认向上查找)
77+
78+
### 2.3 `ninja_backend.cppm` 瘦身
79+
80+
- 删除 `compute_cxxflags()` / `compute_cflags()` 重复代码
81+
- `import mcpp.build.flags;` 复用 `CompileFlags`
82+
- `emit_ninja_string` 里直接用 `flags.cxx` / `flags.cc`
83+
84+
## 3. 调用链
85+
86+
```
87+
cli.cppm: cmd_build()
88+
├── BuildPlan plan = make_plan(...)
89+
├── CompileFlags flags = compute_flags(plan)
90+
├── write_compile_commands(plan, flags) ← 每次 build 自动
91+
└── backend->build(plan, opts) ← ninja 也用 flags
92+
```
93+
94+
## 4. JSON 格式
95+
96+
```json
97+
[
98+
{
99+
"directory": "/home/user/myproject",
100+
"file": "src/main.cpp",
101+
"arguments": [
102+
"/path/to/g++",
103+
"-std=c++23", "-fmodules", "-O2",
104+
"-I/path/to/include",
105+
"-c", "src/main.cpp",
106+
"-o", "target/.../obj/main.o"
107+
],
108+
"output": "target/.../obj/main.o"
109+
}
110+
]
111+
```
112+
113+
对标 clang JSON Compilation Database 规范:
114+
- `directory` + `file`: 必填
115+
- `arguments`: 推荐(优于 `command` 字符串)
116+
- `output`: 可选
117+
118+
## 5. 输出位置
119+
120+
`<projectRoot>/compile_commands.json` — clangd 从源文件向上找,根目录放置
121+
零配置即生效。
122+
123+
## 6. 后续扩展
124+
125+
| 扩展方向 | 实现方式 |
126+
|---|---|
127+
| 新后端(Makefile 等) | import `flags.cppm`,不重复计算 |
128+
| 增量更新 | diff 旧文件,避免无谓重写触发 clangd 重建索引 |
129+
| `mcpp compile-commands` 子命令 | 单独生成,不依赖完整 build |
130+
| per-target 过滤 | 函数参数加 target filter |
131+
| `.clangd` 配置 | `mcpp init` 可选生成 |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ doctor.log
2020
*.pcm
2121
*.ifc
2222
*.ddi
23+
compile_commands.json

mcpp.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ license = "Apache-2.0"
66
authors = ["mcpp-community"]
77
repo = "https://github.com/mcpp-community/mcpp"
88

9+
[build]
10+
# nlohmann/json.hpp lives in src/libs/json/; expose it to the global
11+
# module fragment `#include <json.hpp>` in src/libs/json.cppm.
12+
include_dirs = ["src/libs/json"]
13+
914
[toolchain]
1015
default = "gcc@16.1.0"
1116

src/build/compile_commands.cppm

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// mcpp.build.compile_commands — generate compile_commands.json for IDE integration.
2+
//
3+
// Produces a Clang JSON Compilation Database (compile_commands.json)
4+
// from the BuildPlan + CompileFlags using nlohmann::json for safe
5+
// serialisation (no manual escaping).
6+
//
7+
// Uses the `arguments` array format (preferred over `command` string
8+
// per clangd docs).
9+
//
10+
// Output location: <projectRoot>/compile_commands.json so clangd finds
11+
// it via its default upward directory walk — zero configuration needed.
12+
//
13+
// See .agents/docs/2026-05-12-compile-commands-design.md.
14+
15+
export module mcpp.build.compile_commands;
16+
17+
import std;
18+
import mcpp.build.plan;
19+
import mcpp.build.flags;
20+
import mcpp.libs.json;
21+
22+
export namespace mcpp::build {
23+
24+
// Generate compile_commands.json content as a string.
25+
std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags);
26+
27+
// Write compile_commands.json to the project root.
28+
void write_compile_commands(const BuildPlan& plan, const CompileFlags& flags);
29+
30+
} // namespace mcpp::build
31+
32+
namespace mcpp::build {
33+
34+
namespace {
35+
36+
bool is_c_source(const std::filesystem::path& src) {
37+
return src.extension() == ".c";
38+
}
39+
40+
// Split a flag string into individual tokens.
41+
std::vector<std::string> split_flags(std::string_view s) {
42+
std::vector<std::string> out;
43+
std::size_t i = 0;
44+
while (i < s.size()) {
45+
while (i < s.size() && s[i] == ' ')
46+
++i;
47+
if (i >= s.size())
48+
break;
49+
std::size_t start = i;
50+
while (i < s.size() && s[i] != ' ')
51+
++i;
52+
out.emplace_back(s.substr(start, i - start));
53+
}
54+
return out;
55+
}
56+
57+
} // namespace
58+
59+
std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
60+
nlohmann::json entries = nlohmann::json::array();
61+
62+
for (auto& cu : plan.compileUnits) {
63+
// Pick compiler + flags based on source type.
64+
const auto& compiler = is_c_source(cu.source) ? flags.ccBinary : flags.cxxBinary;
65+
const auto& flagStr = is_c_source(cu.source) ? flags.cc : flags.cxx;
66+
67+
auto output_path = (plan.outputDir / cu.object).string();
68+
69+
// Build arguments array.
70+
nlohmann::json args = nlohmann::json::array();
71+
args.push_back(compiler.string());
72+
for (auto& f : split_flags(flagStr))
73+
args.push_back(std::move(f));
74+
args.push_back("-c");
75+
args.push_back(cu.source.string());
76+
args.push_back("-o");
77+
args.push_back(output_path);
78+
79+
nlohmann::json entry;
80+
entry["directory"] = plan.projectRoot.string();
81+
entry["file"] = cu.source.string();
82+
entry["arguments"] = std::move(args);
83+
entry["output"] = output_path;
84+
85+
entries.push_back(std::move(entry));
86+
}
87+
88+
return entries.dump(2) + "\n";
89+
}
90+
91+
void write_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
92+
auto content = emit_compile_commands(plan, flags);
93+
auto path = plan.projectRoot / "compile_commands.json";
94+
95+
// Only write if content changed (avoid triggering clangd re-index).
96+
if (std::filesystem::exists(path)) {
97+
std::ifstream is(path);
98+
std::stringstream ss;
99+
ss << is.rdbuf();
100+
if (ss.str() == content)
101+
return;
102+
}
103+
104+
std::ofstream os(path);
105+
os << content;
106+
}
107+
108+
} // namespace mcpp::build

0 commit comments

Comments
 (0)