/* 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" #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu #include #endif #include module Crafter.Build:Project_impl; import :Project; import std; import :Configuration; import :Command; namespace fs = std::filesystem; namespace Crafter { Project::Project(fs::path&& path) : path(std::move(path)) { if (!fs::exists(this->path)) { throw std::runtime_error(std::format("Project file: {} not found.", this->path.generic_string())); } std::ifstream f(this->path); nlohmann::json data; try { data = nlohmann::json::parse(f); } catch(std::exception& e) { std::cout << e.what() << std::endl; } name = data["name"].get(); this->path.remove_filename(); nlohmann::json configs = data["configurations"]; nlohmann::json testJson = data["tests"]; if(data.contains("bin_dir")) { binDir = this->path/data["bin_dir"].get(); } else { binDir = this->path/"bin"; } if(data.contains("build_dir")) { buildDir = this->path/data["build_dir"].get(); } else { buildDir = this->path/"build"; } if (!fs::exists(binDir)) { fs::create_directories(binDir); } if (!fs::exists(buildDir)) { fs::create_directories(buildDir); } for (nlohmann::json::iterator it = configs.begin(); it != configs.end(); ++it) { configurations.emplace_back(configs, (*it), this->path, *this); } for (nlohmann::json::iterator it = testJson.begin(); it != testJson.end(); ++it) { tests.emplace_back(configs, (*it), this->path, *this); } } Project::Project(fs::path&& path, const std::string_view config) : path(std::move(path)) { if (!fs::exists(this->path)) { throw std::runtime_error(std::format("Project file: {} not found.", this->path.generic_string())); } std::ifstream f(this->path); nlohmann::json data = nlohmann::json::parse(f); name = data["name"].get(); this->path.remove_filename(); nlohmann::json configs = data["configurations"]; if(data.contains("bin_dir")) { binDir = data["bin_dir"].get(); } else { binDir = "bin"; } if(data.contains("build_dir")) { buildDir = data["build_dir"].get(); } else { buildDir = "build"; } if (!fs::exists(binDir)) { fs::create_directories(binDir); } if (!fs::exists(buildDir)) { fs::create_directories(buildDir); } for (nlohmann::json::iterator it = configs.begin(); it != configs.end(); ++it) { if(it->contains("name")) { if((*it)["name"].get() == config) { configurations.emplace_back(configs, (*it), this->path, *this); goto errorSkip; } } else { throw std::runtime_error("Invalid config json, name field is missing"); } } throw std::runtime_error(std::format("Config {} not found, requested as dependency", config)); errorSkip:; } Project::Project(std::string&& name, fs::path&& path, std::vector&& configurations) : name(std::move(name)), path(std::move(path)), configurations(std::move(configurations)) {} Project::Project(std::string&& name, fs::path&& path, std::vector&& configurations, fs::path&& binDir, fs::path&& buildDir) : name(std::move(name)), path(std::move(path)), configurations(std::move(configurations)), binDir(std::move(binDir)), buildDir(std::move(buildDir)) {} std::tuple Project::Build(std::string_view configuration) { for(Configuration& config : configurations) { if(config.name == configuration){ return {config, Build(config)}; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } std::tuple Project::Build(std::string_view configuration, const fs::path& binDir, const fs::path& outputDir, const fs::path& buildDir, std::string outputName) { for(Configuration& config : configurations) { if(config.name == configuration){ return {config, Build(config)}; } } throw std::runtime_error(std::format("Configuration: {} not found.", configuration)); } BuildResult Project::Build(Configuration& config) const { return Build(config, binDir/config.name, binDir/config.name, buildDir/config.name, name); } void AddLibsRecursive(std::string& libsString, std::unordered_set depLibSet, Configuration& depConfig) { for(const std::string& lib2 : depConfig.libs) { if (depLibSet.insert(lib2).second) { libsString += std::format(" -l{}", lib2); } } for(const std::tuple, Configuration&>& dep2 : depConfig.dependencies) { std::string outputLib = std::get<0>(dep2)->name+std::get<1>(dep2).name; if (depLibSet.insert(outputLib).second) { libsString += std::format(" -l{}", outputLib); } AddLibsRecursive(libsString, depLibSet, std::get<1>(dep2)); } } BuildResult Project::Build(Configuration& config, const fs::path& binDir, const fs::path& outputDir, const fs::path& buildDir, std::string outputName) const { BuildResult buildResult; if (!fs::exists(binDir)) { fs::create_directories(binDir); } if (!fs::exists(outputDir)) { fs::create_directories(outputDir); } if (!fs::exists(buildDir)) { fs::create_directories(buildDir); } std::cout << "8" << std::endl; std::vector threads; for(const Shader& shader : config.shaders) { if(!shader.Check(outputDir)) { threads.emplace_back(&Shader::Compile, &shader, outputDir); } } std::cout << "0" << std::endl; std::thread fileThread([&config, &outputDir](){ for (const fs::path& additionalFile : config.additionalFiles) { fs::path destination = outputDir / additionalFile.filename(); if (fs::is_directory(additionalFile)) { for (const auto& entry : fs::recursive_directory_iterator(additionalFile)) { const fs::path& sourcePath = entry.path(); // Compute relative path inside the directory fs::path relativePath = fs::relative(sourcePath, additionalFile); fs::path destPath = destination / relativePath; if (entry.is_directory()) { // Ensure directory exists in destination if (!fs::exists(destPath)) { fs::create_directories(destPath); } } else if (entry.is_regular_file()) { // Ensure parent directory exists fs::create_directories(destPath.parent_path()); if (!fs::exists(destPath)) { fs::copy_file(sourcePath, destPath); } else if (fs::last_write_time(sourcePath) > fs::last_write_time(destPath)) { fs::copy_file(sourcePath, destPath, fs::copy_options::overwrite_existing); } } } } else { // Handle regular file if (!fs::exists(destination)) { fs::copy_file(additionalFile, destination); } else if (fs::last_write_time(additionalFile) > fs::last_write_time(destination)) { fs::copy_file(additionalFile, destination, fs::copy_options::overwrite_existing); } } } }); fs::path exeDir = GetPath(); std::cout << "before pcm" << std::endl; BuildStdPcm(*this, config); std::cout << "after pcm" << std::endl; std::string editedTarget = config.target; std::replace(editedTarget.begin(), editedTarget.end(), '-', '_'); #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu std::string command = std::format("clang++ -stdlib=libc++ --target={} -march={} -mtune={} -std={} -D CRAFTER_BUILD_CONFIGURATION_TARGET_{} -fprebuilt-module-path={}", config.target, config.march, config.march, config.standard, editedTarget, (exeDir/config.target).string()); #endif #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) std::string command = std::format("clang++ -nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1 --target={} -march={} -mtune={} -std={} -D CRAFTER_BUILD_CONFIGURATION_TARGET_{} -fprebuilt-module-path={}", config.target, config.march, config.march, config.standard, editedTarget, (exeDir/config.target).string()); #endif std::cout << "2" << std::endl; for (const auto& entry : fs::recursive_directory_iterator(buildDir)) { if (entry.is_directory() && entry.path().filename() == "include") { command += " -I" + entry.path().string() + " "; } } for (const auto& entry : fs::recursive_directory_iterator(exeDir / "cloneCache")) { if (entry.is_directory() && entry.path().filename() == "include") { command += " -I" + entry.path().string() + " "; } } std::cout << "4" << std::endl; for (const auto& entry : fs::directory_iterator(buildDir)) { if (entry.is_directory()) { command += " -I" + entry.path().string() + " "; } } for (const auto& entry : fs::directory_iterator(exeDir / "cloneCache")) { if (entry.is_directory()) { command += " -I" + entry.path().string() + " "; } } std::cout << "3" << std::endl; if(config.target == "wasm32-wasi") { command += std::format(" --sysroot={} -fno-exceptions -fno-c++-static-destructors", (exeDir/"wasi-sysroot-28.0").string()); } for(const Define& define : config.defines) { if(define.value.empty()) { command += std::format(" -D {}", define.name); } else { command += std::format(" -D {}={}", define.name, define.value); } } fs::path pcmDir; if(config.type == CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY) { #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu command += " -fPIC -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY"; #endif #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY"; #endif pcmDir = binDir; } else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY) { command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_LIBRARY"; pcmDir = binDir; } else { command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE"; pcmDir = buildDir; } #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) command += " -Wno-deprecated-declarations"; #endif command += std::format(" -fprebuilt-module-path={}", pcmDir.string()); std::string cmakeBuildType; if(config.debug) { cmakeBuildType = "Debug"; command += " -g -D CRAFTER_BUILD_CONFIGURATION_DEBUG"; } else { cmakeBuildType = "Release"; command += " -O3"; } for(const std::string& dir : config.includeDirs) { command += std::format(" -I{}", dir); } std::cout << "5" << std::endl; std::unordered_set depLibSet; std::vector depThreads = std::vector(config.dependencies.size()); std::mutex libMutex; std::string files; std::string libsString; std::vector resultsDep(config.dependencies.size()); for(std::uint_fast32_t i = 0; i < depThreads.size(); i++) { depThreads[i] = std::thread([i, &config, &libMutex, &depLibSet, &buildDir, &pcmDir, &libsString, &binDir, this, &buildResult, &resultsDep](){ for(Configuration& config2 : std::get<0>(config.dependencies[i])->configurations) { config2.target = config.target; config2.march = config.march; } std::string outputLib = std::get<0>(config.dependencies[i])->name+std::get<1>(config.dependencies[i]).name; BuildResult depResult = std::get<0>(config.dependencies[i])->Build(std::get<1>(config.dependencies[i]), pcmDir, binDir, buildDir/std::get<0>(config.dependencies[i])->name/std::get<0>(config.dependencies[i])->buildDir, outputLib); libMutex.lock(); if(depResult.repack) { buildResult.repack = true; } resultsDep[i] = depResult.errors; if (depLibSet.insert(outputLib).second) { libsString+=std::format(" -l{}", outputLib); } AddLibsRecursive(libsString, depLibSet, std::get<1>(config.dependencies[i])); libMutex.unlock(); }); } for(const CmakeDep& cmake : config.cmakeDeps) { depThreads.emplace_back([&cmake, &cmakeBuildType, &buildDir](){ #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu system(std::format("cd {} && cmake -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS=\"-stdlib=libc++\" -DCMAKE_EXE_LINKER_FLAGS=\"-stdlib=libc++\" -DCMAKE_SHARED_LINKER_FLAGS=\"-stdlib=libc++\" -DCMAKE_BUILD_TYPE={} -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY={} {} && cmake --build build --config {}", cmake.path.string(), cmakeBuildType, buildDir.string(), buildDir.string(), cmake.options, cmakeBuildType).c_str()); #endif #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) system(std::format("cd {} && cmake -G Ninja -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS=\"-nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1\" -DCMAKE_EXE_LINKER_FLAGS=\"-nostdinc++ -nostdlib++ -L %LIBCXX_DIR%\\lib -lc++\" -DCMAKE_SHARED_LINKER_FLAGS=\"-nostdinc++ -nostdlib++ -L %LIBCXX_DIR%\\lib -lc++\" -DCMAKE_BUILD_TYPE={} -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY={} {} && cmake --build build --config {}", cmake.path.string(), cmakeBuildType, buildDir.string(), buildDir.string(), cmake.options, cmakeBuildType).c_str()); #endif }); } for(std::thread& thread : depThreads) { thread.join(); } for(const std::string& result2 : resultsDep) { buildResult.errors += result2; } if(!buildResult.errors.empty()) { fileThread.join(); return buildResult; } for(const fs::path& cFile: config.c_files) { files+=std::format(" {}_source.o ",(buildDir/cFile.filename()).string()); if(!fs::exists((buildDir/cFile.filename()).string()+"_source.o") || fs::last_write_time(cFile.string()+".c") > fs::last_write_time((buildDir/cFile.filename()).string()+"_source.o")) { threads.emplace_back(&RunClang, std::format("clang {}.c -c -o {}_source.o", cFile.string(), (buildDir/cFile.filename()).string())); } } for(const fs::path& cFile: config.cuda) { files+=std::format(" {}_source.o ",(buildDir/cFile.filename()).string()); if(!fs::exists((buildDir/cFile.filename()).string()+"_source.o") || fs::last_write_time(cFile.string()+".cu") > fs::last_write_time((buildDir/cFile.filename()).string()+"_source.o")) { threads.emplace_back(&RunClang, std::format("nvcc {}.cu -c -o {}_source.o -O3 -arch=sm_89", cFile.string(), (buildDir/cFile.filename()).string())); } } std::vector resultInterfaces(config.interfaces.size()*2); for(uint_fast32_t i = 0; i < config.interfaces.size(); i++) { if(config.interfaces[i]->Check(pcmDir)) { threads.emplace_back(&Module::Compile, config.interfaces[i].get(), command, pcmDir, buildDir, std::ref(resultInterfaces[i])); threads.emplace_back(&Module::CompileSource, config.interfaces[i].get(), command, pcmDir, buildDir, std::ref(resultInterfaces[config.interfaces.size()+i])); buildResult.repack = true; } files += std::format(" {}/{}.o", buildDir.string(), config.interfaces[i]->path.filename().string()); for(std::unique_ptr& part : config.interfaces[i]->partitions) { files += std::format(" {}/{}.o", buildDir.string(), part->path.filename().string()); } } std::vector resultImplementations(config.implementations.size()); for(uint_fast32_t i = 0; i < config.implementations.size(); i++) { if(config.implementations[i].Check(buildDir, pcmDir)) { buildResult.repack = true; threads.emplace_back(&Implementation::Compile, &config.implementations[i], command, buildDir, std::ref(resultImplementations[i])); } files += std::format(" {}/{}_impl.o", buildDir.string(), config.implementations[i].path.filename().string()); } for(std::thread& thread : threads) { thread.join(); } for(const std::string& result2 : resultInterfaces) { buildResult.errors += result2; } for(const std::string& result2 : resultImplementations) { buildResult.errors += result2; } if(!buildResult.errors.empty()) { fileThread.join(); return buildResult; } if(config.target != "wasm32-wasi") { command += " -L/usr/local/lib"; } command += std::format(" -L{}", buildDir.string()); if(config.type != CRAFTER_CONFIGURATION_TYPE_LIBRARY) { command += libsString; } for(const std::string& lib : config.libs) { depLibSet.insert(lib); command += std::format(" -l{}", lib); } for(const std::string& dir : config.libDirs) { command += std::format(" -L{}", dir); } fileThread.join(); if(config.dependencies.size() > 0){ command += std::format(" -L{}", pcmDir.string()); } if(buildResult.repack) { if(config.type == CRAFTER_CONFIGURATION_TYPE_EXECUTABLE) { if(config.target == "wasm32-wasi") { outputName += ".wasm"; }// #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu buildResult.errors = RunClang(std::format("{}{} -o {} -fuse-ld=lld ", command, files, (binDir/outputName).string())); #endif #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) system(std::format("copy \"%LIBCXX_DIR%\\lib\\c++.dll\" \"{}\\c++.dll\"", binDir.string()).c_str()); buildResult.errors = RunClang(std::format("{}{} -o {}.exe -fuse-ld=lld -L %LIBCXX_DIR%\\lib -lc++ -nostdinc++ -nostdlib++", command, files, (binDir/outputName).string())); #endif } else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY) { #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu RunClang(std::format("ar r {}.a {}", (binDir/fs::path(std::string("lib")+outputName)).string(), files)); #endif #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) RunClang(std::format("llvm-lib.exe {} /OUT:{}.lib", files, (binDir/fs::path(outputName)).string())); #endif } else { buildResult.errors = RunClang(std::format("{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld", command, files, (binDir/(std::string("lib")+outputName)).string())); } } return buildResult; } 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) const { #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu fs::path binDir = path/this->binDir/test.config.name; fs::path buildDir = path/this->buildDir/test.config.name; BuildResult buildResult = Build(test.config, binDir, binDir, buildDir, test.config.name); if(!buildResult.errors.empty()) { return {test.config.name, buildResult.errors}; } std::string lib = std::format("{}/lib{}.so", binDir.string(), test.config.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) { std::string msg = std::string(dlsym_error); dlclose(handle); throw std::runtime_error(std::format("Cannot load symbol 'RunTest': {}", msg)); } std::string* testResult; try { testResult = loadedTest(); } catch(std::exception& e) { return {test.config.name, std::string(e.what())}; } if(testResult != nullptr) { TestResult result = {test.config.name, *testResult}; delete testResult; return result; } else { return {test.config.name, ""}; } return {test.config.name, ""}; #else return {test.config.name, "Tests are not supported on this platform"}; #endif } }