@@ -19,6 +19,7 @@ import mcpp.platform.process;
1919import mcpp.toolchain.cppfly; // std_flag (dialect- and c++fly-aware -std= spelling)
2020import mcpp.toolchain.dialect; // CommandDialect — gnu vs cl.exe spellings
2121import mcpp.toolchain.fingerprint; // hash_file / hash_string (FNV-1a, 16 hex)
22+ import mcpp.build.hostprogram; // bundled `mcpp` module compile (own module: see its header)
2223import mcpp.toolchain.hostflags; // the shared host-compile flag producer
2324import mcpp.toolchain.linkmodel; // shared C-library / clang-cfg-bypass model
2425import mcpp.toolchain.model; // Toolchain, PayloadPaths, is_clang/is_musl_target/is_mingw_target
@@ -215,188 +216,6 @@ std::vector<std::string> host_base_flags(const mcpp::toolchain::Toolchain& tc,
215216 return f;
216217}
217218
218- // The bundled `mcpp` build module — a typed API over the stdout wire protocol
219- // so build.mcpp can `import mcpp;` instead of `#include`. Its own I/O uses
220- // C-level primitives in the global module fragment, so the module itself
221- // needs no std BMI and stays buildable before one exists. (That was once also
222- // a limit on build.mcpp; it no longer is — a build.mcpp may `import std;` and
223- // the engine stages the same std module the main build uses.)
224- // The functions mirror the directive set 1:1; they just print the
225- // `mcpp:` lines the engine already parses. Embedded in the binary (not shipped as
226- // a file) so it always matches this mcpp's protocol.
227- // NOTE: the module declaration line uses a `@MODULE@` placeholder (substituted
228- // with `export module` when written) so mcpp's own line-based module scanner does
229- // not mistake this embedded string for build_program.cppm exporting a 2nd module.
230- constexpr std::string_view kMcppModuleSource = R"CPP( module;
231- #include <cstdio>
232- #include <cstdlib>
233- @MODULE@ mcpp;
234- export namespace mcpp {
235- inline void cxxflag(const char* flag) { std::printf("mcpp:cxxflag=%s\n", flag); }
236- inline void cflag(const char* flag) { std::printf("mcpp:cflag=%s\n", flag); }
237- inline void link_lib(const char* name) { std::printf("mcpp:link-lib=%s\n", name); }
238- inline void link_search(const char* dir) { std::printf("mcpp:link-search=%s\n", dir); }
239- inline void define(const char* name) { std::printf("mcpp:cfg=%s\n", name); }
240- inline void generated(const char* path) { std::printf("mcpp:generated=%s\n", path); }
241- inline void source(const char* path) { std::printf("mcpp:source=%s\n", path); }
242- inline void include_dir(const char* dir) { std::printf("mcpp:include-dir=%s\n", dir); }
243- inline void include_dir_after(const char* dir) { std::printf("mcpp:include-dir-after=%s\n", dir); }
244- inline void rerun_if_changed(const char* path) { std::printf("mcpp:rerun-if-changed=%s\n", path); }
245- inline void rerun_if_env_changed(const char* var) { std::printf("mcpp:rerun-if-env-changed=%s\n", var); }
246- // ── environment contract (read side; values injected by the engine) ─────
247- inline const char* env_or(const char* n) { const char* v = std::getenv(n); return v ? v : ""; }
248- inline const char* target() { return env_or("MCPP_TARGET"); }
249- inline const char* target_os() { return env_or("MCPP_TARGET_OS"); }
250- inline const char* target_arch() { return env_or("MCPP_TARGET_ARCH"); }
251- inline const char* target_env() { return env_or("MCPP_TARGET_ENV"); }
252- inline const char* host() { return env_or("MCPP_HOST"); }
253- inline const char* profile() { return env_or("MCPP_PROFILE"); }
254- inline const char* out_dir() { return env_or("MCPP_OUT_DIR"); }
255- inline const char* manifest_dir() { return env_or("MCPP_MANIFEST_DIR"); }
256- inline bool has_feature(const char* name) {
257- char buf[256] = "MCPP_FEATURE_";
258- unsigned long o = 13;
259- for (const char* p = name; *p && o + 1 < sizeof buf; ++p, ++o) {
260- char c = *p;
261- buf[o] = (c >= 'a' && c <= 'z') ? char(c - 'a' + 'A')
262- : ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) ? c : '_';
263- }
264- buf[o] = 0;
265- return std::getenv(buf) != nullptr;
266- }
267- // mcpp#241: resolved install dir of a declared dependency (by its package
268- // name), or "" if not found. Same sanitize as has_feature; wraps
269- // MCPP_DEP_<SANITIZED_NAME>_DIR.
270- inline const char* dep_dir(const char* name) {
271- char buf[256] = "MCPP_DEP_";
272- unsigned long o = 9;
273- for (const char* p = name; *p && o + 5 < sizeof buf; ++p, ++o) {
274- char c = *p;
275- buf[o] = (c >= 'a' && c <= 'z') ? char(c - 'a' + 'A')
276- : ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) ? c : '_';
277- }
278- buf[o++] = '_'; buf[o++] = 'D'; buf[o++] = 'I'; buf[o++] = 'R'; buf[o] = 0;
279- return env_or(buf);
280- }
281- }
282- )CPP" ;
283-
284- // Compile the bundled `mcpp` module into `bdir` and return the extra flags the
285- // build.mcpp compile needs to import it (the object `mcpp.o` is linked alongside).
286- // GCC : -fmodules → gcm.cache/mcpp.gcm + mcpp.o; build.mcpp compiles from
287- // `bdir` (cwd) so GCC finds gcm.cache/mcpp.gcm.
288- // Clang : --precompile → mcpp.pcm, then -c → mcpp.o; pass -fmodule-file=mcpp=<pcm>.
289- // Does the source contain `import <name>;`?
290- //
291- // A plain substring search is not enough here: "import std" is a prefix of
292- // "import std.compat", so the naive test reports both for a program that
293- // only imports the latter, and mcpp would build a std BMI nobody asked for.
294- // Match the whole module name and require the terminating `;`, tolerating
295- // the whitespace the grammar allows. Occurrences inside comments or string
296- // literals still match — over-detection costs one cached BMI lookup, never
297- // a wrong build, and that is the same trade the `import mcpp` check has
298- // always made.
299- bool imports_module (std::string_view src, std::string_view name) {
300- constexpr std::string_view kImport = " import" ;
301- std::size_t pos = 0 ;
302- while ((pos = src.find (kImport , pos)) != std::string_view::npos) {
303- std::size_t i = pos + kImport .size ();
304- // `importfoo` is not an import.
305- if (i >= src.size () || (src[i] != ' ' && src[i] != ' \t ' )) { ++pos; continue ; }
306- while (i < src.size () && (src[i] == ' ' || src[i] == ' \t ' )) ++i;
307- if (src.compare (i, name.size (), name) == 0 ) {
308- std::size_t j = i + name.size ();
309- while (j < src.size () && (src[j] == ' ' || src[j] == ' \t ' )) ++j;
310- if (j < src.size () && src[j] == ' ;' ) return true ;
311- }
312- ++pos;
313- }
314- return false ;
315- }
316-
317- // What the bundled `mcpp` module contributes to the build.mcpp compile.
318- struct McppModule {
319- std::vector<std::string> useFlags; // how the consumer names the BMI
320- fs::path object; // linked alongside build.mcpp
321- };
322-
323- std::expected<McppModule, std::string>
324- build_mcpp_module (const fs::path& bdir, const fs::path& compiler,
325- const std::vector<std::string>& base, const std::string& stdFlag,
326- const mcpp::toolchain::Toolchain& tc,
327- const std::vector<std::pair<std::string, std::string>>& env) {
328- std::error_code ec;
329- fs::path cppm = bdir / " mcpp.cppm" ;
330- std::string moduleSrc (kMcppModuleSource );
331- if (auto p = moduleSrc.find (" @MODULE@" ); p != std::string::npos)
332- moduleSrc.replace (p, std::string_view (" @MODULE@" ).size (), " export module" );
333- { std::ofstream os (cppm, std::ios::trunc);
334- os << moduleSrc;
335- if (!os) return std::unexpected (std::string (" could not write mcpp module source" )); }
336-
337- auto run = [&](std::vector<std::string> argv, const char * what)
338- -> std::expected<void , std::string> {
339- auto r = mcpp::platform::process::capture_exec (argv, env, bdir.string ());
340- if (r.exit_code != 0 )
341- return std::unexpected (std::format (" mcpp module {} failed (exit {}):\n {}" ,
342- what, r.exit_code , r.output ));
343- return {};
344- };
345- auto with_base = [&](std::vector<std::string> head) {
346- for (auto & b : base) head.push_back (b);
347- return head;
348- };
349-
350- // Dispatch on the SAME module table the main build uses (BmiTraits +
351- // CommandDialect), not on a local is_clang/else. That is what makes a
352- // toolchain family work here as soon as it works there — adding cl.exe
353- // needed no new pipeline, only this row.
354- const auto traits = mcpp::toolchain::bmi_traits (tc);
355- const auto & dial = mcpp::toolchain::dialect_for (tc);
356- McppModule out;
357-
358- if (tc.compiler == mcpp::toolchain::CompilerId::MSVC ) {
359- // cl produces the .ifc and the .obj in one step.
360- fs::path ifc = bdir / (" mcpp" + std::string (traits.bmiExt ));
361- out.object = bdir / (" mcpp" + std::string (dial.objExt ));
362- std::vector<std::string> argv{compiler.string ()};
363- for (auto f : dial.alwaysFlagsArgv ) argv.emplace_back (f);
364- argv.push_back (stdFlag);
365- argv.push_back (" /interface" );
366- for (auto f : dial.forceCxxLangArgv ) argv.emplace_back (f);
367- argv.push_back (dial.compileOnly == std::string_view (" /c" ) ? " /c" : " -c" );
368- argv.push_back (" mcpp.cppm" );
369- argv.push_back (" /ifcOutput" ); argv.push_back (ifc.string ());
370- argv.push_back (std::string (dial.outputObjPrefix ) + out.object .string ());
371- if (auto r = run (with_base (std::move (argv)), " compile" ); !r)
372- return std::unexpected (r.error ());
373- out.useFlags = mcpp::toolchain::bmi_reference_tokens (" /reference mcpp=" , ifc);
374- return out;
375- }
376-
377- out.object = bdir / (" mcpp" + std::string (dial.objExt ));
378- if (mcpp::toolchain::is_clang (tc)) {
379- fs::path pcm = bdir / (" mcpp" + std::string (traits.bmiExt ));
380- if (auto r = run (with_base ({compiler.string (), stdFlag, " --precompile" ,
381- " mcpp.cppm" , " -o" , pcm.string ()}), " precompile" ); !r)
382- return std::unexpected (r.error ());
383- if (auto r = run (with_base ({compiler.string (), stdFlag, " -c" ,
384- pcm.string (), " -o" , out.object .string ()}), " object" ); !r)
385- return std::unexpected (r.error ());
386- out.useFlags = mcpp::toolchain::bmi_reference_tokens (" -fmodule-file=mcpp=" , pcm);
387- return out;
388- }
389-
390- // GCC: BMIs are implicit under <cwd>/gcm.cache, so nothing to name.
391- if (auto r = run (with_base ({compiler.string (), stdFlag,
392- std::string (mcpp::toolchain::bmi_traits (tc).compileModulesFlag ).empty ()
393- ? " -fmodules" : " -fmodules" ,
394- " -c" , " mcpp.cppm" , " -o" , out.object .string ()}), " compile" ); !r)
395- return std::unexpected (r.error ());
396- out.useFlags = {" -fmodules" };
397- return out;
398- }
399-
400219// ── Cache (line-based; one record per line, internal format) ───────────────
401220// program <hash>
402221// compiler <hash>
0 commit comments