Crafter.Build/implementations/Crafter.Build-Project.cpp

384 lines
17 KiB
C++
Raw Normal View History

2025-10-31 16:50:47 +01:00
/*
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 <dlfcn.h>
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<Configuration>&& 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<std::thread> 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<std::string> depLibSet;
std::vector<std::thread> depThreads = std::vector<std::thread>(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<Module>& 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<ModulePartition>& 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::string>();
std::vector<Configuration> configurations;
nlohmann::json configs = data["configurations"];
std::vector<Test> 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<TestResult> Project::RunTests() {
std::vector<TestResult> 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;
}