diff --git a/build.cmd b/build.cmd index 39965a2..c6b8d7e 100644 --- a/build.cmd +++ b/build.cmd @@ -118,7 +118,7 @@ cl /nologo ^ /std:c++latest /W4 /MDd /EHsc ^ /reference "%modules_dir%\std.ifc" ^ /reference "%modules_dir%\std.compat.ifc" ^ - /c /interface /TP "%root_dir%src\dylib.cppm" > NUL + /c /interface /TP "%root_dir%src\dylib.cppm" popd if %ERRORLEVEL% neq 0 ( @@ -132,7 +132,7 @@ cl /nologo ^ /std:c++latest /W4 /MDd /EHsc ^ /reference "%modules_dir%\std.ifc" ^ /reference "%modules_dir%\std.compat.ifc" ^ - /c /interface /TP "%root_dir%src\nlohmann.json.cppm" > NUL + /c /interface /TP "%root_dir%src\nlohmann.json.cppm" popd if %ERRORLEVEL% neq 0 ( @@ -147,7 +147,7 @@ cl /nologo ^ -I"%cppfront_include_dir%" ^ /reference "%modules_dir%\std.ifc" ^ /reference "%modules_dir%\std.compat.ifc" ^ - /c /interface /TP "%root_dir%src\cpp2b_build_info_parser.cppm" > NUL + /c /interface /TP "%root_dir%src\cpp2b_build_info_parser.cppm" popd if %ERRORLEVEL% neq 0 ( diff --git a/build.cpp2 b/build.cpp2 index d2baad7..58ed6dc 100644 --- a/build.cpp2 +++ b/build.cpp2 @@ -1,6 +1,7 @@ import cpp2b.build; cppfront :== "v0.8.1"; +compiler :== cpp2b::compiler_choice::preferred; build: (inout b: cpp2b::build) -> void = { _ = b.cpp1_module().source_path("src/dylib.cppm"); diff --git a/share/cpp2b/cpp2b.build.cppm.tpl b/share/cpp2b/cpp2b.build.cppm.tpl index d7bab7f..42a912c 100644 --- a/share/cpp2b/cpp2b.build.cppm.tpl +++ b/share/cpp2b/cpp2b.build.cppm.tpl @@ -9,6 +9,7 @@ module; export module cpp2b.build; import std; +export import cpp2b; struct cpp2b_detail_build_impl; struct cpp2b_detail_git_repo_impl; @@ -93,6 +94,14 @@ CPP2B_BUILD_DECL_FN( ); export namespace cpp2b { + +enum class compiler_choice { + preferred, + msvc, + gcc, + clang +}; + class git_repo { std::shared_ptr impl; @@ -147,7 +156,7 @@ public: std::source_location caller_srcloc = std::source_location::current() ) -> cpp1_module { CPP2B_BUILD_FN_CHECK(cpp2b_detail_cpp1_module_include_directory); - (*cpp2b_detail_cpp1_module_include_directory)(impl.get(), p, caller_srcloc); + (*cpp2b_detail_cpp1_module_include_directory)(impl.get(), p, caller_srcloc); return *this; } @@ -228,3 +237,4 @@ CPP2B_BUILD_API void cpp2b_detail_build(cpp2b_detail_build_impl* impl) { cpp2b::build b(impl); ::build(b); } + diff --git a/share/cpp2b/cpp2b.cppm.tpl b/share/cpp2b/cpp2b.cppm.tpl index 4878587..4dae717 100644 --- a/share/cpp2b/cpp2b.cppm.tpl +++ b/share/cpp2b/cpp2b.cppm.tpl @@ -2,6 +2,7 @@ module; #ifdef _MSC_VER # include +# include #else # include # include @@ -12,9 +13,7 @@ export module cpp2b; import std; import std.compat; -#ifdef _MSC_VER -extern char** _environ; -#else +#if !defined(_MSC_VER) extern "C" char** environ; #endif diff --git a/src/cpp2b_build_info_parser.cppm b/src/cpp2b_build_info_parser.cppm index f793d9c..d704ea8 100644 --- a/src/cpp2b_build_info_parser.cppm +++ b/src/cpp2b_build_info_parser.cppm @@ -63,6 +63,65 @@ source_info parse_source(const std::string& filename) { result.kind = source_kind::build; // Extract constants for(size_t j = 0; j < all_tokens.size(); ++j) { + auto name = all_tokens[j].as_string_view(); + + // Specific handling for 'compiler :== ...' + if(name == "compiler" && j + 2 < all_tokens.size() && + all_tokens[j + 1].type() == cpp2::lexeme::Colon && + all_tokens[j + 2].type() == cpp2::lexeme::EqualComparison) { + size_t k = j + 3; + auto match_path = [&](const std::vector& parts) { + size_t cur = k; + for(size_t i = 0; i < parts.size(); ++i) { + if(cur >= all_tokens.size()) { + return false; + } + if(all_tokens[cur].as_string_view() != parts[i]) { + return false; + } + cur++; + if(i < parts.size() - 1) { + if(cur >= all_tokens.size()) { + return false; + } + if(all_tokens[cur].as_string_view() == "::") { + cur++; + } else if(cur + 1 < all_tokens.size() && + all_tokens[cur].as_string_view() == ":" && + all_tokens[cur + 1].as_string_view() == ":") { + cur += 2; + } else { + return false; + } + } + } + k = cur; + return true; + }; + + // Case 1: compiler :== cpp2b::compiler_choice::; + if(match_path({"cpp2b", "compiler_choice"})) { + bool has_scope = false; + if(k < all_tokens.size() && all_tokens[k].as_string_view() == "::") { + k++; + has_scope = true; + } else if(k + 1 < all_tokens.size() && + all_tokens[k].as_string_view() == ":" && + all_tokens[k + 1].as_string_view() == ":") { + k += 2; + has_scope = true; + } + + if(has_scope && k < all_tokens.size() && + all_tokens[k].type() == cpp2::lexeme::Identifier) { + result.constants["compiler"] = + std::string(all_tokens[k].as_string_view()); + j = k; + continue; + } + } + } + // identifier == string_literal ; if(j + 3 < all_tokens.size() && all_tokens[j].type() == cpp2::lexeme::Identifier && @@ -78,26 +137,26 @@ source_info parse_source(const std::string& filename) { j += 3; } // identifier :== string_literal ; - else if (j + 3 < all_tokens.size() && - all_tokens[j].type() == cpp2::lexeme::Identifier && - all_tokens[j+1].type() == cpp2::lexeme::Colon && - all_tokens[j+2].type() == cpp2::lexeme::EqualComparison && - all_tokens[j+3].type() == cpp2::lexeme::StringLiteral) { - auto name = all_tokens[j].as_string_view(); - auto value_raw = all_tokens[j+3].as_string_view(); - if (value_raw.size() >= 2) { - auto value = std::string(value_raw.substr(1, value_raw.size() - 2)); - result.constants[std::string(name)] = value; - } - j += 3; + else if(j + 3 < all_tokens.size() && + all_tokens[j].type() == cpp2::lexeme::Identifier && + all_tokens[j + 1].type() == cpp2::lexeme::Colon && + all_tokens[j + 2].type() == cpp2::lexeme::EqualComparison && + all_tokens[j + 3].type() == cpp2::lexeme::StringLiteral) { + auto name = all_tokens[j].as_string_view(); + auto value_raw = all_tokens[j + 3].as_string_view(); + if(value_raw.size() >= 2) { + auto value = std::string(value_raw.substr(1, value_raw.size() - 2)); + result.constants[std::string(name)] = value; + } + j += 3; } // identifier : type == string_literal ; - else if (j + 5 < all_tokens.size() && - all_tokens[j].type() == cpp2::lexeme::Identifier && - all_tokens[j+1].type() == cpp2::lexeme::Colon && - all_tokens[j+3].type() == cpp2::lexeme::EqualComparison && - all_tokens[j+4].type() == cpp2::lexeme::StringLiteral && - all_tokens[j+5].type() == cpp2::lexeme::Semicolon) { + else if(j + 5 < all_tokens.size() && + all_tokens[j].type() == cpp2::lexeme::Identifier && + all_tokens[j + 1].type() == cpp2::lexeme::Colon && + all_tokens[j + 3].type() == cpp2::lexeme::EqualComparison && + all_tokens[j + 4].type() == cpp2::lexeme::StringLiteral && + all_tokens[j + 5].type() == cpp2::lexeme::Semicolon) { auto name = all_tokens[j].as_string_view(); auto value_raw = all_tokens[j + 4].as_string_view(); if(value_raw.size() >= 2) { diff --git a/src/main.cpp2 b/src/main.cpp2 index 3abdfeb..29c1178 100644 --- a/src/main.cpp2 +++ b/src/main.cpp2 @@ -96,6 +96,62 @@ expect: (move opt: std::optional, message) -> T = { return opt*; } +executable_extension: () -> std::string_view = { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + return ".exe"; + } else { + return ""; + } +} + +obj_extension: () -> std::string_view = { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + return ".obj"; + } else { + return ".o"; + } +} + +cmd_exists: (cmd: std::string) -> bool = { + check_cmd: std::string = ""; + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + check_cmd = std::format("where /q {}", cmd); + } else { + check_cmd = std::format("command -v {} > /dev/null 2>&1", cmd); + } + return std::system(check_cmd.c_str()) == 0; +} + +discover_clang_cmd: () -> std::string = { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + if cmd_exists("clang") { return "clang"; } + if cmd_exists("clang-cl") { return "clang-cl"; } + return "clang"; // fallback + } else { + if cmd_exists("clang-19") { return "clang-19"; } + return "clang"; + } +} + +discover_mingw_gxx: () -> std::string = { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + if cmd_exists("g++") { return "g++"; } + + // Common installation paths + common_paths: std::vector = ( + "C:/msys64/mingw64/bin/g++.exe", + "C:/msys64/ucrt64/bin/g++.exe", + "C:/msys64/clang64/bin/g++.exe", + "C:/MinGW/bin/g++.exe" + ); + + for common_paths do (p: fs::path) { + if fs::exists(p) { return p.string(); } + } + } + return "g++"; +} + GitRepo: type = { private repo_name: fs::path = (); private clone_url: std::string = (); @@ -160,19 +216,25 @@ Usage: module_source_extension: (compiler: cpp2b::compiler_type) -> std::string_view = { if compiler == cpp2b::compiler_type::msvc { return ".ixx"; } - if compiler == cpp2b::compiler_type::clang { return ".cppm"; } + if compiler == cpp2b::compiler_type::clang { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + return ".ixx"; + } else { + return ".cppm"; + } + } if compiler == cpp2b::compiler_type::gcc { return ".cxx"; } - std::exit(1); + return ".cppm"; } bmi_extension: (compiler: cpp2b::compiler_type) -> std::string_view = { if compiler == cpp2b::compiler_type::msvc { return ".ifc"; } if compiler == cpp2b::compiler_type::clang { return ".pcm"; } if compiler == cpp2b::compiler_type::gcc { return ".pcm"; } // TODO: Is this right? - std::exit(1); + return ".pcm"; } -generate_cpp2b_module: () = { +generate_cpp2b_module: (compiler: cpp2b::compiler_type) = { using std::string_literals::_; cpp2b_module_template_path: fs::path = share_dir() / "cpp2b.cppm.tpl"s; @@ -183,7 +245,7 @@ generate_cpp2b_module: () = { std::exit(1); } - cpp2b_module_source_path: fs::path = ".cache/cpp2/source/_build/cpp2b(module_source_extension(cpp2b::compiler()))$"; + cpp2b_module_source_path: fs::path = ".cache/cpp2/source/_build/cpp2b(module_source_extension(compiler))$"; cpp2b_module_source_stream: std::ofstream = (cpp2b_module_source_path); if !cpp2b_module_source_stream { @@ -214,14 +276,14 @@ generate_cpp2b_module: () = { impl: cpp2b_detail_cpp1_module_impl = (); impl.source_path = cpp2b_module_source_path; - build_cpp1_module(impl, "cpp2b", :std::vector=("std", "std.compat")); + build_cpp1_module(impl, "cpp2b", :std::vector=("std", "std.compat"), compiler); } -generate_cpp2b_build_module: () = { +generate_cpp2b_build_module: (compiler: cpp2b::compiler_type) = { using std::string_literals::_; cpp2b_module_template_path: fs::path = share_dir() / "cpp2b.build.cppm.tpl"s; - cpp2b_module_source_path: fs::path = ".cache/cpp2/source/_build/cpp2b.build(module_source_extension(cpp2b::compiler()))$"; + cpp2b_module_source_path: fs::path = ".cache/cpp2/source/_build/cpp2b.build(module_source_extension(compiler))$"; cpp2b_module_source_stream: std::ofstream = (cpp2b_module_source_path); cpp2b_module_template_stream: std::ifstream = (cpp2b_module_template_path); @@ -251,7 +313,7 @@ generate_cpp2b_build_module: () = { impl: cpp2b_detail_cpp1_module_impl = (); impl.source_path = cpp2b_module_source_path; - build_cpp1_module(impl, "cpp2b.build", :std::vector=("std", "std.compat")); + build_cpp1_module(impl, "cpp2b.build", :std::vector=("std", "std.compat"), compiler); } get_vs_tools_dir: () -> fs::path = { @@ -261,41 +323,61 @@ get_vs_tools_dir: () -> fs::path = { } get_llvm_root: () -> fs::path = { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + if cmd_exists("clang") { + // Typically C:\Program Files\LLVM\bin\clang.exe -> C:\Program Files\LLVM + // Let's check common paths first. + common_paths: std::vector = ( + "C:/Program Files/LLVM", + "C:/Program Files (x86)/LLVM" + ); + for common_paths do (p: fs::path) { + if fs::exists(p) { return p; } + } + } + return "C:/Program Files/LLVM"; + } return "/usr/lib/llvm-19"; } get_system_modules_dir: (compiler: cpp2b::compiler_type) -> fs::path = { if compiler == cpp2b::compiler_type::msvc { return get_vs_tools_dir() / "modules"; } - if compiler == cpp2b::compiler_type::clang { return get_llvm_root() / "share" / "libc++" / "v1"; } + if compiler == cpp2b::compiler_type::clang { + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + return get_vs_tools_dir() / "modules"; + } else { + return get_llvm_root() / "share" / "libc++" / "v1"; + } + } log_error("cannot find system cpp20 modules directory"); std::exit(1); } -ensure_system_module: (name: std::string) = { - ensure_system_module(name, :std::vector = ()); +ensure_system_module: (name: std::string, compiler: cpp2b::compiler_type) = { + ensure_system_module(name, :std::vector = (), compiler); } -ensure_system_module: (name: std::string, deps) = { +ensure_system_module: (name: std::string, deps, compiler: cpp2b::compiler_type) = { d := modules_dir(); - bmi := d / std::format("{}{}", name, bmi_extension(cpp2b::compiler())); - system_modules_dir := get_system_modules_dir(cpp2b::compiler()); + bmi := d / std::format("{}{}", name, bmi_extension(compiler)); + system_modules_dir := get_system_modules_dir(compiler); if !fs::exists(bmi) { impl: cpp2b_detail_cpp1_module_impl = (); - impl.source_path = system_modules_dir / std::format("{}{}", name, module_source_extension(cpp2b::compiler())); - build_cpp1_module(impl, name, deps); + impl.source_path = system_modules_dir / std::format("{}{}", name, module_source_extension(compiler)); + build_cpp1_module(impl, name, deps, compiler); } } -ensure_std_modules: () = { - ensure_system_module("std"); - ensure_system_module("std.compat", :std::vector=("std")); +ensure_std_modules: (compiler: cpp2b::compiler_type) = { + ensure_system_module("std", compiler); + ensure_system_module("std.compat", :std::vector=("std"), compiler); } -cl_build_cpp1_module_cmd: (impl: cpp2b_detail_cpp1_module_impl, name: std::string, module_deps, bmi_path: fs::path) -> std::string = { +cl_build_cpp1_module_cmd: (compiler_cmd: std::string, impl: cpp2b_detail_cpp1_module_impl, name: std::string, module_deps, bmi_path: fs::path) -> std::string = { d := fs::absolute(modules_dir()); - cmd_str: std::string = "cl /nologo /std:c++latest /W4 /MDd /EHsc /c /interface /TP"; + cmd_str: std::string = std::format("{} /nologo /std:c++latest /W4 /MDd /EHsc /c /interface /TP", compiler_cmd); for impl.system_include_directories do (dir: fs::path) { cmd_str += " /I\"(fs::absolute(dir).string())$\""; @@ -317,9 +399,13 @@ cl_build_cpp1_module_cmd: (impl: cpp2b_detail_cpp1_module_impl, name: std::strin unix_build_cpp1_module_cmd: (impl: cpp2b_detail_cpp1_module_impl, compiler_cmd: std::string, name: std::string, module_deps, bmi_path: fs::path) -> std::string = { d := fs::absolute(modules_dir()); - sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; - cmd_str: std::string = std::format("{} -stdlib=libc++ -std=c++23 -fexperimental-library", compiler_cmd); - cmd_str += std::format(" -isystem \"{}\"", fs::absolute(sys_inc_dir).string()); + cmd_str: std::string = std::format("{} -std=c++23", compiler_cmd); + + if constexpr cpp2b::host_platform() != cpp2b::platform::windows { + cmd_str += " -stdlib=libc++ -fexperimental-library"; + sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; + cmd_str += std::format(" -isystem \"{}\"", fs::absolute(sys_inc_dir).string()); + } for impl.system_include_directories do (dir: fs::path) { cmd_str += std::format(" -isystem \"{}\"", fs::absolute(dir).string()); @@ -330,13 +416,27 @@ unix_build_cpp1_module_cmd: (impl: cpp2b_detail_cpp1_module_impl, compiler_cmd: } cmd_str += std::format(" -fprebuilt-module-path=\"{}\"", fs::absolute(d).string()); - cmd_str += " \"(fs::absolute(impl.source_path).string())$\""; - cmd_str += std::format(" --precompile -o {}/{}.pcm", d.string(), name); + + // Clang uses .pcm for modules. If it's clang, also generate the object file. + if compiler_cmd.find("clang") != std::string::npos { + obj_path := bmi_path; + obj_path.replace_extension(obj_extension()); + cmd_str += std::format(" -x c++-module \"{}\" -c -o \"{}\" -fmodule-output=\"{}\"", + fs::absolute(impl.source_path).string(), + fs::absolute(obj_path).string(), + fs::absolute(bmi_path).string() + ); + } else { + cmd_str += std::format(" -x c++-module \"{}\" --precompile -o \"{}\"", + fs::absolute(impl.source_path).string(), + fs::absolute(bmi_path).string() + ); + } + return cmd_str; } -build_cpp1_module: (impl: cpp2b_detail_cpp1_module_impl, name: std::string, module_deps) = { - compiler :== cpp2b::compiler(); +build_cpp1_module: (impl: cpp2b_detail_cpp1_module_impl, name: std::string, module_deps, compiler: cpp2b::compiler_type) = { d := fs::absolute(modules_dir()); bmi := d / std::format("{}{}", name, bmi_extension(compiler)); log_path := fs::path(".cache") / "cpp2" / "log" / "build" / ("(name)$.log"); @@ -345,10 +445,16 @@ build_cpp1_module: (impl: cpp2b_detail_cpp1_module_impl, name: std::string, modu ensure_dir(log_path.parent_path()); cmd_str: std::string = ""; - if compiler == cpp2b::compiler_type::msvc { cmd_str = cl_build_cpp1_module_cmd(impl, name, module_deps, bmi); } - else if compiler == cpp2b::compiler_type::clang { cmd_str = unix_build_cpp1_module_cmd(impl, "clang-19", name, module_deps, bmi); } - else if compiler == cpp2b::compiler_type::gcc { cmd_str = unix_build_cpp1_module_cmd(impl, "gcc", name, module_deps, bmi); } - else { log_error("unsupported compiler"); std::exit(1); } + if compiler == cpp2b::compiler_type::msvc { + cmd_str = cl_build_cpp1_module_cmd("cl", impl, name, module_deps, bmi); + } else if compiler == cpp2b::compiler_type::clang { + cmd_str = unix_build_cpp1_module_cmd(impl, discover_clang_cmd(), name, module_deps, bmi); + } else if compiler == cpp2b::compiler_type::gcc { + cmd_str = unix_build_cpp1_module_cmd(impl, discover_mingw_gxx(), name, module_deps, bmi); + } else { + log_error("unsupported compiler"); + std::exit(1); + } cmd_str += cmd_log_output(fs::absolute(log_path)); @@ -551,14 +657,6 @@ main: (args) -> int = { return 1; } -executable_extension: () -> std::string_view = { - if constexpr cpp2b::target_platform() == cpp2b::platform::windows { - return ".exe"; - } else { - return ""; - } -} - shared_library_extension: () -> std::string_view = { if constexpr cpp2b::target_platform() == cpp2b::platform::windows { return ".dll"; @@ -596,8 +694,9 @@ transpile_cpp2: (src: fs::path, out_dir: fs::path) -> transpile_cpp2_result = { result.output_log = fs::path(".cache") / "cpp2" / "log" / "transpile" / fs::path(src).replace_extension(".log"); ensure_dir(result.output_log.parent_path()); - cppfront := fs::absolute(".cache/cpp2/tools/cppfront(executable_extension())$"); - cmd_str: std::string = "(cppfront.string())$ (src.string())$ -o (result.cpp1_output.string())$ -pure -import-std -l -format-colon-errors (cmd_log_output(result.output_log))$"; + cppfront_bin_name: std::string = "cppfront(executable_extension())$"; + cppfront_exe := fs::absolute(".cache/cpp2/tools") / cppfront_bin_name; + cmd_str: std::string = std::format("{} {} -o {} -pure -import-std -l -format-colon-errors {}", cppfront_exe.string(), src.string(), result.cpp1_output.string(), cmd_log_output(result.output_log)); ensure_dir(result.cpp1_output.parent_path()); result.cppfront_exit_code = measure( @@ -610,16 +709,21 @@ transpile_cpp2: (src: fs::path, out_dir: fs::path) -> transpile_cpp2_result = { unix_compile_cppfront: (cc: std::string_view, cppfront_source: fs::path, out_cppfront_binary: fs::path, log_path: fs::path) -> int = { cwd := fs::current_path(); - cmd_str: std::string = "(cc)$ -lstdc++ -lc -lm -std=c++20 cppfront.cpp -o (out_cppfront_binary.string())$ (cmd_log_output(fs::absolute(log_path)))$"; + cmd_str: std::string = std::format("{} -std=c++20 cppfront.cpp -o {}", cc, out_cppfront_binary.string()); + if constexpr cpp2b::host_platform() != cpp2b::platform::windows { + cmd_str += " -lstdc++ -lc -lm"; + } + cmd_str += cmd_log_output(fs::absolute(log_path)); fs::current_path(cppfront_source / "source"); exit_code := std::system(cmd_str.c_str()); fs::current_path(cwd); return exit_code; } -cl_compile_cppfront: (cppfront_source: fs::path, out_cppfront_binary: fs::path, log_path: fs::path) -> int = { +cl_compile_cppfront: (compiler_cmd: std::string, cppfront_source: fs::path, out_cppfront_binary: fs::path, log_path: fs::path) -> int = { cwd := fs::current_path(); - cmd_str: std::string = "cl.exe /nologo /std:c++latest /EHsc cppfront.cpp (cmd_log_output(fs::absolute(log_path)))$"; + abs_log_path := fs::absolute(log_path); + cmd_str: std::string = std::format("{} /nologo /std:c++latest /EHsc cppfront.cpp {}", compiler_cmd, cmd_log_output(abs_log_path)); fs::current_path(cppfront_source / "source"); exit_code := std::system(cmd_str.c_str()); if exit_code == 0 { @@ -629,23 +733,32 @@ cl_compile_cppfront: (cppfront_source: fs::path, out_cppfront_binary: fs::path, return exit_code; } -ensure_cppfront: (cppfront_source: fs::path) -> int = { - cppfront: fs::path = ".cache/cpp2/tools/cppfront(executable_extension())$"; - if !fs::exists(cppfront) { +ensure_cppfront: (cppfront_source: fs::path, compiler: cpp2b::compiler_type) -> int = { + cppfront_exe: fs::path = ".cache/cpp2/tools/cppfront(executable_extension())$"; + if !fs::exists(cppfront_exe) { log_info("compiling cppfront..."); cppfront_compile_log_path: fs::path = ".cache/cpp2/log/cppfront.log"; - if cpp2b::compiler() == cpp2b::compiler_type::clang { - return unix_compile_cppfront("clang-19", fs::absolute(cppfront_source), fs::absolute(cppfront), cppfront_compile_log_path); + if compiler == cpp2b::compiler_type::clang { + clang_cmd := discover_clang_cmd(); + if constexpr cpp2b::host_platform() == cpp2b::platform::windows { + if clang_cmd == "clang-cl" { + return cl_compile_cppfront(clang_cmd, fs::absolute(cppfront_source), fs::absolute(cppfront_exe), cppfront_compile_log_path); + } else { + return unix_compile_cppfront(clang_cmd, fs::absolute(cppfront_source), fs::absolute(cppfront_exe), cppfront_compile_log_path); + } + } else { + return unix_compile_cppfront(clang_cmd, fs::absolute(cppfront_source), fs::absolute(cppfront_exe), cppfront_compile_log_path); + } } - if cpp2b::compiler() == cpp2b::compiler_type::gcc { - return unix_compile_cppfront("gcc", fs::absolute(cppfront_source), fs::absolute(cppfront), cppfront_compile_log_path); + if compiler == cpp2b::compiler_type::gcc { + return unix_compile_cppfront(discover_mingw_gxx(), fs::absolute(cppfront_source), fs::absolute(cppfront_exe), cppfront_compile_log_path); } - if cpp2b::compiler() == cpp2b::compiler_type::msvc { - return cl_compile_cppfront(fs::absolute(cppfront_source), fs::absolute(cppfront), cppfront_compile_log_path); + if compiler == cpp2b::compiler_type::msvc { + return cl_compile_cppfront("cl", fs::absolute(cppfront_source), fs::absolute(cppfront_exe), cppfront_compile_log_path); } log_error("unknown compiler"); @@ -673,16 +786,14 @@ find_git_root: (copy dir: fs::path) -> std::optional = { } find_root_dir: (copy dir: fs::path) -> std::optional = { - root_dir: std::optional = (); - while !dir.empty() { - if fs::exists(dir / "build.cpp2") { root_dir = dir; } + if fs::exists(dir / "build.cpp2") { return dir; } parent_dir := dir.parent_path(); if parent_dir == dir { break; } dir = parent_dir; } - return root_dir; + return std::nullopt; } find_build_cpp2_dir: (copy dir: fs::path) -> std::optional = { @@ -705,14 +816,14 @@ build_binary_result: @struct type = { duration: std::chrono::milliseconds = (); } -cl_build_binary_cmd: (info: cpp2b_source_binary_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { +cl_build_binary_cmd: (compiler_cmd: std::string, info: cpp2b_source_binary_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { transpiled_src := fs::absolute(".cache/cpp2/source") / fs::path(info.src).replace_extension(".cpp"); d := fs::absolute(modules_dir()); bin_parent_dir := fs::relative(bin_outpath).parent_path().string(); std::replace(bin_parent_dir.begin(), bin_parent_dir.end(), '/', '\\'); - cmd_str: std::string = "cl /nologo /std:c++latest /W4 /MDd /EHsc /DEBUG:full /Zi /FC"; + cmd_str: std::string = std::format("{} /nologo /std:c++latest /W4 /MDd /EHsc /DEBUG:full /Zi /FC", compiler_cmd); for info.imports do (imp: std::string) { imp_bmi := d / ("(imp)$.ifc"); imp_obj := d / ("(imp)$.obj"); @@ -727,28 +838,36 @@ cl_build_binary_cmd: (info: cpp2b_source_binary_info, bin_outpath: fs::path, cpp } unix_build_binary_cmd: (compiler_cmd: std::string, info: cpp2b_source_binary_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { - sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; - sys_lib_dir := get_llvm_root() / "lib"; transpiled_src := fs::absolute(".cache/cpp2/source") / fs::path(info.src).replace_extension(".cpp"); d := fs::absolute(modules_dir()); - cmd_str: std::string = std::format("{} -stdlib=libc++ -fPIC", compiler_cmd); + cmd_str: std::string = std::format("{} -std=c++23", compiler_cmd); + + if constexpr cpp2b::host_platform() != cpp2b::platform::windows { + cmd_str += " -stdlib=libc++ -fPIC -fexperimental-library"; + sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; + sys_lib_dir := get_llvm_root() / "lib"; + cmd_str += std::format(" -L{} -isystem {}", sys_lib_dir.string(), sys_inc_dir.string()); + cmd_str += " -lc++abi -lc++ -lm -static -fuse-ld=lld"; + } + for info.imports do (imp: std::string) { - cmd_str += " \"" + (d / ("(imp)$.pcm")).string() + "\""; + cmd_str += std::format(" -fmodule-file={}=\"{}\"", imp, (d / ("(imp)$.pcm")).string()); + + // Link the object file if it exists + obj_path := d / (imp + std::string(obj_extension())); + if fs::exists(obj_path) { + cmd_str += " \"" + obj_path.string() + "\""; + } } cmd_str += " \"(transpiled_src.string())$\""; - cmd_str += " -std=c++23 -fexperimental-library"; - cmd_str += std::format(" -fprebuilt-module-path={}", d.string()); - cmd_str += std::format(" -L{}", sys_lib_dir.string()); - cmd_str += std::format(" -isystem {}", sys_inc_dir.string()); - cmd_str += " -lc++abi -lc++ -lm -static -fuse-ld=lld"; + cmd_str += std::format(" -fprebuilt-module-path=\"{}\"", d.string()); cmd_str += " -I\"(cppfront_include_dir.string())$\""; cmd_str += " -o \"(bin_outpath.string())$\""; return cmd_str; } -build_binary: (info: cpp2b_source_binary_info, cppfront_include_dir: fs::path) -> build_binary_result = { - compiler :== cpp2b::compiler(); +build_binary: (info: cpp2b_source_binary_info, cppfront_include_dir: fs::path, compiler: cpp2b::compiler_type) -> build_binary_result = { bin_basename: fs::path = info.name(); if bin_basename.extension().empty() { bin_basename.replace_extension(executable_extension()); @@ -762,10 +881,16 @@ build_binary: (info: cpp2b_source_binary_info, cppfront_include_dir: fs::path) - d := fs::absolute(modules_dir()); cmd_str: std::string = ""; - if compiler == cpp2b::compiler_type::msvc { cmd_str = cl_build_binary_cmd(info, bin_outpath, cppfront_include_dir); } - else if compiler == cpp2b::compiler_type::clang { cmd_str = unix_build_binary_cmd("clang-19", info, bin_outpath, cppfront_include_dir); } - else if compiler == cpp2b::compiler_type::gcc { cmd_str = unix_build_binary_cmd("gcc", info, bin_outpath, cppfront_include_dir); } - else { log_error("Unsupported compiler"); std::exit(1); } + if compiler == cpp2b::compiler_type::msvc { + cmd_str = cl_build_binary_cmd("cl", info, bin_outpath, cppfront_include_dir); + } else if compiler == cpp2b::compiler_type::clang { + cmd_str = unix_build_binary_cmd(discover_clang_cmd(), info, bin_outpath, cppfront_include_dir); + } else if compiler == cpp2b::compiler_type::gcc { + cmd_str = unix_build_binary_cmd(discover_mingw_gxx(), info, bin_outpath, cppfront_include_dir); + } else { + log_error("Unsupported compiler"); + std::exit(1); + } cmd_str += " (cmd_log_output(fs::absolute(log_path)))$"; @@ -924,10 +1049,10 @@ cpp2b_detail_build: (copy _impl: *cpp2b_detail_build_impl) -> void = { // empty. this is just so we can decltype the signature } -cl_build_build_script_cmd: (info: cpp2b_source_build_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { +cl_build_build_script_cmd: (compiler_cmd: std::string, info: cpp2b_source_build_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { transpiled_src := fs::absolute(".cache/cpp2/source") / fs::path(info.src).replace_extension(".cpp"); d := fs::absolute(modules_dir()); - cmd_str: std::string = "cl /nologo /std:c++latest /W4 /MDd /EHsc /LDd /DLL"; + cmd_str: std::string = std::format("{} /nologo /std:c++latest /W4 /MDd /EHsc /LDd /DLL", compiler_cmd); for info.imports do (imp: std::string) { imp_bmi := d / ("(imp)$.ifc"); imp_obj := d / ("(imp)$.obj"); @@ -944,28 +1069,36 @@ cl_build_build_script_cmd: (info: cpp2b_source_build_info, bin_outpath: fs::path unix_build_build_script_cmd: (compiler_cmd: std::string, info: cpp2b_source_build_info, bin_outpath: fs::path, cppfront_include_dir: fs::path) -> std::string = { - sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; - sys_lib_dir := get_llvm_root() / "lib"; transpiled_src := fs::absolute(".cache/cpp2/source") / fs::path(info.src).replace_extension(".cpp"); d := fs::absolute(modules_dir()); - cmd_str: std::string = std::format("{} -stdlib=libc++ -shared", compiler_cmd); + cmd_str: std::string = std::format("{} -std=c++23 -shared", compiler_cmd); + + if constexpr cpp2b::host_platform() != cpp2b::platform::windows { + cmd_str += " -stdlib=libc++ -fexperimental-library -fPIC"; + sys_inc_dir := get_llvm_root() / "include" / "c++" / "v1"; + sys_lib_dir := get_llvm_root() / "lib"; + cmd_str += std::format(" -L{} -isystem {}", sys_lib_dir.string(), sys_inc_dir.string()); + cmd_str += " -lc++abi -lc++ -lm -fuse-ld=lld"; + } + for info.imports do (imp: std::string) { - cmd_str += " \"" + (d / ("(imp)$.pcm")).string() + "\""; + cmd_str += std::format(" -fmodule-file={}=\"{}\"", imp, (d / ("(imp)$.pcm")).string()); + + // Link the object file if it exists + obj_path := d / (imp + std::string(obj_extension())); + if fs::exists(obj_path) { + cmd_str += " \"" + obj_path.string() + "\""; + } } cmd_str += " \"(transpiled_src.string())$\""; - cmd_str += " -std=c++23 -fexperimental-library -fPIC"; - cmd_str += std::format(" -fprebuilt-module-path={}", d.string()); - cmd_str += std::format(" -L{}", sys_lib_dir.string()); - cmd_str += std::format(" -isystem {}", sys_inc_dir.string()); - cmd_str += " -lc++abi -lc++ -lm -fuse-ld=lld"; + cmd_str += std::format(" -fprebuilt-module-path=\"{}\"", d.string()); cmd_str += " -I\"(cppfront_include_dir.string())$\""; cmd_str += " -o \"(bin_outpath.string())$\""; return cmd_str; } -build_build_script: (info: cpp2b_source_build_info, cppfront_include_dir: fs::path) -> build_binary_result = { - compiler :== cpp2b::compiler(); +build_build_script: (info: cpp2b_source_build_info, cppfront_include_dir: fs::path, compiler: cpp2b::compiler_type) -> build_binary_result = { bin_outpath := fs::absolute(".cache/cpp2/bin") / fs::path(info.src).replace_extension(shared_library_extension()); log_path := fs::absolute(".cache/cpp2/log/compile") / fs::path(info.src).replace_extension(".log"); ensure_dir(log_path.parent_path()); @@ -973,10 +1106,16 @@ build_build_script: (info: cpp2b_source_build_info, cppfront_include_dir: fs::pa d := fs::absolute(modules_dir()); cmd_str: std::string = ""; - if compiler == cpp2b::compiler_type::msvc { cmd_str = cl_build_build_script_cmd(info, bin_outpath, cppfront_include_dir); } - else if compiler == cpp2b::compiler_type::clang { cmd_str = unix_build_build_script_cmd("clang-19", info, bin_outpath, cppfront_include_dir); } - else if compiler == cpp2b::compiler_type::gcc { cmd_str = unix_build_build_script_cmd("gcc", info, bin_outpath, cppfront_include_dir); } - else { log_error("Unsupported compiler"); std::exit(1); } + if compiler == cpp2b::compiler_type::msvc { + cmd_str = cl_build_build_script_cmd("cl", info, bin_outpath, cppfront_include_dir); + } else if compiler == cpp2b::compiler_type::clang { + cmd_str = unix_build_build_script_cmd(discover_clang_cmd(), info, bin_outpath, cppfront_include_dir); + } else if compiler == cpp2b::compiler_type::gcc { + cmd_str = unix_build_build_script_cmd(discover_mingw_gxx(), info, bin_outpath, cppfront_include_dir); + } else { + log_error("Unsupported compiler"); + std::exit(1); + } cmd_str += " (cmd_log_output(fs::relative(log_path)))$"; @@ -1009,6 +1148,7 @@ full_build_info: @struct type = { build_scripts: std::vector = (); unknowns: std::vector = (); cppfront_include_dir: fs::path = (); + active_compiler: cpp2b::compiler_type = cpp2b::compiler(); bin_results: std::vector = (); @@ -1114,12 +1254,33 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c assert(build_info_variant.is_build()); build_info := build_info_variant.build(); + if build_info.constants.contains("cppfront_url") { cppfront_url = build_info.constants.at("cppfront_url"); } if build_info.constants.contains("cppfront") { cppfront = build_info.constants.at("cppfront"); } + if build_info.constants.contains("compiler") { + c_str := build_info.constants.at("compiler"); + if c_str == "msvc" { + stuff.active_compiler = cpp2b::compiler_type::msvc; + } else if c_str == "clang" { + stuff.active_compiler = cpp2b::compiler_type::clang; + } else if c_str == "gcc" { + stuff.active_compiler = cpp2b::compiler_type::gcc; + } else if c_str == "preferred" { + if cpp2b::host_platform() == cpp2b::platform::windows { + stuff.active_compiler = cpp2b::compiler_type::msvc; + } else { + stuff.active_compiler = cpp2b::compiler_type::clang; + } + } else { + log_error("unknown compiler '{}'", c_str); + std::exit(1); + } + log_info("using compiler {}", c_str); + } cppfront_info_path: fs::path = ".cache/cpp2/tools/cppfront.info"; cppfront_bin: fs::path = ".cache/cpp2/tools/cppfront(executable_extension())$"; @@ -1155,7 +1316,7 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c repo.add("include"); } - exit_code = ensure_cppfront(repo.path()); + exit_code = ensure_cppfront(repo.path(), stuff.active_compiler); if exit_code != 0 { return; } if needs_recompile { @@ -1169,9 +1330,9 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c } } - ensure_std_modules(); - generate_cpp2b_module(); - generate_cpp2b_build_module(); + ensure_std_modules(stuff.active_compiler); + generate_cpp2b_module(stuff.active_compiler); + generate_cpp2b_build_module(stuff.active_compiler); transpile_source_dir: fs::path = ".cache/cpp2/source"; cpp2_source_files: std::vector = (); @@ -1267,7 +1428,7 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c build_script_futures.emplace_back( std::ref(build_script), - std::async(std::launch::async, build_build_script, build_script, stuff.cppfront_include_dir) + std::async(std::launch::async, build_build_script, build_script, stuff.cppfront_include_dir, stuff.active_compiler) ); } @@ -1373,7 +1534,7 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c std::exit(1); } - build_cpp1_module(cpp1m*, result.module_name, result.imports); + build_cpp1_module(cpp1m*, result.module_name, result.imports, stuff.active_compiler); built_modules[result.module_name] = true; } @@ -1414,7 +1575,7 @@ do_build: (targets: std::vector) -> (stuff: full_build_info, exit_c bin_futures.emplace_back( std::ref(bin), - std::async(std::launch::async, build_binary, bin, stuff.cppfront_include_dir) + std::async(std::launch::async, build_binary, bin, stuff.cppfront_include_dir, stuff.active_compiler) ); } @@ -1782,6 +1943,13 @@ cpp2b_parse_source: (source_file: fs::path) -> cpp2b_source_info = { result.set_unknown(cpp2b_source_unknown_info(source_file)); if info.kind == cpp2b_build_info_parser::source_kind::build { + if std::ranges::find(info.imports, "cpp2b") == info.imports.end() { + info.imports.emplace_back("cpp2b"); + } + if std::ranges::find(info.imports, "cpp2b.build") == info.imports.end() { + info.imports.emplace_back("cpp2b.build"); + } + build_info: cpp2b_source_build_info = (); build_info.src = source_file; build_info.imports = info.imports;