/* 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 version 3.0 as published by the Free Software Foundation; 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 "../lib/json.hpp" #include module Crafter.Build:Project_impl; import :Project; import std; import :Configuration; import :Command; namespace fs = std::filesystem; namespace Crafter { Project::Project(std::string&& name, fs::path&& path, std::vector&& configurations) : name(std::move(name)), path(std::move(path)), configurations(std::move(configurations)) {} Configuration& Project::Build(std::string_view configuration) { for(Configuration& config : configurations) { if(config.name == configuration){ Build(config); return config; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } Configuration& Project::Build(std::string_view configuration, const fs::path& outputDir) { for(Configuration& config : configurations) { if(config.name == configuration){ Build(config, outputDir); return config; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } Configuration& Project::Build(std::string_view configuration, const fs::path& outputDir, const fs::path& binDir) { for(Configuration& config : configurations) { if(config.name == configuration){ Build(config, outputDir, binDir); return config; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } Configuration& Project::Build(std::string_view configuration, const fs::path& outputDir, const fs::path& binDir, const fs::path& buildDir) { for(Configuration& config : configurations) { if(config.name == configuration){ Build(config, outputDir, binDir, buildDir); return config; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } void Project::Build(Configuration& config) const { if(config.outputDir.empty()) { Build(config, "bin"); } else { Build(config, config.outputDir); } } void Project::Build(Configuration& config, const fs::path& outputDir) const { Build(config, outputDir, outputDir); } void Project::Build(Configuration& config, const fs::path& outputDir, const fs::path& binDir) const { fs::path buildDir; if(config.buildDir.empty()) { buildDir = "build"/fs::path(config.name); } else { buildDir = config.buildDir/config.name; } Build(config, outputDir, binDir, buildDir); } void Project::Build(Configuration& config, const fs::path& outputDir, const fs::path& binDir, const fs::path& buildDir) const { if (!fs::exists(outputDir)) { fs::create_directories(outputDir); } if (!fs::exists(buildDir)) { fs::create_directories(buildDir); } std::vector threads; for(const Shader& shader : config.shaders) { if(shader.Check(outputDir)) { threads.emplace_back(&Shader::Compile, &shader, binDir); } } std::thread fileThread([&config, &binDir](){ for(const fs::path& additionalFile : config.additionalFiles){ if(!fs::exists(binDir/additionalFile.filename())) { fs::copy(additionalFile, binDir); } else if (fs::last_write_time(additionalFile) > fs::last_write_time(binDir/additionalFile.filename())){ fs::remove(binDir/additionalFile.filename()); fs::copy(additionalFile, binDir); } } }); std::string command = "clang++"; if(!config.target.empty()) { command += std::format(" -target={}", config.target); } if(!config.march.empty()) { command += std::format(" -march={}", config.march); } if(!config.standard.empty()) { command += std::format(" -std={}", config.standard); } else { command += std::format(" -std=c++26"); } for(const Define& define : config.defines) { command += std::format(" -D {}={}", define.name, define.value); } fs::path pcmDir; if(config.type == CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY) { command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY"; pcmDir = outputDir; } else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY) { command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_LIBRARY"; pcmDir = outputDir; } else { command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE"; pcmDir = buildDir; } if(config.debug) { command += " -g -D CRAFTER_BUILD_CONFIGURATION_DEBUG"; } else { command += " -O3"; } std::unordered_set depLibSet; std::vector depThreads = std::vector(config.dependencies.size()); std::mutex libMutex; std::string libsString; for(std::uint_fast32_t i = 0; i < depThreads.size(); i++) { depThreads[i] = std::thread([i, &config, &libMutex, &depLibSet, &buildDir, &pcmDir, &libsString, &binDir](){ if(config.dependencies[i].path.ends_with(".git")) { fs::path name = fs::path(config.dependencies[i].path).filename(); name.replace_extension(); if(!fs::exists(buildDir/name)) { if(!config.dependencies[i].branch.empty()) { RunCommandIgnore(std::format("cd {} && git clone {} && cd {} && git switch {}", buildDir.string(), config.dependencies[i].path, (buildDir/name).string(), config.dependencies[i].branch)); } else if(!config.dependencies[i].commit.empty()){ std::cout << std::format("cd {} && git clone {} && cd {} && git checkout {}", buildDir.string(), config.dependencies[i].path, (buildDir/name).string(), config.dependencies[i].commit) << std::endl; RunCommandIgnore(std::format("cd {} && git clone {} && cd {} && git checkout {}", buildDir.string(), config.dependencies[i].path, (buildDir/name).string(), config.dependencies[i].commit)); } else { RunCommandIgnore(std::format("cd {} && git clone {}", buildDir.string(), config.dependencies[i].path)); } } else if(config.dependencies[i].commit.empty()) { RunCommandIgnore(std::format("cd {} && git pull", (buildDir/name).string())); } config.dependencies[i].path = fs::path(config.dependencies[i].path).filename().replace_extension()/"project.json"; } Project project = Project::LoadFromJSON(config.dependencies[i].path); for(Configuration& depConfig : project.configurations) { if(depConfig.name == config.dependencies[i].configuration){ fs::path depBuildDir; if(depConfig.buildDir.empty()) { depBuildDir = "build"/fs::path(config.name); } else { depBuildDir = depConfig.buildDir/config.name; } project.Build(depConfig, pcmDir, binDir, (fs::path(config.dependencies[i].path).parent_path()/depBuildDir/depConfig.name)); libMutex.lock(); if (depLibSet.insert(project.name).second) { libsString+=std::format(" -l{}", project.name); } for(const std::string& lib2 : depConfig.libs) { if (depLibSet.insert(lib2).second) { libsString+=std::format(" -l{}", lib2); } } for(const Dependency& dep2 : depConfig.dependencies) { if (depLibSet.insert(project.name).second) { libsString+=std::format(" -l{}", project.name); } } libMutex.unlock(); return; } } throw std::runtime_error(std::format("Configuration: {} not found.", config.dependencies[i].configuration)); }); } if(!fs::exists((fs::path(buildDir).parent_path()/path.filename()).string()+".o")) { std::string result = RunCommand(std::format(R"(GCC_VERSION=$(g++ -dumpversion) STD_HEADER="/usr/include/c++/$GCC_VERSION/bits/std.cc" if [ ! -f "$STD_HEADER" ]; then echo "Cannot find std.cc for GCC $GCC_VERSION" exit 1 fi cp "$STD_HEADER" {}/std.cppm clang++ -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/std.cppm -o {}/std.pcm )", fs::path(buildDir).parent_path().string(), fs::path(buildDir).parent_path().string(), fs::path(buildDir).parent_path().string())); if(result != "") { throw std::runtime_error(result); } } for(std::thread& thread : depThreads) { thread.join(); } std::string files; bool repack = false; for(std::unique_ptr& modulee : config.interfaces) { if(modulee->Check(pcmDir)) { threads.emplace_back(&Module::Compile, modulee.get(), command, pcmDir, buildDir); repack = true; } files += std::format(" {}/{}.o", buildDir.string(), modulee->path.filename().string()); for(std::unique_ptr& part : modulee->partitions) { files += std::format(" {}/{}.o", buildDir.string(), part->path.filename().string()); } } for(const Implementation& implementation : config.implementations) { if(implementation.Check(buildDir, pcmDir)) { repack = true; threads.emplace_back(&Implementation::Compile, &implementation, command, pcmDir, buildDir); } files += std::format(" {}/{}_impl.o", buildDir.string(), implementation.path.filename().string()); } for(std::thread& thread : threads) { thread.join(); } command += " -L/usr/local/lib"; command += libsString; for(const std::string& lib : config.libs) { depLibSet.insert(lib); command += std::format(" -l{}", lib); } fileThread.join(); if(config.dependencies.size() > 0){ command += std::format(" -L{}", pcmDir.string()); } if(repack) { if(config.type == CRAFTER_CONFIGURATION_TYPE_EXECUTABLE){ RunClang(std::format("{}{} -o {} -fuse-ld=lld", command, files, (outputDir/name).string())); } else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY){ std::cout << std::format("ar r {}.a {}", (outputDir/fs::path("lib"+name)).string(), files) << std::endl; RunCommandIgnore(std::format("ar r {}.a {}", (outputDir/fs::path("lib"+name)).string(), files)); } else { RunClang(std::format("{}{} -shared -o {}.so -fuse-ld=lld", command, files, (outputDir/name).string())); } } } Project Project::LoadFromJSON(const fs::path& path) { if (!fs::exists(path)) { throw std::runtime_error(std::format("Project file: {} not found.", path.generic_string())); } std::ifstream f(path); nlohmann::json data = nlohmann::json::parse(f); std::string name = data["name"].get(); std::vector configurations; nlohmann::json configs = data["configurations"]; std::vector tests; nlohmann::json testJson = data["tests"]; fs::path workingDir = path; workingDir.remove_filename(); for (nlohmann::json::iterator it = configs.begin(); it != configs.end(); ++it) { configurations.emplace_back(configs, (*it), workingDir); } for (nlohmann::json::iterator it = testJson.begin(); it != testJson.end(); ++it) { tests.emplace_back(configs, (*it), workingDir); } Project project(std::move(name), std::move(workingDir), std::move(configurations)); project.tests = std::move(tests); return project; } std::vector Project::RunTests() { std::vector results; for(Test& test : tests) { results.push_back(RunTest(test)); } return results; } TestResult Project::RunTest(const std::string_view testName) { for(Test& test : tests) { if(test.config.name == testName){ return RunTest(test); } } throw std::runtime_error(std::format("Test: {} not found.", testName)); } TestResult Project::RunTest(Test& test) { fs::path buildDir; if(test.config.buildDir.empty()) { buildDir = "build"/fs::path(test.config.name); } else { buildDir = test.config.buildDir/test.config.name; } char path[PATH_MAX];// ssize_t count = readlink("/proc/self/exe", path, PATH_MAX); if (count == -1) { throw std::runtime_error("Failed to get executable path"); } path[count] = '\0'; if (!fs::exists(buildDir)) { fs::create_directories(buildDir); } if(!fs::exists((fs::path(buildDir).parent_path()/this->path.filename()).string()+".o")) { std::string result = RunCommand(std::format(R"(GCC_VERSION=$(g++ -dumpversion) STD_HEADER="/usr/include/c++/$GCC_VERSION/bits/std.cc" if [ ! -f "$STD_HEADER" ]; then echo "Cannot find std.cc for GCC $GCC_VERSION" exit 1 fi cp "$STD_HEADER" {}/std.cppm clang++ -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/std.cppm -o {}/std.pcm )", fs::path(buildDir).parent_path().string(), fs::path(buildDir).parent_path().string(), fs::path(buildDir).parent_path().string())); if(result != "") { throw std::runtime_error(result); } } // if(!fs::exists(buildDir.parent_path().string()+"/Crafter.Test.pcm")) { // RunClang(std::format("clang++ -std=c++26 --precompile -fprebuilt-module-path={} {}/interfaces/Crafter.Test.cppm -o {}/Crafter.Test.pcm", buildDir.parent_path().string(), std::filesystem::absolute(path).parent_path().parent_path().string(), buildDir.parent_path().string())); // } Build(test.config, buildDir, buildDir); std::string lib = std::format("{}/{}.so", buildDir.string(), name); void* handle = dlopen(lib.c_str(), RTLD_NOW); if (!handle) { throw std::runtime_error(std::format("Failed to load test library, {}, {}", lib, dlerror())); } dlerror(); typedef std::string* (*RunTestFunc)(); RunTestFunc loadedTest = (RunTestFunc) dlsym(handle, "RunTest"); const char* dlsym_error = dlerror(); if (dlsym_error) { dlclose(handle); throw std::runtime_error(std::format("Cannot load symbol 'RunTest': {}", dlsym_error)); } std::string* testResult = loadedTest(); if(testResult != nullptr) { TestResult result = {test.config.name, *testResult}; delete testResult; return result; } else { return {test.config.name, ""}; } } // void AddModule(std::string_view configuration, std::string_view filename); // void AddModule(Configuration& configuration, std::string_view filename); // void AddModuleSourcePair(std::string_view configuration, std::string_view filename); // void AddModuleSourcePair(Configuration& configuration, std::string_view filename); // void AddTest(std::string_view configuration, std::string_view filename, std::string_view content); // void AddTest(Configuration& configuration, std::string_view filename, std::string_view content); // void SaveToJSON(const fs::path& path) const; }