Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .agents/docs/2026-05-12-compile-commands-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# compile_commands.json 设计方案

> mcpp 0.0.9 — IDE 集成(clangd / VSCode / CLion)

## 1. 动机

C++ IDE 和语言服务器(clangd)需要 `compile_commands.json` 来理解编译参数,
提供跳转、补全、诊断等功能。CMake/Meson/xmake 都有此能力,mcpp 需要补齐。

## 2. 模块文件划分

```
src/build/
plan.cppm ← BuildPlan 数据结构(不变)
flags.cppm ← 🆕 共用 flag 计算逻辑
compile_commands.cppm ← 🆕 compile_commands.json 生成
ninja_backend.cppm ← ninja build.ninja 生成(瘦身,复用 flags)
backend.cppm ← Backend 抽象接口(不变)
```

### 2.1 `src/build/flags.cppm`

职责:从 BuildPlan 计算出所有编译/链接 flag。

```cpp
export module mcpp.build.flags;
import mcpp.build.plan;

export namespace mcpp::build {

struct CompileFlags {
std::string cxx; // "-std=c++23 -fmodules -O2 -I... --sysroot=..."
std::string cc; // "-std=c11 -O2 -I... --sysroot=..."
std::string ld; // "-static -static-libstdc++ --sysroot=..."
std::filesystem::path cxxBinary; // g++ 路径
std::filesystem::path ccBinary; // gcc 路径(派生)
std::filesystem::path arBinary; // ar 路径
};

CompileFlags compute_flags(const BuildPlan& plan);

}
```

从 ninja_backend.cppm 中提取:
- include_dirs → `-I` 拼接
- sysroot → `--sysroot=`
- binutils → `-B`
- opt_flag (`-O2` / `-Og`)
- pic_flag、user_cxxflags/cflags
- C 编译器路径推导 (`derive_c_compiler`)

**一处计算,多处消费**(ninja、compile_commands、未来后端)。

### 2.2 `src/build/compile_commands.cppm`

职责:生成标准 compile_commands.json。

```cpp
export module mcpp.build.compile_commands;
import mcpp.build.plan;
import mcpp.build.flags;

export namespace mcpp::build {

std::string emit_compile_commands(const BuildPlan& plan,
const CompileFlags& flags);

void write_compile_commands(const BuildPlan& plan,
const CompileFlags& flags);

}
```

- 用 `arguments` 数组格式(clangd 推荐,避免 shell 转义问题)
- 写到 `<projectRoot>/compile_commands.json`(clangd 默认向上查找)

### 2.3 `ninja_backend.cppm` 瘦身

- 删除 `compute_cxxflags()` / `compute_cflags()` 重复代码
- `import mcpp.build.flags;` 复用 `CompileFlags`
- `emit_ninja_string` 里直接用 `flags.cxx` / `flags.cc`

## 3. 调用链

```
cli.cppm: cmd_build()
├── BuildPlan plan = make_plan(...)
├── CompileFlags flags = compute_flags(plan)
├── write_compile_commands(plan, flags) ← 每次 build 自动
└── backend->build(plan, opts) ← ninja 也用 flags
```

## 4. JSON 格式

```json
[
{
"directory": "/home/user/myproject",
"file": "src/main.cpp",
"arguments": [
"/path/to/g++",
"-std=c++23", "-fmodules", "-O2",
"-I/path/to/include",
"-c", "src/main.cpp",
"-o", "target/.../obj/main.o"
],
"output": "target/.../obj/main.o"
}
]
```

对标 clang JSON Compilation Database 规范:
- `directory` + `file`: 必填
- `arguments`: 推荐(优于 `command` 字符串)
- `output`: 可选

## 5. 输出位置

`<projectRoot>/compile_commands.json` — clangd 从源文件向上找,根目录放置
零配置即生效。

## 6. 后续扩展

| 扩展方向 | 实现方式 |
|---|---|
| 新后端(Makefile 等) | import `flags.cppm`,不重复计算 |
| 增量更新 | diff 旧文件,避免无谓重写触发 clangd 重建索引 |
| `mcpp compile-commands` 子命令 | 单独生成,不依赖完整 build |
| per-target 过滤 | 函数参数加 target filter |
| `.clangd` 配置 | `mcpp init` 可选生成 |
132 changes: 132 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# -----------------------------------------------------------------------------
# mcpp-community C++ Style Configuration
#
# This configuration follows the style specification defined in:
# https://github.com/mcpp-community/mcpp-style-ref
#
# Maintained by: mcpp-community
# -----------------------------------------------------------------------------

Language: Cpp # Apply configuration to C++

# -----------------------------------------------------------------------------
# Language Standard
# -----------------------------------------------------------------------------
Standard: Latest # Always use the latest supported C++ standard

# -----------------------------------------------------------------------------
# Indentation
# -----------------------------------------------------------------------------
IndentWidth: 4 # Use 4 spaces for indentation
TabWidth: 4 # Visual width of tab
UseTab: Never # Never use tabs

# -----------------------------------------------------------------------------
# Line Width
# -----------------------------------------------------------------------------
ColumnLimit: 100 # Maximum line width

# -----------------------------------------------------------------------------
# Spaces
# -----------------------------------------------------------------------------
SpaceBeforeParens: ControlStatements # Space before parentheses for control statements (if/for/while); function calls remain tight
SpacesInContainerLiterals: true # Enforce spaces in container literals
SpaceBeforeAssignmentOperators: true # Add space before assignment operators
SpacesInParentheses: false # No extra spaces inside parentheses
PointerAlignment: Left # Pointer/reference attaches to type
QualifierAlignment: Left # Place qualifiers like const to the left

# -----------------------------------------------------------------------------
# Braces
# -----------------------------------------------------------------------------
BreakBeforeBraces: Attach # Opening brace stays on same line
AllowShortFunctionsOnASingleLine: Empty # Allow empty functions like `void f() {}`
AllowShortIfStatementsOnASingleLine: Never # Disallow single-line if statements
AllowShortBlocksOnASingleLine: Empty # Allow empty blocks on one line
AllowShortLoopsOnASingleLine: false # Disallow one-line loops

# -----------------------------------------------------------------------------
# Constructor Initializer List
# -----------------------------------------------------------------------------
BreakConstructorInitializers: BeforeColon # Keep initializer list compact when short
ConstructorInitializerAllOnOneLineOrOnePerLine: true # Allow compact initializer lists
BreakConstructorInitializersBeforeComma: false # Do not break before commas

# -----------------------------------------------------------------------------
# Access Modifiers
# -----------------------------------------------------------------------------
AccessModifierOffset: -4 # Align access modifiers with class indentation
EmptyLineBeforeAccessModifier: Never # Do not insert empty line before access specifiers
EmptyLineAfterAccessModifier: Never # Do not insert empty line after access specifiers

# -----------------------------------------------------------------------------
# Namespace Formatting
# -----------------------------------------------------------------------------
NamespaceIndentation: None # Do not indent contents inside namespace
FixNamespaceComments: true # Enforce closing namespace comments
CompactNamespaces: true # Prefer namespace a::b instead of nested namespaces

# -----------------------------------------------------------------------------
# Switch / Case Formatting
# -----------------------------------------------------------------------------
IndentCaseLabels: true # Indent case/default labels
IndentCaseBlocks: true # Indent statements inside case blocks

# -----------------------------------------------------------------------------
# Includes
# -----------------------------------------------------------------------------
SortIncludes: true # Automatically sort includes
IncludeBlocks: Regroup # Regroup include blocks
IncludeCategories:
- Regex: "^<.*>" # Standard / third-party headers
Priority: 1
- Regex: '^".*"' # Project headers
Priority: 2

# -----------------------------------------------------------------------------
# Comment Alignment
# -----------------------------------------------------------------------------
AlignTrailingComments: true # Align trailing comments
SpacesBeforeTrailingComments: 2 # Two spaces before trailing comments

# -----------------------------------------------------------------------------
# Parameter Formatting
# -----------------------------------------------------------------------------
BinPackParameters: true # Allow parameters on same line
BinPackArguments: true # Allow arguments on same line

# -----------------------------------------------------------------------------
# Template Formatting
# -----------------------------------------------------------------------------
AlwaysBreakTemplateDeclarations: Yes # Always place template declarations on their own line

# -----------------------------------------------------------------------------
# Operator Line Breaking
# -----------------------------------------------------------------------------
BreakBeforeBinaryOperators: None # Break after operators

# -----------------------------------------------------------------------------
# Method Chain Formatting
# -----------------------------------------------------------------------------
PenaltyBreakBeforeFirstCallParameter: 10000 # Avoid breaking chained calls unless necessary

# -----------------------------------------------------------------------------
# Lambda Formatting
# -----------------------------------------------------------------------------
AllowShortLambdasOnASingleLine: All # Allow short lambdas on one line

# -----------------------------------------------------------------------------
# Using Declarations
# -----------------------------------------------------------------------------
SortUsingDeclarations: true # Automatically sort using declarations

# -----------------------------------------------------------------------------
# Return Type Formatting
# -----------------------------------------------------------------------------
AlwaysBreakAfterReturnType: None # Break return type only when exceeding ColumnLimit

# -----------------------------------------------------------------------------
# Concepts / Requires
# -----------------------------------------------------------------------------
RequiresClausePosition: OwnLine # Force requires clause to a new line
IndentRequiresClause: false # Do not indent requires clause
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ target/

# mcpp's per-workspace xlings sandbox + lockfile + diagnostic logs
/.xlings/
.sisyphus
.claude
mcpp.lock
doctor.log
Expand All @@ -19,3 +20,4 @@ doctor.log
*.pcm
*.ifc
*.ddi
compile_commands.json
5 changes: 5 additions & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ license = "Apache-2.0"
authors = ["mcpp-community"]
repo = "https://github.com/mcpp-community/mcpp"

[build]
# nlohmann/json.hpp lives in src/libs/json/; expose it to the global
# module fragment `#include <json.hpp>` in src/libs/json.cppm.
include_dirs = ["src/libs/json"]

[toolchain]
default = "gcc@16.1.0"

Expand Down
108 changes: 108 additions & 0 deletions src/build/compile_commands.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// mcpp.build.compile_commands — generate compile_commands.json for IDE integration.
//
// Produces a Clang JSON Compilation Database (compile_commands.json)
// from the BuildPlan + CompileFlags using nlohmann::json for safe
// serialisation (no manual escaping).
//
// Uses the `arguments` array format (preferred over `command` string
// per clangd docs).
//
// Output location: <projectRoot>/compile_commands.json so clangd finds
// it via its default upward directory walk — zero configuration needed.
//
// See .agents/docs/2026-05-12-compile-commands-design.md.

export module mcpp.build.compile_commands;

import std;
import mcpp.build.plan;
import mcpp.build.flags;
import mcpp.libs.json;

export namespace mcpp::build {

// Generate compile_commands.json content as a string.
std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags);

// Write compile_commands.json to the project root.
void write_compile_commands(const BuildPlan& plan, const CompileFlags& flags);

} // namespace mcpp::build

namespace mcpp::build {

namespace {

bool is_c_source(const std::filesystem::path& src) {
return src.extension() == ".c";
}

// Split a flag string into individual tokens.
std::vector<std::string> split_flags(std::string_view s) {
std::vector<std::string> out;
std::size_t i = 0;
while (i < s.size()) {
while (i < s.size() && s[i] == ' ')
++i;
if (i >= s.size())
break;
std::size_t start = i;
while (i < s.size() && s[i] != ' ')
++i;
out.emplace_back(s.substr(start, i - start));
}
return out;
}

} // namespace

std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
nlohmann::json entries = nlohmann::json::array();

for (auto& cu : plan.compileUnits) {
// Pick compiler + flags based on source type.
const auto& compiler = is_c_source(cu.source) ? flags.ccBinary : flags.cxxBinary;
const auto& flagStr = is_c_source(cu.source) ? flags.cc : flags.cxx;

auto output_path = (plan.outputDir / cu.object).string();

// Build arguments array.
nlohmann::json args = nlohmann::json::array();
args.push_back(compiler.string());
for (auto& f : split_flags(flagStr))
args.push_back(std::move(f));
args.push_back("-c");
args.push_back(cu.source.string());
args.push_back("-o");
args.push_back(output_path);

nlohmann::json entry;
entry["directory"] = plan.projectRoot.string();
entry["file"] = cu.source.string();
entry["arguments"] = std::move(args);
entry["output"] = output_path;

entries.push_back(std::move(entry));
}

return entries.dump(2) + "\n";
}

void write_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
auto content = emit_compile_commands(plan, flags);
auto path = plan.projectRoot / "compile_commands.json";

// Only write if content changed (avoid triggering clangd re-index).
if (std::filesystem::exists(path)) {
std::ifstream is(path);
std::stringstream ss;
ss << is.rdbuf();
if (ss.str() == content)
return;
}

std::ofstream os(path);
os << content;
}

} // namespace mcpp::build
Loading
Loading