Skip to content

Commit f9d4f95

Browse files
committed
feat(build): generate compile_commands.json during build
Automatically emit a Clang-compatible compile_commands.json to the target/ directory on every mcpp build. The JSON includes all compile units (project + dependencies) with exact compiler commands, matching the flags used by the ninja backend. - Add json_escape() helper for JSON string escaping - Add emit_compile_commands_json() using shared flag helpers - Wire into NinjaBackend::build() after build.ninja generation
1 parent 13928b0 commit f9d4f95

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

src/build/ninja_backend.cppm

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ bool is_c_source(const std::filesystem::path& src) {
126126
return src.extension() == ".c";
127127
}
128128

129+
// Escape a string for JSON (handles " and \).
130+
std::string json_escape(std::string_view s) {
131+
std::string out;
132+
out.reserve(s.size());
133+
for (char c : s) {
134+
if (c == '"') out += "\\\"";
135+
else if (c == '\\') out += "\\\\";
136+
else out.push_back(c);
137+
}
138+
return out;
139+
}
140+
129141
// Compute the full C++ flags string (everything between compiler binary and -c).
130142
// Returns raw strings; escape_ninja_path() is applied by the caller where needed.
131143
std::string compute_cxxflags(const BuildPlan& plan) {
@@ -269,6 +281,56 @@ std::string compute_cflags(const BuildPlan& plan) {
269281
b_flag, include_flags, user_cflags);
270282
}
271283

284+
// Emit compile_commands.json content as a string.
285+
std::string emit_compile_commands_json(const BuildPlan& plan) {
286+
std::string out;
287+
out.reserve(4096);
288+
out += "[\n";
289+
290+
std::string cxxflags = compute_cxxflags(plan);
291+
std::string cflags = compute_cflags(plan);
292+
293+
for (size_t i = 0; i < plan.compileUnits.size(); ++i) {
294+
auto& cu = plan.compileUnits[i];
295+
296+
// Determine compiler and flags based on source type
297+
std::string compiler;
298+
std::string flags;
299+
if (is_c_source(cu.source)) {
300+
compiler = derive_c_compiler(plan.toolchain.binaryPath).string();
301+
flags = cflags;
302+
} else {
303+
compiler = plan.toolchain.binaryPath.string();
304+
flags = cxxflags;
305+
}
306+
307+
// Build the command: compiler + " " + flags + " -c " + source + " -o " + output
308+
auto output_path = (plan.outputDir / cu.object).string();
309+
std::string command = compiler + " " + flags + " -c " + cu.source.string() + " -o " + output_path;
310+
311+
// Escape all strings for JSON
312+
std::string dir_escaped = json_escape(plan.projectRoot.string());
313+
std::string cmd_escaped = json_escape(command);
314+
std::string file_escaped = json_escape(cu.source.string());
315+
std::string output_escaped = json_escape(output_path);
316+
317+
// Emit entry with 2-space indentation, field order: directory, command, file, output
318+
out += " {\n";
319+
out += std::format(" \"directory\": \"{}\",\n", dir_escaped);
320+
out += std::format(" \"command\": \"{}\",\n", cmd_escaped);
321+
out += std::format(" \"file\": \"{}\",\n", file_escaped);
322+
out += std::format(" \"output\": \"{}\"\n", output_escaped);
323+
out += " }";
324+
if (i + 1 < plan.compileUnits.size()) {
325+
out += ",";
326+
}
327+
out += "\n";
328+
}
329+
330+
out += "]";
331+
return out;
332+
}
333+
272334
} // namespace
273335

274336
std::string emit_ninja_string(const BuildPlan& plan) {
@@ -608,6 +670,9 @@ NinjaBackend::build(const BuildPlan& plan, const BuildOptions& opts)
608670
auto ninja_path = plan.outputDir / "build.ninja";
609671
write_file(ninja_path, emit_ninja_string(plan));
610672

673+
auto cc_path = plan.projectRoot / "target" / "compile_commands.json";
674+
write_file(cc_path, emit_compile_commands_json(plan));
675+
611676
if (opts.dryRun) {
612677
BuildResult r;
613678
r.exitCode = 0;

0 commit comments

Comments
 (0)