/* Crafter.Build Copyright (C) 2025 Catcrafts® Catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; #include #include #include #include #include #include #include #include module Crafter.Build; using namespace Crafter::Build; namespace fs = std::filesystem; ModulePartition::ModulePartition(const std::string& name, const fs::path& path, Module* parent, const std::string& fileContent, const fs::path& pcmDir) : name(name), path(path), parent(parent) { { std::regex pattern(R"(import :([a-zA-Z_\-0-9\.]*);)"); std::sregex_iterator currentMatch(fileContent.begin(), fileContent.end(), pattern); std::sregex_iterator lastMatch; while (currentMatch != lastMatch) { std::smatch match = *currentMatch; partitionDependencies.push_back(match[1]); ++currentMatch; } } { std::regex pattern(R"(import ([a-zA-Z_\-0-9\.]*);)"); std::sregex_iterator currentMatch(fileContent.begin(), fileContent.end(), pattern); std::sregex_iterator lastMatch; while (currentMatch != lastMatch) { std::smatch match = *currentMatch; moduleDependencies.push_back(match[1]); ++currentMatch; } } if(!fs::exists((pcmDir/path.filename()).generic_string()+".pcm")) { needsRecompiling = true; } else if(fs::last_write_time(path.generic_string()+".cppm") > fs::last_write_time((pcmDir/path.filename()).generic_string()+".pcm")) { needsRecompiling = true; } } void ModulePartition::AddDependants() { for(const std::string& dependency: partitionDependencies) { auto pos = parent->partitions.find(dependency); if (pos != parent->partitions.end()) { pos->second.partitionDependants.push_back(this); partitionDependenciesP.push_back(&pos->second); } else { throw std::runtime_error(std::format("Partition {}:{} not found", parent->name, dependency)); } } } void ModulePartition::Check() { if(!needsRecompiling) { for(ModulePartition* dependency : partitionDependenciesP) { if(dependency->needsRecompiling) { needsRecompiling = true; break; } } } } std::vector ModulePartition::Compile(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags) { std::vector errors; if(needsRecompiling) { std::string defines; for(const Define& define : config.defines) { defines+=define.ToString(); } for(ModulePartition* dependency : partitionDependenciesP) { if(dependency->needsRecompiling) { dependency->compiled->wait(false); } } std::string command = std::format("{} {} {} -std={} {}.cppm --precompile {} -fprebuilt-module-path={} -o {}.pcm {}", clangDir, defines, flags, config.standard, path.generic_string(), march, pcmDir.generic_string(), (pcmDir/path.filename()).generic_string(), target); if(config.verbose) { std::cout << command << std::endl; } errors = RunClang(command); } *compiled = true; compiled->notify_all(); return errors; } std::vector ModulePartition::CompileSource(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags, const fs::path& buildDir) { std::string defines; for(const Define& define : config.defines) { defines+=define.ToString(); } std::string command = std::format("{} {} -std={} {}.pcm -fprebuilt-module-path={} -c -O{} {} {} -o {}.o {}", clangDir, defines, config.standard, (pcmDir/path.filename()).generic_string(), pcmDir.generic_string(), config.optimizationLevel, march, flags, (buildDir/path.filename()).generic_string(), target); if(config.verbose) { std::cout << command << std::endl; } return RunClang(command); } Module::Module(const std::string& name, const fs::path& path, const Configuration& config, const fs::path& pcmDir, std::string& files, const fs::path& buildDir) : name(name), path(path) { for(const fs::path& file: config.moduleFiles) { std::ifstream t(file.generic_string()+".cppm"); std::stringstream buffer; buffer << t.rdbuf(); std::string fileContent = buffer.str(); std::regex pattern("export module ([a-zA-Z_\\-0-9\\.]*):([a-zA-Z_\\-0-9\\.]*);"); std::smatch match; if (std::regex_search(fileContent, match, pattern)) { if(match[1] == name) { partitions.insert({match[2], ModulePartition(match[2], file, this, fileContent, pcmDir)}); files += std::format("{}.o ",(buildDir/file.filename()).generic_string()); } } } if(!fs::exists((pcmDir/path.filename()).generic_string()+".pcm")) { needsRecompiling = true; } else if(fs::last_write_time(path.generic_string()+".cppm") > fs::last_write_time((pcmDir/path.filename()).generic_string()+".pcm")) { needsRecompiling = true; } for(auto& [key, val] : partitions) { val.AddDependants(); } files += std::format("{}.o ",(buildDir/path.filename()).generic_string()); } void Module::Check() { for(auto& [key, val] : partitions) { val.Check(); } for(auto& [key, val] : partitions) { if(val.needsRecompiling) { needsRecompiling = true; for(auto& [key, val] : partitions) { val.needsRecompiling = true; } break; } } } std::vector Module::Compile(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags, const fs::path& buildDir) { std::string defines; for(const Define& define : config.defines) { defines+=define.ToString(); } std::mutex errorMutex; std::vector errors; std::vector threads; for(auto& [key, val] : partitions) {; if(val.needsRecompiling) { threads.emplace_back([&val, &clangDir, &config, &pcmDir, &target, &march, &flags, &errors, &errorMutex](){ std::vector newErrors = val.Compile(clangDir, config, pcmDir, target, march, flags); if(newErrors.size() > 0) { errorMutex.lock(); errors.insert(errors.end(), newErrors.begin(), newErrors.end()); errorMutex.unlock(); } }); } } for(std::thread& thread : threads){ thread.join(); } if(errors.size() > 0) { return errors; } { std::string command = std::format("{} {} {} -std={} {}.cppm --precompile {} -fprebuilt-module-path={} -o {}.pcm {}", clangDir, defines, flags, config.standard, path.generic_string(), march, pcmDir.generic_string(), (pcmDir/path.filename()).generic_string(), target); if(config.verbose) { std::cout << command << std::endl; } errors = RunClang(command); } if(errors.size() > 0) { return errors; } std::vector threads2; for(auto& [key, val] : partitions) {; if(val.needsRecompiling) { threads2.emplace_back([&val, &clangDir, &config, &pcmDir, &target, &march, &flags, &buildDir, &errors, &errorMutex](){ std::vector newErrors = val.CompileSource(clangDir, config, pcmDir, target, march, flags, buildDir); if(newErrors.size() > 0) { errorMutex.lock(); errors.insert(errors.end(), newErrors.begin(), newErrors.end()); errorMutex.unlock(); } }); } } threads2.emplace_back([this, &clangDir, &config, &pcmDir, &target, &march, &flags, &buildDir, &defines, &errors, &errorMutex](){ std::string command = std::format("{} {} -std={} {}.pcm -fprebuilt-module-path={} -c -O{} {} {} -o {}.o {}", clangDir, defines, config.standard, (pcmDir/path.filename()).generic_string(), pcmDir.generic_string(), config.optimizationLevel, march, flags, (buildDir/path.filename()).generic_string(), target); if(config.verbose) { std::cout << command << std::endl; } std::vector newErrors = RunClang(command); if(newErrors.size() > 0) { errorMutex.lock(); errors.insert(errors.end(), newErrors.begin(), newErrors.end()); errorMutex.unlock(); } }); for(std::thread& thread : threads2){ thread.join(); } return errors; } std::tuple, std::vector> Module::GetModules(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags, std::string& files, const fs::path& buildDir) { std::vector modules; for(const fs::path& file: config.moduleFiles) { std::ifstream t(file.generic_string()+".cppm"); std::stringstream buffer; buffer << t.rdbuf(); std::string fileContent = buffer.str(); std::regex pattern("export module ([a-zA-Z_\\-0-9\\.]*);"); std::smatch match; if (std::regex_search(fileContent, match, pattern)) { modules.emplace_back(match[1], file, config, pcmDir, files, buildDir); } } std::vector errors; std::mutex errorMutex; std::for_each(std::execution::par, modules.begin(), modules.end(), [&clangDir, &config, &pcmDir, &target, &march, &flags, &buildDir, &errors, &errorMutex](Module& modulee) { modulee.Check(); std::vector newErrors = modulee.Compile(clangDir, config, pcmDir, target, march, flags, buildDir); if(newErrors.size() > 0) { errorMutex.lock(); errors.insert(errors.end(), newErrors.begin(), newErrors.end()); errorMutex.unlock(); } }); return {modules, errors}; }