Skip to content

Commit 7b5b7a8

Browse files
committed
fix: launch compilers outside ninja environment
1 parent cb557f6 commit 7b5b7a8

2 files changed

Lines changed: 58 additions & 13 deletions

File tree

src/build/ninja_backend.cppm

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,38 @@ void write_file(const std::filesystem::path& p, std::string_view content) {
281281
os << content;
282282
}
283283

284+
const std::vector<std::filesystem::path>& compiler_invocation_dirs(const BuildPlan& plan) {
285+
return plan.toolchain.compilerInvocationRuntimeDirs.empty()
286+
? plan.toolchain.compilerRuntimeDirs
287+
: plan.toolchain.compilerInvocationRuntimeDirs;
288+
}
289+
290+
bool needs_compiler_launcher(const BuildPlan& plan) {
291+
return mcpp::platform::is_linux && !compiler_invocation_dirs(plan).empty();
292+
}
293+
294+
std::filesystem::path compiler_launcher_path(const BuildPlan& plan, bool cxx) {
295+
return plan.outputDir / (cxx ? "mcpp-cxx" : "mcpp-cc");
296+
}
297+
298+
void write_compiler_launcher(const std::filesystem::path& path,
299+
const std::filesystem::path& compiler,
300+
const std::vector<std::filesystem::path>& dirs) {
301+
std::string libraryPath;
302+
for (auto const& dir : dirs) {
303+
if (!libraryPath.empty()) libraryPath += ':';
304+
libraryPath += dir.string();
305+
}
306+
write_file(path, std::format(
307+
"#!/bin/sh\nLD_LIBRARY_PATH={}\nexport LD_LIBRARY_PATH\nexec {} \"$@\"\n",
308+
mcpp::platform::shell::quote(libraryPath),
309+
mcpp::platform::shell::quote(compiler.string())));
310+
311+
std::error_code ec;
312+
std::filesystem::permissions(path, std::filesystem::perms::owner_exec,
313+
std::filesystem::perm_options::add, ec);
314+
}
315+
284316
bool run(const std::string& cmd, std::string& output_capture, bool capture_output = true) {
285317
output_capture.clear();
286318
if (capture_output) {
@@ -384,6 +416,10 @@ std::vector<std::string> command_prefixes(const CompileFlags& flags,
384416
};
385417
add(flags.cxxBinary);
386418
add(flags.ccBinary);
419+
if (needs_compiler_launcher(plan)) {
420+
add(compiler_launcher_path(plan, /*cxx=*/true));
421+
add(compiler_launcher_path(plan, /*cxx=*/false));
422+
}
387423
add(flags.arBinary);
388424
add(plan.scanDepsPath);
389425
// mcpp itself drives the dyndep and stage_file rules; its echoed command
@@ -589,20 +625,18 @@ std::string emit_ninja_string(const BuildPlan& plan) {
589625
// The macOS initializer-ordering shim (#336) is a C translation unit, so
590626
// it needs the C driver bindings even in a project with no .c sources.
591627
const bool need_ios_init_shim = flags.needsStreamInitShim;
592-
auto compiler_command = [&](const std::filesystem::path& binary) {
593-
const auto& dirs = plan.toolchain.compilerInvocationRuntimeDirs.empty()
594-
? plan.toolchain.compilerRuntimeDirs
595-
: plan.toolchain.compilerInvocationRuntimeDirs;
596-
return mcpp::platform::linux_::build_clean_ld_library_path_prefix(dirs)
597-
+ escape_ninja_path(binary);
628+
auto compiler_command = [&](const std::filesystem::path& binary, bool cxx) {
629+
if (needs_compiler_launcher(plan))
630+
return escape_ninja_path(compiler_launcher_path(plan, cxx));
631+
return escape_ninja_path(binary);
598632
};
599-
append(std::format("cxx = {}\n", compiler_command(flags.cxxBinary)));
633+
append(std::format("cxx = {}\n", compiler_command(flags.cxxBinary, /*cxx=*/true)));
600634
// clang-scan-deps receives the driver after `--` as argv, not as a shell
601-
// command. Keep a raw path for that interface; `$cxx` may start with env.
635+
// command. Keep a raw path for that interface; `$cxx` may be a launcher.
602636
append(std::format("cxx_driver = {}\n", escape_ninja_path(flags.cxxBinary)));
603637
append(std::format("cxxflags = {}\n", flags.cxx));
604638
if (need_c_rule || need_asm_rule || need_ios_init_shim) { // asm_object drives the C compiler too
605-
append(std::format("cc = {}\n", compiler_command(flags.ccBinary)));
639+
append(std::format("cc = {}\n", compiler_command(flags.ccBinary, /*cxx=*/false)));
606640
}
607641
if (need_c_rule || need_ios_init_shim) {
608642
append(std::format("cflags = {}\n", flags.cc));
@@ -2281,10 +2315,22 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
22812315
plan.outputDir.string(), ec.message()),
22822316
plan.outputDir});
22832317

2318+
auto flags = compute_flags(plan);
2319+
stage("compute-flags");
2320+
22842321
auto ninja_path = plan.outputDir / "build.ninja";
22852322
auto manifest = emit_ninja_string(plan);
22862323
stage("emit-ninja");
22872324

2325+
if (needs_compiler_launcher(plan)) {
2326+
const auto& dirs = compiler_invocation_dirs(plan);
2327+
write_compiler_launcher(compiler_launcher_path(plan, /*cxx=*/true),
2328+
flags.cxxBinary, dirs);
2329+
write_compiler_launcher(compiler_launcher_path(plan, /*cxx=*/false),
2330+
flags.ccBinary, dirs);
2331+
}
2332+
stage("write-compiler-launcher");
2333+
22882334
// Command-length backstop (see
22892335
// .agents/docs/2026-08-06-command-length-architecture.md). The structural
22902336
// defence is that every unbounded payload goes through a response file;
@@ -2299,8 +2345,6 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
22992345
stage("write-ninja");
23002346

23012347
// compile_commands.json — via the dedicated module.
2302-
auto flags = compute_flags(plan);
2303-
stage("compute-flags");
23042348
auto cdb = write_compile_commands(plan, flags);
23052349
stage("compile-commands");
23062350
if (!cdb) {

tests/unit/test_ninja_backend.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,8 +814,9 @@ TEST(NinjaBackend, ClangScanUsesRawDriverWhenCompilerNeedsRuntimeLauncher) {
814814
auto ninja = emit_ninja_string(plan);
815815

816816
if constexpr (mcpp::platform::is_linux) {
817-
EXPECT_NE(ninja.find("cxx = env LD_LIBRARY_PATH=/opt/xim-x-llvm/lib "
818-
"/opt/xim-x-llvm/bin/clang++"), std::string::npos) << ninja;
817+
EXPECT_NE(ninja.find("cxx = /tmp/mcpp-ninja-test/target/test/mcpp-cxx"),
818+
std::string::npos) << ninja;
819+
EXPECT_EQ(ninja.find("LD_LIBRARY_PATH"), std::string::npos) << ninja;
819820
} else {
820821
EXPECT_NE(ninja.find("cxx = /opt/xim-x-llvm/bin/clang++"), std::string::npos)
821822
<< ninja;

0 commit comments

Comments
 (0)