Skip to content

Commit 2e79437

Browse files
committed
basic support for C++20 modules
* windows / MSVC only * changed clang `module` compilation rules * remove check for existence of commands * change `main` to use `fmt::print` directly * update README message
1 parent 475c2a9 commit 2e79437

File tree

13 files changed

+172
-102
lines changed

13 files changed

+172
-102
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# muuk
22

3+
## There is now basic module support for MSVC / Windows only
4+
35
I don't like CMake very much but I do like cargo a lot, so I created my own cargo-like build system.
46

57
![meme](meme.jpg)

include/buildparser.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ void parse_compilation_unit(
4848
const std::filesystem::path& pkg_dir,
4949
CompilationFlags compilation_flags);
5050

51+
/// Extract platform-specific flags
5152
std::vector<std::string> extract_platform_flags(
5253
const toml::table& package_table,
5354
muuk::Compiler compiler);
5455

56+
/// Extract compiler-specific flags
5557
std::vector<std::string> extract_compiler_flags(
5658
const toml::table& package_table,
5759
muuk::Compiler compiler);

include/muukterminal.hpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ namespace muuk {
3131
static inline const std::string WHITE = "\033[37m";
3232
};
3333

34+
inline constexpr fmt::color color_from_name(std::string_view name) {
35+
if (name == "red")
36+
return fmt::color::red;
37+
if (name == "green")
38+
return fmt::color::green;
39+
if (name == "yellow")
40+
return fmt::color::yellow;
41+
if (name == "blue")
42+
return fmt::color::blue;
43+
if (name == "magenta")
44+
return fmt::color::magenta;
45+
if (name == "cyan")
46+
return fmt::color::cyan;
47+
if (name == "white")
48+
return fmt::color::white;
49+
if (name == "black")
50+
return fmt::color::black;
51+
52+
return fmt::color::white; // Default fallback
53+
}
54+
55+
template <typename... Args>
56+
inline void info(std::string_view color_name, fmt::format_string<Args...> format_str, Args&&... args) {
57+
fmt::print(
58+
fg(color_from_name(color_name)),
59+
format_str,
60+
std::forward<Args>(args)...);
61+
}
62+
3463
static int current_indent_level = 0;
3564

3665
std::string get_indent();

include/util.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ namespace util {
2525
std::string to_linux_path(const std::string& path, const std::string& prefix = "");
2626
std::set<std::string> to_linux_path(const std::set<std::string>& paths, const std::string& prefix = "");
2727
std::vector<std::string> to_linux_path(const std::vector<std::string>& paths, const std::string& prefix = "");
28+
29+
std::string escape_drive_letter(const std::string& path);
2830
}
2931

3032
// ==========================

modules/math.ixx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#ifdef DEBUG
1+
#ifndef NDEBUG
22
export module math;
33

44
export int add(int a, int b) {

src/builder/buildparser.cpp

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ std::vector<std::string> extract_platform_flags(const toml::table& package_table
8585
return flags;
8686
}
8787

88-
// Extract compiler-specific flags
8988
std::vector<std::string> extract_compiler_flags(const toml::table& package_table, const muuk::Compiler compiler) {
9089
std::vector<std::string> flags;
9190
if (!package_table.contains("compiler"))
@@ -104,6 +103,7 @@ std::vector<std::string> extract_compiler_flags(const toml::table& package_table
104103
return flags;
105104
}
106105

106+
/// Parses compilation units (modules or sources) from the TOML array
107107
void parse_compilation_unit(BuildManager& build_manager, const toml::array& unit_array, const CompilationUnitType compilation_unit_type, const std::filesystem::path& pkg_dir, CompilationFlags compilation_flags) {
108108

109109
for (const auto& unit_entry : unit_array) {
@@ -113,11 +113,11 @@ void parse_compilation_unit(BuildManager& build_manager, const toml::array& unit
113113
if (!unit_entry.contains("path"))
114114
continue;
115115

116-
std::string src_path = util::file_system::to_linux_path(
117-
std::filesystem::absolute(
118-
std::filesystem::path(unit_entry.at("path").as_string()))
119-
.string(),
120-
"../../");
116+
std::string src_path
117+
= util::file_system::to_linux_path(
118+
std::filesystem::absolute(
119+
std::filesystem::path(unit_entry.at("path").as_string()))
120+
.string());
121121

122122
std::string obj_path = util::file_system::to_linux_path(
123123
(pkg_dir / std::filesystem::path(src_path).stem()).string() + OBJ_EXT,
@@ -246,20 +246,30 @@ void parse_libraries(BuildManager& build_manager, const muuk::Compiler compiler,
246246
}
247247

248248
std::vector<std::string> obj_files;
249-
if (library_table.contains("sources")) {
250-
auto sources = library_table.at("sources").as_array();
251-
for (const auto& src_entry : sources) {
252-
if (!src_entry.is_table())
249+
250+
auto parse_entries = [&build_dir, &library_name, &library_version, &obj_files](const toml::array& entries) {
251+
for (const auto& entry : entries) {
252+
if (!entry.is_table())
253253
continue;
254-
auto source_table = src_entry.as_table();
255-
if (!source_table.contains("path"))
254+
auto entry_table = entry.as_table();
255+
if (!entry_table.contains("path"))
256256
continue;
257257

258258
const auto obj_file = util::file_system::to_linux_path(
259-
(build_dir / library_name / library_version / std::filesystem::path(source_table.at("path").as_string()).stem()).string() + OBJ_EXT,
259+
(build_dir / library_name / library_version / std::filesystem::path(entry_table.at("path").as_string()).stem()).string() + OBJ_EXT,
260260
"../../");
261261
obj_files.push_back(obj_file);
262262
}
263+
};
264+
265+
// Parse modules
266+
if (library_table.contains("modules")) {
267+
parse_entries(library_table.at("modules").as_array());
268+
}
269+
270+
// Parse sources
271+
if (library_table.contains("sources")) {
272+
parse_entries(library_table.at("sources").as_array());
263273
}
264274

265275
auto aflags = muuk::parse_array_as_vec(library_table.as_table(), "aflags");
@@ -393,7 +403,8 @@ void parse_executables(BuildManager& build_manager, const muuk::Compiler compile
393403
if (lib_map.contains(lib_name) && lib_map.at(lib_name).contains(version)) {
394404
const auto& lib_table = lib_map.at(lib_name).at(version).as_table();
395405

396-
if (lib_table.contains("sources")) {
406+
// If it doesn't have these keys, then its an empty library so we should skip it
407+
if (lib_table.contains("sources") || lib_table.contains("modules")) {
397408
std::string lib_path = util::file_system::to_linux_path(
398409
(build_dir / lib_name / version / (lib_name + LIB_EXT)).string(), "../../");
399410
libs.push_back(lib_path);
@@ -417,10 +428,10 @@ void parse_executables(BuildManager& build_manager, const muuk::Compiler compile
417428
build_manager.add_link_target(exe_path, obj_files, libs, lflags);
418429

419430
muuk::logger::info("Added link target: {}", exe_path);
420-
muuk::logger::trace(" - Object Files: {}", fmt::join(obj_files, ", "));
421-
muuk::logger::trace(" - Libraries: {}", fmt::join(libs, ", "));
422-
muuk::logger::trace(" - Include Flags: {}", fmt::join(iflags, ", "));
423-
muuk::logger::trace(" - Linker Flags: {}", fmt::join(lflags, ", "));
431+
muuk::logger::trace(" - Object Files: '{}'", fmt::join(obj_files, "', '"));
432+
muuk::logger::trace(" - Libraries: '{}'", fmt::join(libs, "', '"));
433+
muuk::logger::trace(" - Include Flags: '{}'", fmt::join(iflags, "', '"));
434+
muuk::logger::trace(" - Linker Flags: '{}'", fmt::join(lflags, "', '"));
424435
}
425436
}
426437

src/builder/moduleresolver.cpp

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,8 @@ namespace muuk {
4040
command += " " + normalized;
4141
}
4242

43-
// TODO: Remove more elegantly
44-
std::string sanitized_input = target.input;
45-
sanitized_input.erase(
46-
std::remove(
47-
sanitized_input.begin(),
48-
sanitized_input.end(),
49-
'$'),
50-
sanitized_input.end());
51-
5243
// Append input file
53-
command += " " + sanitized_input;
44+
command += " " + target.input;
5445

5546
// Append output file
5647
command += " -o " + target.output;
@@ -59,7 +50,7 @@ namespace muuk {
5950
nlohmann::json entry;
6051
entry["directory"] = build_dir; // Use current directory
6152
entry["command"] = command;
62-
entry["file"] = sanitized_input;
53+
entry["file"] = target.input;
6354
entry["output"] = target.output;
6455

6556
// Add entry to the compilation database
@@ -113,7 +104,7 @@ namespace muuk {
113104
}
114105
}
115106

116-
// Resolves required modules and links dependencies
107+
/// Resolves required modules and links dependencies
117108
void resolve_required_modules(
118109
const nlohmann::json& dependencies,
119110
BuildManager& build_manager,
@@ -136,6 +127,7 @@ namespace muuk {
136127
continue;
137128

138129
std::string required_source = require["source-path"];
130+
139131
CompilationTarget* required_target = build_manager.find_compilation_target("input", required_source);
140132
if (required_target) {
141133
primary_target->dependencies.push_back(required_target);
@@ -148,7 +140,7 @@ namespace muuk {
148140
}
149141
}
150142

151-
// Orchestrates module resolution
143+
/// Orchestrates module resolution
152144
void resolve_modules(BuildManager& build_manager, const std::string& build_dir) {
153145
std::string dependency_db = build_dir + "/dependency-db.json";
154146

src/builder/ninjabackend.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ std::string NinjaBackend::generate_rule(const CompilationTarget& target) {
6565
}
6666

6767
if (is_module) {
68-
rule << "build " << module_output << ": compile_module " << target.inputs[0] << "\n";
68+
rule << "build " << module_output << ": compile_module "
69+
<< util::file_system::escape_drive_letter(target.inputs[0])
70+
<< "\n";
6971

7072
if (!target.flags.empty()) {
7173
rule << " cflags =";
@@ -77,15 +79,19 @@ std::string NinjaBackend::generate_rule(const CompilationTarget& target) {
7779
rule << "\n";
7880
}
7981

80-
rule << "build " << target.output << ": compile " << target.inputs[0];
82+
if (compiler_ == muuk::Compiler::Clang && is_module) {
83+
rule << "build " << target.output << ": compile " << util::file_system::escape_drive_letter(module_output);
84+
} else {
85+
rule << "build " << target.output << ": compile " << util::file_system::escape_drive_letter(target.inputs[0]);
86+
}
8187

8288
if (is_module)
8389
rule << " | " << module_output; // Ensure dependency on module rule
8490

8591
if (!target.dependencies.empty()) {
8692
rule << " |";
8793
for (const auto& dep : target.dependencies)
88-
rule << " ../../" << dep;
94+
rule << " ../../" << util::file_system::to_linux_path((build_dir_ / "modules" / (dep->logical_name + ".ifc")).string());
8995
}
9096

9197
rule << "\n";
@@ -212,9 +218,11 @@ void NinjaBackend::write_header(std::ostringstream& out, std::string profile) {
212218
<< " description = Compiling C++ module $in\n\n";
213219
} else if (compiler_ == muuk::Compiler::Clang) {
214220
// Clang Compiler
221+
// -x c++-module is used to specify that the input file is a module (ie: when it doesn't end with .cppm)
215222
out << "rule compile_module\n"
216-
<< " command = $cxx -x c++-module -std=c++20 -fmodules-ts -c $in -o $out -fmodule-output="
217-
<< module_dir << " $cflags $profile_cflags\n"
223+
<< " command = $cxx -x c++-module -std=c++20 --precompile "
224+
<< "-fprebuilt-module-path=" << module_dir << " "
225+
<< "$in -o $out $cflags $profile_cflags\n"
218226
<< " description = Compiling C++ module $in\n\n";
219227
} else if (compiler_ == muuk::Compiler::GCC) {
220228
// GCC Compiler

src/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <vector>
55

66
#include <argparse/argparse.hpp>
7+
#include <fmt/ostream.h>
78
#include <nlohmann/json.hpp>
89
#include <tl/expected.hpp>
910

@@ -17,7 +18,6 @@
1718
#include "commands/run.hpp"
1819
#include "logger.hpp"
1920
#include "muuk_parser.hpp"
20-
#include "muukterminal.hpp"
2121
#include "rustify.hpp"
2222
#include "version.h"
2323

@@ -117,7 +117,7 @@ int main(int argc, char* argv[]) {
117117
program.add_subparser(add_command);
118118

119119
if (argc < 2) {
120-
muuk::terminal::error("Usage: {} <command> [--muuk-path <path>] [other options]", std::string(argv[0]));
120+
fmt::print("Usage: {} <command> [--muuk-path <path>] [other options]", std::string(argv[0]));
121121
return 1;
122122
}
123123

src/test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#ifdef DEBUG
1+
#ifndef NDEBUG
22
import math;
33

44
int do_something() {

0 commit comments

Comments
 (0)