Skip to content

Commit 2b2a614

Browse files
committed
feat: enable dyndep for Clang via clang-scan-deps
Clang lacks GCC's -fdeps-format=p1689r5 compiler flag but ships clang-scan-deps which produces identical P1689 JSON output per-file. Changes: - plan.cppm: add scanDepsPath field to BuildPlan - clang.cppm: add find_scan_deps() helper - cli.cppm: discover clang-scan-deps in compiler's bin/ directory - ninja_backend.cppm: enable dyndep when scanDepsPath is available, emit Clang-specific cxx_scan rule using clang-scan-deps with -format=p1689 and stdout redirection to .ddi file This gives Clang the same per-file incremental rebuild capability that GCC has had via the dyndep pipeline.
1 parent 181cd53 commit 2b2a614

4 files changed

Lines changed: 42 additions & 10 deletions

File tree

src/build/ninja_backend.cppm

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,12 @@ bool is_c_source(const std::filesystem::path& src) {
126126
} // namespace
127127

128128
std::string emit_ninja_string(const BuildPlan& plan) {
129-
// Clang uses clang-scan-deps (not -fdeps-format=p1689r5) for P1689
130-
// scanning, so dyndep is GCC-only for now. Clang falls through to the
131-
// static-deps path which uses BmiTraits for correct pcm.cache/ paths.
132-
bool dyndep = dyndep_mode_enabled()
133-
&& mcpp::toolchain::is_gcc(plan.toolchain);
129+
// dyndep requires P1689 scanning capability:
130+
// GCC: built-in -fdeps-format=p1689r5
131+
// Clang: external clang-scan-deps tool (same P1689 output format)
132+
bool has_scanner = mcpp::toolchain::is_gcc(plan.toolchain)
133+
|| !plan.scanDepsPath.empty();
134+
bool dyndep = dyndep_mode_enabled() && has_scanner;
134135
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
135136
std::string out;
136137
auto append = [&](std::string s) { out += std::move(s); };
@@ -166,6 +167,9 @@ std::string emit_ninja_string(const BuildPlan& plan) {
166167
}
167168
if (dyndep) {
168169
append(std::format("mcpp = {}\n", escape_ninja_path(mcpp_exe_path())));
170+
if (!plan.scanDepsPath.empty()) {
171+
append(std::format("scan_deps = {}\n", escape_ninja_path(plan.scanDepsPath)));
172+
}
169173
}
170174
append("\n");
171175

@@ -239,12 +243,22 @@ std::string emit_ninja_string(const BuildPlan& plan) {
239243

240244
if (dyndep) {
241245
// Scan rule: produce P1689 .ddi for one TU.
242-
// -E -M -MM -MF gives us the dep file; -fdeps-* gives us the .ddi.
243-
std::string scan_modules_flag = traits.scanNeedsFModules ? "-fmodules " : "";
246+
// GCC: built-in -fdeps-format=p1689r5 flags during preprocessing.
247+
// Clang: external clang-scan-deps tool with -format=p1689.
244248
append("rule cxx_scan\n");
245-
append(std::format(" command = $toolenv $cxx $cxxflags {}-fdeps-format=p1689r5 "
246-
"-fdeps-file=$out -fdeps-target=$compile_target "
247-
"-M -MM -MF $out.dep -E $in -o $compile_target\n", scan_modules_flag));
249+
if (plan.scanDepsPath.empty()) {
250+
// GCC path: compiler-integrated P1689 scanning.
251+
append(" command = $toolenv $cxx $cxxflags -fmodules "
252+
"-fdeps-format=p1689r5 "
253+
"-fdeps-file=$out -fdeps-target=$compile_target "
254+
"-M -MM -MF $out.dep -E $in -o $compile_target\n");
255+
} else {
256+
// Clang path: clang-scan-deps produces P1689 JSON to stdout,
257+
// then we redirect to $out. The -- separator passes the full
258+
// compile command so clang-scan-deps knows the flags/sysroot.
259+
append(" command = $toolenv $scan_deps -format=p1689 -- "
260+
"$cxx $cxxflags -c $in -o $compile_target > $out\n");
261+
}
248262
append(" description = SCAN $out\n\n");
249263

250264
// Aggregate .ddi files into a Ninja dyndep file.

src/build/plan.cppm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ struct BuildPlan {
3737
std::filesystem::path outputDir; // target/<triple>/<fp>/
3838
std::filesystem::path stdBmiPath; // absolute path to prebuilt std.gcm
3939
std::filesystem::path stdObjectPath; // absolute path to prebuilt std.o
40+
std::filesystem::path scanDepsPath; // clang-scan-deps binary (Clang only)
4041

4142
std::vector<CompileUnit> compileUnits; // topologically sorted
4243
std::vector<LinkUnit> linkUnits;

src/cli.cppm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,14 @@ prepare_build(bool print_fingerprint,
19911991
ctx.plan = mcpp::build::make_plan(*m, *tc, fp, scan.graph, report.topoOrder,
19921992
*root, ctx.outputDir, stdBmiPath, stdObjectPath);
19931993

1994+
// Clang: discover clang-scan-deps for P1689 dyndep scanning.
1995+
if (mcpp::toolchain::is_clang(*tc)) {
1996+
auto sd = tc->binaryPath.parent_path() / "clang-scan-deps";
1997+
if (std::filesystem::exists(sd)) {
1998+
ctx.plan.scanDepsPath = sd;
1999+
}
2000+
}
2001+
19942002
// ─── M3.2: BMI cache stage / populate-task collection ─────────────
19952003
// For each version-based dep package (i.e. fetched from a registry,
19962004
// not a path dep), check the global BMI cache. If cached → stage into

src/toolchain/clang.cppm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ std::vector<std::string> std_module_build_commands(const Toolchain& tc,
2828

2929
std::filesystem::path archive_tool(const Toolchain& tc);
3030

31+
// Locate clang-scan-deps in the same bin/ directory as clang++.
32+
std::optional<std::filesystem::path> find_scan_deps(const Toolchain& tc);
33+
3134
} // namespace mcpp::toolchain::clang
3235

3336
namespace mcpp::toolchain::clang {
@@ -164,4 +167,10 @@ std::filesystem::path archive_tool(const Toolchain& tc) {
164167
return {};
165168
}
166169

170+
std::optional<std::filesystem::path> find_scan_deps(const Toolchain& tc) {
171+
auto p = tc.binaryPath.parent_path() / "clang-scan-deps";
172+
if (std::filesystem::exists(p)) return p;
173+
return std::nullopt;
174+
}
175+
167176
} // namespace mcpp::toolchain::clang

0 commit comments

Comments
 (0)