This commit is contained in:
Jorijn van der Graaf 2025-10-31 16:50:47 +01:00
commit 0eed272765
35 changed files with 1634 additions and 1507 deletions

View file

@ -1,25 +0,0 @@
name: Main
on:
push:
tags:
- '*'
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: ./build.sh
- name: Release
- uses: actions/forgejo-release@v2
with:
direction: upload
url: https://forgejo.catcrafts.net
repo: "Crafter/Crafter.Build"
tag: "${{ github.ref_name }}"
sha: "${{ github.sha }}"
release-dir: bin
token: ${{ secrets.TOKEN }}

View file

@ -1,89 +0,0 @@
/*
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 <iostream>
#include <memory>
#include <stdexcept>
#include <array>
#include <string>
#include <cstdio>
#include <string_view>
#include <regex>
export module Crafter.Build:Bounce;
namespace Crafter::Build {
export std::string RunCommand(const std::string_view cmd) {
std::array<char, 128> buffer;
std::string result;
std::string with = std::string(cmd) + " 2>&1";
// Open pipe to file
FILE* pipe = popen(with.c_str(), "r");
if (!pipe) throw std::runtime_error("popen() failed!");
// Read till end of process:
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
result += buffer.data();
}
// Close pipe
pclose(pipe);
return result;
}
export void RunCommandIgnore(const std::string_view cmd) {
std::string with = std::string(cmd) + " > /dev/null 2>&1";
FILE* pipe = popen(with.c_str(), "r");
if (!pipe) throw std::runtime_error("popen() failed!");
pclose(pipe);
}
export struct ClangError {
std::string filename;
uint32_t line_number;
std::string error_message;
std::string code;
};
export std::vector<ClangError> RunClang(const std::string_view cmd) {
std::string result = RunCommand(cmd);
std::vector<ClangError> errors;
std::regex error_regex(R"((/[^:]+\.cpp):(\d+):\d+: error: (.*)\n\s*[0-9| ]*\s*(.*))");
std::smatch match;
while (std::regex_search(result, match, error_regex)) {
ClangError error;
error.filename = match[1].str();
error.line_number = std::stoi(match[2].str());
error.error_message = match[3].str();
error.code = match[4].str();
errors.push_back(error);
result = match.suffix().str();
}
if(result != "" && errors.size() == 0) {
throw std::runtime_error(result);
}
return errors;
}
}

View file

@ -1,199 +0,0 @@
/*
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 "json.hpp"
#include <glslang/Public/ShaderLang.h>
module Crafter.Build;
using namespace Crafter::Build;
namespace fs = std::filesystem;
Configuration::Configuration(std::string name, std::string standard, std::vector<fs::path> sourceFiles, std::vector<fs::path> moduleFiles, std::string optimizationLevel, std::string buildDir, std::string outputDir, std::string type, std::string target, std::string march, std::vector<Dependency> dependencies, std::vector<fs::path> additionalFiles, std::vector<std::string> flags, bool debug, std::vector<std::string> libs, std::vector<std::string> lib_paths, std::vector<fs::path> c_files, std::vector<Shader> shaderFiles, std::vector<std::string> includeDirs, bool verbose, std::vector<Define> defines): name(name), standard(standard), sourceFiles(sourceFiles), moduleFiles(moduleFiles), optimizationLevel(optimizationLevel), buildDir(buildDir), outputDir(outputDir), type(type), target(target), march(march), dependencies(dependencies), additionalFiles(additionalFiles), flags(flags), debug(debug), libs(libs), lib_paths(lib_paths), c_files(c_files), shaderFiles(shaderFiles), includeDirs(includeDirs), verbose(verbose), defines(defines) {
}
Configuration::Configuration(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir) {
name = config["name"].get<std::string>();
for (auto& [key, val] : config.items())
{
if(key == "standard"){
standard = val.get<std::string>();
} else if(key == "target") {
target = val.get<std::string>();
} else if(key == "debug") {
debug = val.get<bool>();
} else if(key == "verbose") {
verbose = val.get<bool>();
} else if(key == "type") {
type = val.get<std::string>();
} else if(key == "march") {
march = val.get<std::string>();
} else if(key == "source_files") {
const std::vector<std::string> tempSourceFiles = val.get<std::vector<std::string>>();
sourceFiles = std::vector<fs::path>(tempSourceFiles.size());
for(std::int_fast32_t i = 0; i < sourceFiles.size(); i++){
const std::filesystem::path filePath (tempSourceFiles[i]);
const std::filesystem::path fullFilePath = workingDir / filePath;
sourceFiles[i] = fullFilePath.generic_string();
}
} else if(key == "c_files") {
const std::vector<std::string> tempSourceFiles = val.get<std::vector<std::string>>();
c_files = std::vector<fs::path>(tempSourceFiles.size());
for(std::int_fast32_t i = 0; i < c_files.size(); i++){
const std::filesystem::path filePath (tempSourceFiles[i]);
const std::filesystem::path fullFilePath = workingDir / filePath;
c_files[i] = fullFilePath.generic_string();
}
} else if(key == "flags") {
flags = val.get<std::vector<std::string>>();
} else if(key == "lib_paths") {
lib_paths = val.get<std::vector<std::string>>();
} else if(key == "include_dirs") {
includeDirs = val.get<std::vector<std::string>>();
} else if(key == "libs") {
libs = val.get<std::vector<std::string>>();
} else if(key == "module_files") {
const std::vector<std::string> tempModuleFiles = val.get<std::vector<std::string>>();
moduleFiles = std::vector<fs::path>(tempModuleFiles.size());
for(std::int_fast32_t i = 0; i < moduleFiles.size(); i++){
const std::filesystem::path filePath (tempModuleFiles[i]);
const std::filesystem::path fullFilePath = workingDir / filePath;
moduleFiles[i] = fullFilePath.generic_string();
}
} else if(key == "shaders") {
for (auto it : val) {
const std::filesystem::path filePath (it["path"].get<std::string>());
const std::filesystem::path fullFilePath = workingDir / filePath;
shaderFiles.emplace_back(fullFilePath, it["entrypoint"].get<std::string>(), static_cast<EShLanguage>(it["type"].get<std::uint32_t>()));
}
} else if(key == "defines") {
for (auto it : val) {
std::string name = it["name"].get<std::string>();
std::string value = it["value"].get<std::string>();
defines.emplace_back(name, value);
}
} else if(key == "additional_files") {
const std::vector<std::string> tempAdditionalFiles = val.get<std::vector<std::string>>();
additionalFiles = std::vector<fs::path>(tempAdditionalFiles.size());
for(std::int_fast32_t i = 0; i < additionalFiles.size(); i++){
const std::filesystem::path filePath (tempAdditionalFiles[i]);
const std::filesystem::path fullFilePath = workingDir / filePath;
additionalFiles[i] = fullFilePath.generic_string();
}
} else if(key == "optimization_level") {
optimizationLevel = val.get<std::string>();
} else if(key == "build_dir") {
const std::string tempBuildDir = val.get<std::string>();
const std::filesystem::path buildPath (tempBuildDir);
const std::filesystem::path fullBuildPath = workingDir / buildPath;
buildDir = fullBuildPath.generic_string();
} else if(key == "output_dir") {
const std::string tempOutputDir = val.get<std::string>();
const std::filesystem::path outputPath (tempOutputDir);
const std::filesystem::path fullOutputPath = workingDir / outputPath;
outputDir = fullOutputPath.generic_string();
} else if(key == "dependencies") {
for (auto it : val) {
std::string commit;
std::string branch;
if(it.contains("commit")){
commit = it["commit"].get<std::string>();
}
if(it.contains("branch")){
branch = it["branch"].get<std::string>();
}
dependencies.emplace_back(it["path"].get<std::string>(), it["configuration"].get<std::string>(), commit, branch);
}
} else if(key != "extends") {
additionalProperties.insert({key, val});
}
}
if(config.contains("extends")) {
const std::vector<std::string> extends = config["extends"].get<std::vector<std::string>>();
for(const std::string& extendName : extends) {
for (auto it : configs) {
if(it["name"].get<std::string>() == extendName) {
Configuration extendData = Configuration(configs, it, workingDir);
if(!extendData.standard.empty() && standard.empty()){
standard = extendData.standard;
}
if(!extendData.sourceFiles.empty()){
sourceFiles.insert(sourceFiles.end(), extendData.sourceFiles.begin(), extendData.sourceFiles.end());
}
if(!extendData.moduleFiles.empty()){
moduleFiles.insert(moduleFiles.end(), extendData.moduleFiles.begin(), extendData.moduleFiles.end());
}
if(!extendData.additionalFiles.empty()){
additionalFiles.insert(additionalFiles.end(), extendData.additionalFiles.begin(), extendData.additionalFiles.end());
}
if(!extendData.optimizationLevel.empty() && optimizationLevel.empty()){
optimizationLevel = extendData.optimizationLevel;
}
if(!extendData.dependencies.empty()){
dependencies.insert(dependencies.end(), extendData.dependencies.begin(), extendData.dependencies.end());
}
if(!extendData.buildDir.empty() && buildDir.empty()) {
buildDir = extendData.buildDir;
}
if(!extendData.outputDir.empty() && outputDir.empty()) {
outputDir = extendData.outputDir;
}
if(!extendData.target.empty() && target.empty()) {
target = extendData.target;
}
if(!extendData.type.empty() && type.empty()) {
type = extendData.type;
}
if(!extendData.march.empty() && march.empty()) {
march = extendData.march;
}
if(!extendData.lib_paths.empty()){
lib_paths.insert(lib_paths.end(), extendData.lib_paths.begin(), extendData.lib_paths.end());
}
if(!extendData.c_files.empty()){
c_files.insert(c_files.end(), extendData.c_files.begin(), extendData.c_files.end());
}
if(!extendData.flags.empty()){
flags.insert(flags.end(), extendData.flags.begin(), extendData.flags.end());
}
if(extendData.debug){
debug = true;
}
if(extendData.verbose){
verbose = true;
}
if(!extendData.libs.empty()){
libs.insert(libs.end(), extendData.libs.begin(), extendData.libs.end());
}
if(!extendData.shaderFiles.empty()){
shaderFiles.insert(shaderFiles.end(), extendData.shaderFiles.begin(), extendData.shaderFiles.end());
}
if(!extendData.includeDirs.empty()){
includeDirs.insert(includeDirs.end(), extendData.includeDirs.begin(), extendData.includeDirs.end());
}
if(!extendData.defines.empty()){
defines.insert(defines.end(), extendData.defines.begin(), extendData.defines.end());
}
break;
}
}
}
}
}

View file

@ -1,69 +0,0 @@
/*
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 <string>
#include <vector>
#include "json.hpp"
#include <unordered_map>
#include <format>
export module Crafter.Build:Configuration;
import :Dependency;
import :Shader;
namespace fs = std::filesystem;
export namespace Crafter::Build {
struct Define {
std::string name;
std::string value;
std::string ToString() const {
return std::format(" -D {}={}", name, value);
}
};
class Configuration {
public:
std::string name;
std::string standard;
std::vector<fs::path> sourceFiles;
std::vector<fs::path> c_files;
std::vector<fs::path> moduleFiles;
std::vector<Shader> shaderFiles;
std::vector<fs::path> additionalFiles;
std::vector<std::string> includeDirs;
std::vector<Define> defines;
std::string optimizationLevel;
std::string buildDir;
std::string outputDir;
std::string type;
std::string target;
std::string march;
bool debug;
std::vector<std::string> libs;
std::vector<std::string> lib_paths;
std::vector<Dependency> dependencies;
std::unordered_map<std::string, nlohmann::json> additionalProperties;
std::vector<std::string> flags;
bool verbose = false;
Configuration(std::string name, std::string standard, std::vector<fs::path> sourceFiles, std::vector<fs::path> moduleFiles, std::string optimizationLevel, std::string buildDir, std::string outputDir, std::string type, std::string target, std::string march, std::vector<Dependency> dependencies, std::vector<fs::path> additionalFiles, std::vector<std::string> flags, bool debug, std::vector<std::string> libs, std::vector<std::string> lib_paths, std::vector<fs::path> c_files, std::vector<Shader> shaderFiles, std::vector<std::string> includeDirs, bool verbose, std::vector<Define> defines);
Configuration(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir);
};
}

View file

@ -1,270 +0,0 @@
/*
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 <string>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <print>
#include <execution>
#include <tuple>
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<ClangError> ModulePartition::Compile(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags) {
std::vector<ClangError> 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<ClangError> 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<ClangError> 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<ClangError> errors;
std::vector<std::thread> threads;
for(auto& [key, val] : partitions) {;
if(val.needsRecompiling) {
threads.emplace_back([&val, &clangDir, &config, &pcmDir, &target, &march, &flags, &errors, &errorMutex](){
std::vector<ClangError> 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<std::thread> threads2;
for(auto& [key, val] : partitions) {;
if(val.needsRecompiling) {
threads2.emplace_back([&val, &clangDir, &config, &pcmDir, &target, &march, &flags, &buildDir, &errors, &errorMutex](){
std::vector<ClangError> 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<ClangError> 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>, std::vector<ClangError>> 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<Module> 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<ClangError> 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<ClangError> 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};
}

View file

@ -1,68 +0,0 @@
/*
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 <string>
#include <filesystem>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <atomic>
#include <thread>
#include <condition_variable>
#include <tuple>
export module Crafter.Build:ModuleFile;
import :Configuration;
import :Bounce;
namespace fs = std::filesystem;
export namespace Crafter::Build {
class Module;
class ModulePartition {
public:
bool needsRecompiling = false;
fs::path path;
Module* parent;
std::vector<std::string> moduleDependencies;
std::vector<ModulePartition*> partitionDependants;
std::vector<std::string> partitionDependencies;
std::vector<ModulePartition*> partitionDependenciesP;
const std::string name;
std::atomic<bool>* compiled = new std::atomic<bool>(false);
ModulePartition(const std::string& name, const fs::path& path, Module* parent, const std::string& fileContent, const fs::path& pcmDir);
void AddDependants();
void Check();
std::vector<ClangError> Compile(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags);
std::vector<ClangError> 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);
};
class Module {
public:
bool needsRecompiling = false;
std::unordered_map<std::string, ModulePartition> partitions;
const std::string name;
const fs::path path;
Module(const std::string& name, const fs::path& path, const Configuration& config, const fs::path& pcmDir, std::string& files, const fs::path& buildDir);
void Check();
std::vector<ClangError> 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);
static std::tuple<std::vector<Module>, std::vector<ClangError>> 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);
};
}

View file

@ -1,304 +0,0 @@
/*
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 <vector>
#include <string>
#include <print>
#include <fstream>
#include <iostream>
#include "json.hpp"
#include <filesystem>
#include <unordered_set>
#include <thread>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <regex>
#include <mutex>
#include <tuple>
module Crafter.Build;
using namespace Crafter::Build;
namespace fs = std::filesystem;
Project::Project(std::string name, fs::path path, std::vector<Configuration> configurations) : name(name), path(path), configurations(configurations) {
}
std::tuple<Configuration&, std::vector<ClangError>> Project::Build(std::string configuration) {
for(Configuration& config : configurations) {
if(config.name == configuration){
return {config, Build(config)};
}
}
throw std::runtime_error("Configuration: " + configuration + " not found.");
}
std::tuple<Configuration&, std::vector<ClangError>> Project::Build(std::string configuration, fs::path outputDir) {
for(Configuration& config : configurations) {
if(config.name == configuration){
return {config, Build(config, outputDir)};
}
}
throw std::runtime_error("Configuration: " + configuration + " not found.");
}
std::tuple<Configuration&, std::vector<ClangError>> Project::Build(std::string configuration, fs::path outputDir, fs::path binDir) {
for(Configuration& config : configurations) {
if(config.name == configuration){
return {config, Build(config, outputDir, binDir)};
}
}
throw std::runtime_error("Configuration: " + configuration + " not found.");
}
std::vector<ClangError> Project::Build(Configuration& configuration) {
return Build(configuration, configuration.outputDir);
}
std::vector<ClangError> Project::Build(Configuration& config, fs::path outputDir) {
return Build(config, outputDir, outputDir);
}
std::vector<ClangError> Project::Build(Configuration& config, fs::path outputDir, fs::path binDir) {
if(config.standard.empty()) {
config.standard = "c++26";
}
if(config.march.empty()) {
config.march = "native";
}
if(config.type.empty()) {
config.type = "executable";
}
if (!fs::exists(config.buildDir)) {
fs::create_directory(config.buildDir);
}
if (!fs::exists(outputDir)) {
fs::create_directory(outputDir);
}
if (!fs::exists(config.buildDir)) {
fs::create_directory(config.buildDir);
}
std::string buildDir = config.buildDir/fs::path(config.name);
if (!fs::exists(buildDir)) {
fs::create_directory(buildDir);
}
std::string target;
if(!config.target.empty()){
target = std::format("-target {}", config.target);
}
fs::path pcmDir;
if(config.type == "library" || config.type == "shared-library"){
pcmDir = outputDir;
}else{
pcmDir = buildDir;
}
std::string libs = " -L/usr/local/lib";
if(config.target != "x86_64-w64-mingw64" && config.target != "x86_64-w64-mingw32") {
libs += " -L/usr/lib/";
}
for(const std::string& path : config.lib_paths) {
libs += std::format(" -L{}", path);
}
for(const std::string& path : config.includeDirs) {
libs += std::format(" -I{}", path);
}
for(Shader& shader : config.shaderFiles) {
shader.Compile(binDir);
}
std::vector<std::thread> depThreads = std::vector<std::thread>(config.dependencies.size());
if(config.dependencies.size() > 0){
libs += std::format(" -L{}", pcmDir.generic_string());
}
std::unordered_set<std::string> depLibSet;
for(std::int_fast32_t i = 0; i < depThreads.size(); i++) {
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()) {
system(std::format("cd {} && git clone {} && cd {} && git switch {}", buildDir, config.dependencies[i].path, (buildDir/name).generic_string(), config.dependencies[i].branch).c_str());
} else if(!config.dependencies[i].commit.empty()){
std::cout << std::format("cd {} && git clone {} && cd {} && git checkout {}", buildDir, config.dependencies[i].path, (buildDir/name).generic_string(), config.dependencies[i].commit).c_str() << std::endl;
system(std::format("cd {} && git clone {} && cd {} && git checkout {}", buildDir, config.dependencies[i].path, (buildDir/name).generic_string(), config.dependencies[i].commit).c_str());
} else {
system(std::format("cd {} && git clone {}", buildDir, config.dependencies[i].path).c_str());
}
} else if(config.dependencies[i].commit.empty()) {
system(std::format("cd {} && git pull", (buildDir/name).generic_string()).c_str());
}
config.dependencies[i].path = fs::path(config.dependencies[i].path).filename().replace_extension();
Project project = Project::LoadFromJSON(fs::path(buildDir)/config.dependencies[i].path/"project.json");
config.dependencies[i].name = project.name;
if (depLibSet.insert(project.name).second) {
libs+=std::format(" -l{}", project.name);
}
std::tuple<Configuration&, std::vector<ClangError>> depConfig = project.Build(config.dependencies[i].configuration, pcmDir, binDir);
if(std::get<1>(depConfig).size() > 0) {
return std::get<1>(depConfig);
}
for(const std::string& lib2 : std::get<0>(depConfig).libs) {
if (depLibSet.insert(lib2).second) {
libs+=std::format(" -l{}", lib2);
}
}
for(const Dependency& dep2 : std::get<0>(depConfig).dependencies) {
if (depLibSet.insert(dep2.name).second) {
libs+=std::format(" -l{}", dep2.name);
}
}
} else{
Project project = Project::LoadFromJSON(config.dependencies[i].path);
config.dependencies[i].name = project.name;
if (depLibSet.insert(project.name).second) {
libs+=std::format(" -l{}", project.name);
}
std::tuple<Configuration&, std::vector<ClangError>> depConfig = project.Build(config.dependencies[i].configuration, pcmDir, binDir);
if(std::get<1>(depConfig).size() > 0) {
return std::get<1>(depConfig);
}
for(const std::string& lib2 : std::get<0>(depConfig).libs) {
if (depLibSet.insert(lib2).second) {
libs+=std::format(" -l{}", lib2);
}
}
for(const Dependency& dep2 : std::get<0>(depConfig).dependencies) {
if (depLibSet.insert(dep2.name).second) {
libs+=std::format(" -l{}", dep2.name);
}
}
}
}
std::string name = this->name;
std::string clangDir;
if(config.target == "wasm32-unknown-wasi" || config.target == "wasm64-unknown-wasi"){
clangDir = "${WASI_SDK_PATH}/bin/clang++ --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -Wno-unused-command-line-argument -Wl,--export-all -fno-exceptions";
if(config.type != "library") {
name+=".wasm";
}
} else if(config.target == "wasm32" || config.target == "wasm64") {
clangDir = "${WASI_SDK_PATH}/bin/clang++ --no-standard-libraries -Wl,--no-entry -Wl,--export-all -Wno-unused-command-line-argument -fno-exceptions";
if(config.type != "library") {
name+=".wasm";
}
} else {
clangDir = "clang++";
}
std::string flags;
for(const std::string& flag : config.flags) {
flags+=flag;
}
if(config.debug) {
flags+=" -g";
}
for(const std::string& lib : config.libs) {
libs+= std::format(" -l{}",lib);
}
std::string march;
if(config.target != "wasm32-unknown-wasi"){
march = std::format("-march={}", config.march);
}
std::string files;
std::tuple<std::vector<Module>, std::vector<ClangError>> modules = Module::GetModules(clangDir, config, pcmDir, target, march, flags, files, buildDir);
if(std::get<1>(modules).size() > 0) {
return std::get<1>(modules);
}
std::vector<ClangError> errors = Source::GetSourceFiles(clangDir, config, pcmDir, target, march, flags, std::get<0>(modules), files, buildDir);
if(errors.size() > 0) {
return errors;
}
std::vector<std::thread> threads;
for(std::uint_fast32_t i = 0; i < config.c_files.size(); i++) {
files+=std::format("{}_source.o ",(buildDir/config.c_files[i].filename()).generic_string());
if(!fs::exists((buildDir/config.c_files[i].filename()).generic_string()+"_source.o") || fs::last_write_time(config.c_files[i].generic_string()+".c") > fs::last_write_time((buildDir/config.c_files[i].filename()).generic_string()+"_source.o")) {
threads.emplace_back([i, &config, pcmDir, target, clangDir, flags, march, &buildDir](){
RunCommand(std::format("clang {}.c -c -O{} {} {} -o {}_source.o {}", config.c_files[i].generic_string(), config.optimizationLevel, march, flags, (buildDir/config.c_files[i].filename()).generic_string(), target).c_str());
});
}
}
for(std::thread& thread : threads){
thread.join();
}
if(config.type == "executable"){
std::string command = std::format("{} {} {}-O{} -o {} {} {} -fuse-ld=lld", clangDir, flags, files, config.optimizationLevel, (outputDir/name).generic_string(), target, libs);
if(config.verbose) {
std::cout << command << std::endl;
}
std::vector<ClangError> errors = RunClang(command);
if(errors.size() > 0) {
return errors;
}
} else if(config.type == "library"){
RunCommandIgnore(std::format("ar r {}.a {}", (outputDir/fs::path("lib"+name)).generic_string(), files).c_str());
} else if(config.type == "shared-library"){
RunCommandIgnore(std::format("ar r {}.so {}", (outputDir/fs::path("lib"+name)).generic_string(), files).c_str());
}
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);
}
}
return {};
}
Project Project::LoadFromJSON(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);
const std::string name = data["name"].get<std::string>();
std::vector<Configuration> configurations;
nlohmann::json configs = data["configurations"];
const fs::path workingDir = path.remove_filename();
for (nlohmann::json::iterator it = configs.begin(); it != configs.end(); ++it) {
configurations.emplace_back(configs, (*it), workingDir);
}
return Project(name, workingDir, configurations);
}

View file

@ -1,47 +0,0 @@
/*
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 <vector>
#include <string>
#include <filesystem>
#include <tuple>
export module Crafter.Build:Project;
import :Configuration;
import :Bounce;
namespace fs = std::filesystem;
export namespace Crafter::Build {
class Project {
public:
std::string name;
fs::path path;
std::vector<Configuration> configurations;
Project(std::string name, fs::path path, std::vector<Configuration> configurations);
std::tuple<Configuration&, std::vector<ClangError>> Build(std::string configuration);
std::tuple<Configuration&, std::vector<ClangError>> Build(std::string configuration, fs::path outputDir);
std::tuple<Configuration&, std::vector<ClangError>> Build(std::string configuration, fs::path outputDir, fs::path binDir);
std::vector<ClangError> Build(Configuration& configuration);
std::vector<ClangError> Build(Configuration& configuration, fs::path outputDir);
std::vector<ClangError> Build(Configuration& configuration, fs::path outputDir, fs::path binDir);
void SaveToJSON(fs::path path);
static Project LoadFromJSON(fs::path path);
};
}

View file

@ -1,102 +0,0 @@
/*
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 <string>
#include <iostream>
#include <filesystem>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/Public/ResourceLimits.h>
#include "DirStackFileIncluder.h"
#include <fstream>
#include <iterator>
module Crafter.Build;
using namespace Crafter::Build;
Shader::Shader(fs::path path, std::string entrypoint, EShLanguage type): path(path), entrypoint(entrypoint), type(type) {
}
void Shader::Compile(fs::path outputDir) {
if(!fs::exists((outputDir/path.filename()).replace_extension("spirv")) || fs::last_write_time(path) > fs::last_write_time((outputDir/path.filename()).replace_extension("spirv"))) {
glslang::InitializeProcess();
EShMessages messages = static_cast<EShMessages>(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules);
std::ifstream fileStream(path, std::ios::in | std::ios::binary);
if (!fileStream) {
throw std::ios_base::failure("Failed to open file: " + path.string());
}
std::ostringstream contents;
contents << fileStream.rdbuf();
std::string src = contents.str();
const char *file_name_list[1] = {""};
const char *shader_source = reinterpret_cast<const char *>(src.data());
glslang::TShader shader(type);
shader.setStringsWithLengthsAndNames(&shader_source, nullptr, file_name_list, 1);
shader.setEntryPoint(entrypoint.c_str());
shader.setSourceEntryPoint(entrypoint.c_str());
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4);
DirStackFileIncluder includeDir;
includeDir.pushExternalLocalDirectory(path.parent_path().generic_string());
std::string info_log;
if (!shader.parse(GetDefaultResources(), 100, false, messages, includeDir))
{
info_log = std::string(shader.getInfoLog()) + std::string(shader.getInfoDebugLog());
}
// Add shader to new program object.
glslang::TProgram program;
program.addShader(&shader);
// Link program.
if (!program.link(messages))
{
info_log = std::string(program.getInfoLog()) + std::string(program.getInfoDebugLog());
}
// Save any info log that was generated.
if (shader.getInfoLog())
{
info_log += std::string(shader.getInfoLog()) + std::string(shader.getInfoDebugLog());
}
if (program.getInfoLog())
{
info_log += std::string(program.getInfoLog()) + std::string(program.getInfoDebugLog());
}
glslang::TIntermediate* intermediate = program.getIntermediate(type);
if (!intermediate)
{
info_log += "Failed to get shared intermediate code.";
}
spv::SpvBuildLogger logger;
std::vector<std::uint32_t> spirv;
std::cout << info_log;
glslang::GlslangToSpv(*intermediate, spirv, &logger);
std::cout << logger.getAllMessages();
glslang::FinalizeProcess();
fs::path filename = path.filename().replace_extension("spirv");
std::ofstream file(outputDir/filename, std::ios::binary);
file.write(reinterpret_cast<const char*>(spirv.data()), spirv.size() * sizeof(std::uint32_t));
}
}

View file

@ -1,104 +0,0 @@
/*
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 <string>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <print>
#include <mutex>
#include <execution>
module Crafter.Build;
using namespace Crafter::Build;
namespace fs = std::filesystem;
std::vector<ClangError> Source::GetSourceFiles(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags, const std::vector<Module>& modules, std::string& files, const fs::path& buildDir) {
std::string defines;
for(const Define& define : config.defines) {
defines+=define.ToString();
}
std::vector<std::thread> threads;
std::mutex errorMutex;
std::vector<ClangError> errors;
for(const fs::path& sourceFile : config.sourceFiles) {
files+=std::format("{}_source.o ",(buildDir/sourceFile.filename()).generic_string());
if(Source::Check(sourceFile, modules, config, buildDir)) {
threads.emplace_back([&config, sourceFile, pcmDir, target, clangDir, flags, march, buildDir, defines, &errors, &errorMutex]() {
std::string command = std::format("{} {} -std={} {}.cpp -fprebuilt-module-path={} -c -O{} {} {} -o {}_source.o {}", clangDir, defines, config.standard, sourceFile.generic_string(), pcmDir.generic_string(), config.optimizationLevel, march, flags, (buildDir/sourceFile.filename()).generic_string(), target);
if(config.verbose) {
std::cout << command << std::endl;
}
std::vector<ClangError> newErrors = RunClang(command);
if(newErrors.size() > 0) {
errorMutex.lock();
errors.insert(errors.end(), newErrors.begin(), newErrors.end());
errorMutex.unlock();
}
});
}
}
for(std::thread& thread : threads){
thread.join();
}
return errors;
}
bool Source::Check(const fs::path& path, const std::vector<Module>& modules, const Configuration& config, const fs::path& buildDir) {
if(!fs::exists((buildDir/path.filename()).generic_string()+"_source.o") || fs::last_write_time((path).generic_string()+".cpp") > fs::last_write_time((buildDir/path.filename()).generic_string()+"_source.o")) {
return true;
}
std::ifstream t(path.generic_string()+".cpp");
std::stringstream buffer;
buffer << t.rdbuf();
std::string fileContent = buffer.str();
{
std::regex pattern(R"(module ([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;
for(const Module& module : modules) {
if(module.name == match[1] && module.needsRecompiling) {
return true;
}
}
++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;
for(const Module& module : modules) {
if(module.name == match[1] && module.needsRecompiling) {
return true;
}
}
++currentMatch;
}
}
return false;
}

View file

@ -1,42 +0,0 @@
/*
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 <string>
#include <filesystem>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <atomic>
#include <thread>
#include <condition_variable>
export module Crafter.Build:SourceFile;
import :Configuration;
import :ModuleFile;
namespace fs = std::filesystem;
namespace Crafter::Build {
class Source {
public:
static std::vector<ClangError> GetSourceFiles(std::string clangDir, const Configuration& config, fs::path pcmDir, std::string target, const std::string& march, const std::string& flags, const std::vector<Module>& modules, std::string& files, const fs::path& buildDir);
private:
static bool Check(const fs::path& path, const std::vector<Module>& modules, const Configuration& config, const fs::path& buildDir);
};
}

View file

@ -7,11 +7,10 @@ This is a simple and easy to use C++ build system for Linux designed for use wit
## Prerequisites: ## Prerequisites:
``` ```
lld lld
clang 18> clang
git git
libpqxx lldb
onetbb onetbb
boost
glslang glslang
``` ```

View file

@ -1,31 +1,79 @@
mkdir build mkdir build
mkdir bin mkdir bin
clang++ -std=c++26 Crafter.Build-Bounce.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Bounce.pcm GCC_VERSION=$(g++ -dumpversion)
clang++ -std=c++26 Crafter.Build-Shader.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Shader.pcm STD_HEADER="/usr/include/c++/$GCC_VERSION/bits/std.cc"
clang++ -std=c++26 Crafter.Build-Dependency.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Dependency.pcm if [ ! -f "$STD_HEADER" ]; then
clang++ -std=c++26 Crafter.Build-Configuration.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Configuration.pcm echo "Cannot find std.cc for GCC $GCC_VERSION"
clang++ -std=c++26 Crafter.Build-ModuleFile.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-ModuleFile.pcm exit 1
clang++ -std=c++26 Crafter.Build-SourceFile.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-SourceFile.pcm fi
clang++ -std=c++26 Crafter.Build-Project.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Project.pcm cp "$STD_HEADER" ./build/std.cppm
clang++ -std=c++26 Crafter.Build.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build.pcm clang++ -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile ./build/std.cppm -o ./build/std.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-FixedVector.cppm -o ./build/Crafter.Build-FixedVector.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Command.cppm -o ./build/Crafter.Build-Command.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Shader.cppm -o ./build/Crafter.Build-Shader.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Module.cppm -o ./build/Crafter.Build-Module.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Implementation.cppm -o ./build/Crafter.Build-Implementation.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Configuration.cppm -o ./build/Crafter.Build-Configuration.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Test.cppm -o ./build/Crafter.Build-Test.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build interfaces/Crafter.Build-Project.cppm -o ./build/Crafter.Build-Project.pcm
clang++ -std=c++26 --precompile -fprebuilt-module-path=./build -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE interfaces/Crafter.Build.cppm -o ./build/Crafter.Build.pcm
clang++ -std=c++26 Crafter.Build-Dependency.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Dependency_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-FixedVector.pcm -o ./build/Crafter.Build-FixedVector.o
clang++ -std=c++26 Crafter.Build-Configuration.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Configuration_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Command.pcm -o ./build/Crafter.Build-Command.o
clang++ -std=c++26 Crafter.Build-ModuleFile.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-ModuleFile_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Shader.pcm -o ./build/Crafter.Build-Shader.o
clang++ -std=c++26 Crafter.Build-SourceFile.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-SourceFile_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Module.pcm -o ./build/Crafter.Build-Module.o
clang++ -std=c++26 Crafter.Build-Shader.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Shader_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Implementation.pcm -o ./build/Crafter.Build-Implementation.o
clang++ -std=c++26 Crafter.Build-Project.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Project_source.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Configuration.pcm -o ./build/Crafter.Build-Configuration.o
clang++ -std=c++26 main.cpp -fprebuilt-module-path=./build -c -o ./build/main.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Project.pcm -o ./build/Crafter.Build-Project.o
clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Test.pcm -o ./build/Crafter.Build-Test.o
clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build.pcm -o ./build/Crafter.Build.o
clang++ -std=c++26 ./build/Crafter.Build-Bounce.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Bounce.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Command.cpp -o ./build/Crafter.Build-Command_impl.o
clang++ -std=c++26 ./build/Crafter.Build-Project.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Project.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Test.cpp -o ./build/Crafter.Build-Test_impl.o
clang++ -std=c++26 ./build/Crafter.Build-Configuration.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Configuration.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Implementation.cpp -o ./build/Crafter.Build-Implementation_impl.o
clang++ -std=c++26 ./build/Crafter.Build-ModuleFile.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-ModuleFile.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Shader.cpp -o ./build/Crafter.Build-Shader_impl.o
clang++ -std=c++26 ./build/Crafter.Build-SourceFile.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-SourceFile.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Module.cpp -o ./build/Crafter.Build-Module_impl.o
clang++ -std=c++26 ./build/Crafter.Build-Shader.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Shader.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Configuration.cpp -o ./build/Crafter.Build-Configuration_impl.o
clang++ -std=c++26 ./build/Crafter.Build-Dependency.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Dependency.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Project.cpp -o ./build/Crafter.Build-Project_impl.o
clang++ -std=c++26 ./build/Crafter.Build.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build.o clang++ -std=c++26 -O3 -fprebuilt-module-path=./build -c ./implementations/main.cpp -o ./build/main.o
clang++ ./build/main.o ./build/Crafter.Build-ModuleFile_source.o ./build/Crafter.Build-Bounce.o ./build/Crafter.Build-ModuleFile.o ./build/Crafter.Build-SourceFile_source.o ./build/Crafter.Build-SourceFile.o ./build/Crafter.Build-Shader_source.o ./build/Crafter.Build-Shader.o ./build/Crafter.Build.o ./build/Crafter.Build-Configuration.o ./build/Crafter.Build-Configuration_source.o ./build/Crafter.Build-Project.o ./build/Crafter.Build-Project_source.o ./build/Crafter.Build-Dependency.o ./build/Crafter.Build-Dependency_source.o -O3 -o ./bin/crafter-build -L/usr/local/lib -L/usr/lib/ -lvulkan -lMachineIndependent -lOSDependent -lGenericCodeGen -lglslang -lglslang-default-resource-limits -lSPIRV -lSPVRemapper -ltbb -fuse-ld=lld
clang++ -std=c++26 -O3 -L/usr/local/lib -L/usr/lib/ -lvulkan -lMachineIndependent -lOSDependent -lGenericCodeGen -lglslang -lglslang-default-resource-limits -lSPIRV -ltbb -fuse-ld=lld ./build/Crafter.Build-Command.o ./build/Crafter.Build-FixedVector.o ./build/Crafter.Build-Shader.o ./build/Crafter.Build-Module.o ./build/Crafter.Build-Configuration.o ./build/Crafter.Build-Project.o ./build/Crafter.Build.o ./build/Crafter.Build-Command_impl.o ./build/Crafter.Build-Shader_impl.o ./build/Crafter.Build-Module_impl.o ./build/Crafter.Build-Configuration_impl.o ./build/Crafter.Build-Project_impl.o ./build/Crafter.Build-Implementation.o ./build/Crafter.Build-Implementation_impl.o ./build/Crafter.Build-Test_impl.o ./build/Crafter.Build-Test.o ./build/main.o -o ./bin/crafter-build
rm -rf build rm -rf build
# clang++ -std=c++26 implementations/Crafter.Build-Command.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Command_impl.pcm
# clang++ -std=c++26 implementations/Crafter.Build-Shader.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Shader_impl.pcm
# clang++ -std=c++26 implementations/Crafter.Build-Module.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Module_impl.pcm
# clang++ -std=c++26 implementations/Crafter.Build-Configuration.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Configuration_impl.pcm
# clang++ -std=c++26 implementations/Crafter.Build-Project.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Project_impl.pcm
# clang++ -std=c++26 implementations/Crafter.Build.cppm -stdlib=libc++ --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build_impl.pcm
# clang++ -std=c++26 Crafter.Build-Shader.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Shader.pcm
# clang++ -std=c++26 Crafter.Build-Dependency.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Dependency.pcm
# clang++ -std=c++26 Crafter.Build-Configuration.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Configuration.pcm
# clang++ -std=c++26 Crafter.Build-ModuleFile.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-ModuleFile.pcm
# clang++ -std=c++26 Crafter.Build-SourceFile.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-SourceFile.pcm
# clang++ -std=c++26 Crafter.Build-Project.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Project.pcm
# clang++ -std=c++26 Crafter.Build.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build.pcm
# clang++ -std=c++26 Crafter.Build-Dependency.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Dependency_source.o
# clang++ -std=c++26 Crafter.Build-Configuration.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Configuration_source.o
# clang++ -std=c++26 Crafter.Build-ModuleFile.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-ModuleFile_source.o
# clang++ -std=c++26 Crafter.Build-SourceFile.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-SourceFile_source.o
# clang++ -std=c++26 Crafter.Build-Shader.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Shader_source.o
# clang++ -std=c++26 Crafter.Build-Project.cpp -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Project_source.o
# clang++ -std=c++26 main.cpp -fprebuilt-module-path=./build -c -o ./build/main.o
# clang++ -std=c++26 ./build/Crafter.Build-Bounce.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Bounce.o
# clang++ -std=c++26 ./build/Crafter.Build-Project.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Project.o
# clang++ -std=c++26 ./build/Crafter.Build-Configuration.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Configuration.o
# clang++ -std=c++26 ./build/Crafter.Build-ModuleFile.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-ModuleFile.o
# clang++ -std=c++26 ./build/Crafter.Build-SourceFile.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-SourceFile.o
# clang++ -std=c++26 ./build/Crafter.Build-Shader.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Shader.o
# clang++ -std=c++26 ./build/Crafter.Build-Dependency.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build-Dependency.o
# clang++ -std=c++26 ./build/Crafter.Build.pcm -fprebuilt-module-path=./build -c -O3 -o ./build/Crafter.Build.o
# clang++ ./build/main.o ./build/Crafter.Build-ModuleFile_source.o ./build/Crafter.Build-Bounce.o ./build/Crafter.Build-ModuleFile.o ./build/Crafter.Build-SourceFile_source.o ./build/Crafter.Build-SourceFile.o ./build/Crafter.Build-Shader_source.o ./build/Crafter.Build-Shader.o ./build/Crafter.Build.o ./build/Crafter.Build-Configuration.o ./build/Crafter.Build-Configuration_source.o ./build/Crafter.Build-Project.o ./build/Crafter.Build-Project_source.o ./build/Crafter.Build-Dependency.o ./build/Crafter.Build-Dependency_source.o -O3 -o ./bin/crafter-build -L/usr/local/lib -L/usr/lib/ -lvulkan -lMachineIndependent -lOSDependent -lGenericCodeGen -lglslang -lglslang-default-resource-limits -lSPIRV -ltbb -fuse-ld=lld
# rm -rf build

View file

@ -0,0 +1,90 @@
/*
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 <stdio.h>
module Crafter.Build:Command_impl;
import :Command;
import std;
namespace Crafter {
std::string RunCommand(const std::string_view cmd) {
std::array<char, 128> buffer;
std::string result;
std::string with = std::string(cmd) + " 2>&1";
// Open pipe to file
FILE* pipe = popen(with.c_str(), "r");
if (!pipe) throw std::runtime_error("popen() failed!");
// Read till end of process:
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
result += buffer.data();
}
// Close pipe
pclose(pipe);
return result;
}
void RunCommandIgnore(const std::string_view cmd) {
std::string with = std::string(cmd) + " > /dev/null 2>&1";
FILE* pipe = popen(with.c_str(), "r");
if (!pipe) throw std::runtime_error("popen() failed!");
pclose(pipe);
}
CompileException::CompileException(std::vector<CompileError>&& errors) : errors(std::move(errors)) {
for(CompileError error : errors) {
message += std::format("File: {}:{}\nMessage: {}\nCode: {}", error.filename, error.line, error.message, error.code);
}
};
const char* CompileException::what() const noexcept {
return message.c_str();
}
void RunClang(const std::string_view cmd) {
std::cout << cmd << std::endl;
std::string result = RunCommand(cmd);
std::cout << result << std::endl;
// std::vector<CompileError> errors;
// std::regex error_regex(R"((/[^:]+\.cpp):(\d+):\d+: error: (.*)\n\s*[0-9| ]*\s*(.*))");
// std::smatch match;
// while (std::regex_search(result, match, error_regex)) {
// CompileError error;
// error.filename = match[1].str();
// error.line = std::stoi(match[2].str());
// error.message = match[3].str();
// error.code = match[4].str();
// errors.push_back(error);
// result = match.suffix().str();
// }
if(result != "") {
// if(errors.size() != 0) {
// throw CompileException(std::move(errors));
// } else {
throw std::runtime_error(result);
//}
}
}
}

View file

@ -0,0 +1,241 @@
/*
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 <glslang/Public/ShaderLang.h>
module Crafter.Build:Configuration_impl;
import :Configuration;
import std;
//import :Dependency;
import :Shader;
import :Module;
namespace fs = std::filesystem;
namespace Crafter {
Configuration::Configuration(std::string&& name) : name(std::move(name)) {
}
Configuration::Configuration(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir) {
SetDataFromJson(configs, config, workingDir);
}
void Configuration::SetDataFromJson(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir) {
if(config.contains("extends")) {
const std::vector<std::string> extends = config["extends"].get<std::vector<std::string>>();
for(const std::string& extendName : extends) {
for (auto it : configs) {
if(it["name"].get<std::string>() == extendName) {
SetDataFromJson(configs, it, workingDir);
}
}
}
}
if(config.contains("name")) {
name = config["name"].get<std::string>();
} else {
throw std::runtime_error("Invalid config json, name field is missing");
}
if(config.contains("standard")) {
standard = config["standard"].get<std::string>();
}
if(config.contains("target")) {
target = config["target"].get<std::string>();
}
if(config.contains("debug")) {
debug = config["debug"].get<bool>();
}
if(config.contains("type")) {
std::string typeString = config["type"].get<std::string>();
if(typeString == "executable") {
type = CRAFTER_CONFIGURATION_TYPE_EXECUTABLE;
} else if(typeString == "library") {
type = CRAFTER_CONFIGURATION_TYPE_LIBRARY;
} else if(typeString == "shared-library") {
type = CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY;
} else {
throw std::invalid_argument("Unknown type: " + typeString);
}
}
if(config.contains("march")) {
march = config["march"].get<std::string>();
}
if(config.contains("libs")) {
for (auto it : config["libs"]) {
libs.push_back(it.get<std::string>());
}
}
if(config.contains("interfaces")) {
const std::vector<std::string> tempModuleFiles = config["interfaces"].get<std::vector<std::string>>();
std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>> tempModulePaths = std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>>(tempModuleFiles.size());
for(std::uint_fast32_t i = 0; i < tempModuleFiles.size(); i++){
const std::filesystem::path file = workingDir / (tempModuleFiles[i]+".cppm");
std::ifstream t(file);
std::stringstream buffer;
buffer << t.rdbuf();
std::string fileContent = buffer.str();
fileContent = std::regex_replace(fileContent, std::regex(R"(//[^\n]*)"), "");
fileContent = std::regex_replace(fileContent, std::regex(R"(/\*.*?\*/)"), "");
tempModulePaths[i] = {file, fileContent, nullptr, nullptr};
}
std::erase_if(tempModulePaths, [this](std::tuple<fs::path, std::string, ModulePartition*, Module*>& file) {
std::smatch match;
if (std::regex_search(std::get<1>(file), match, std::regex(R"(export module ([a-zA-Z0-9_\.\-]+);)"))) {
std::get<0>(file).replace_extension("");
interfaces.push_back(std::make_unique<Module>(std::move(match[1].str()), std::move(std::get<0>(file))));
return true;
} else {
return false;
}
});
for(uint_fast32_t i = 0; i < tempModulePaths.size(); i++) {
std::smatch match;
if (std::regex_search(std::get<1>(tempModulePaths[i]), match, std::regex(R"(export module ([a-zA-Z_0-9\.\-]+):([a-zA-Z_0-9\.\-]+);)"))) {
for(const std::unique_ptr<Module>& modulee : interfaces) {
if(modulee->name == match[1]) {
std::string name = match[2].str();
fs::path pthCpy = std::get<0>(tempModulePaths[i]);
pthCpy.replace_extension("");
std::unique_ptr<ModulePartition> partition = std::make_unique<ModulePartition>(std::move(name), std::move(pthCpy));
std::get<2>(tempModulePaths[i]) = partition.get();
modulee->partitions.push_back(std::move(partition));
std::get<3>(tempModulePaths[i]) = modulee.get();
goto next;
}
}
throw std::runtime_error(std::format("Module {} not found, referenced in {}", match[1].str(), std::get<0>(tempModulePaths[i]).string()));
} else {
throw std::runtime_error(std::format("No module declaration found in {}", std::get<0>(tempModulePaths[i]).string()));
}
next:;
}
for(std::tuple<fs::path, std::string, ModulePartition*, Module*>& file : tempModulePaths) {
std::regex pattern(R"(import :([a-zA-Z_\-0-9\.]+);)");
std::sregex_iterator currentMatch(std::get<1>(file).begin(), std::get<1>(file).end(), pattern);
std::sregex_iterator lastMatch;
while (currentMatch != lastMatch) {
std::smatch match = *currentMatch;
for(std::unique_ptr<ModulePartition>& partition2 : std::get<3>(file)->partitions) {
if(partition2->name == match[1]) {
std::get<2>(file)->partitionDependencies.push_back(partition2.get());
goto next2;
}
}
throw std::runtime_error(std::format("imported partition {}:{} not found, referenced in {}", std::get<3>(file)->name, match[1].str(), std::get<0>(file).string()));
next2: ++currentMatch;
}
}
}
if(config.contains("implementations")) {
const std::vector<std::string> tempFiles = config["implementations"].get<std::vector<std::string>>();
for(const std::string& tempFile : tempFiles) {
const std::filesystem::path file = workingDir / (tempFile + ".cpp");
std::ifstream t(file);
std::stringstream buffer;
buffer << t.rdbuf();
std::string fileContent = buffer.str();
fileContent = std::regex_replace(fileContent, std::regex(R"(//[^\n]*)"), "");
fileContent = std::regex_replace(fileContent, std::regex(R"(/\*.*?\*/)"), "");
std::smatch match;
fs::path fileCopy = file;
fileCopy.replace_extension("");
Implementation& implementation = implementations.emplace_back(std::move(fileCopy));
if (std::regex_search(fileContent, match, std::regex(R"(module ([a-zA-Z0-9_\.\-]+))"))) {
for(const std::unique_ptr<Module>& interface : interfaces) {
if(interface->name == match[1]) {
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 match2 = *currentMatch;
for(const std::unique_ptr<ModulePartition>& partition : interface->partitions) {
if(partition->name == match2[1]) {
implementation.partitionDependencies.push_back(partition.get());
goto next3;
}
}
throw std::runtime_error(std::format("imported partition {}:{} not found, referenced in {}", match[1].str(), match2[1].str(), file.string()));
next3: ++currentMatch;
}
goto next4;
}
}
throw std::runtime_error(std::format("Module {} not found not found, referenced in {}", match[1].str(), file.string()));
next4:;
} else {
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;
for(const std::unique_ptr<Module>& interface : interfaces) {
if(interface->name == match[1]) {
implementation.moduleDependencies.push_back(interface.get());
goto next5;
}
}
//throw std::runtime_error(std::format("imported module {} not found, referenced in {}", match[1].str(), file.string()));
next5: ++currentMatch;
}
}
}
}
if(config.contains("shaders")) {
for (auto it : config["shaders"]) {
shaders.emplace_back(workingDir / it["path"].get<std::string>(), it["entrypoint"].get<std::string>(), static_cast<EShLanguage>(it["type"].get<std::uint32_t>()));
}
}
if(config.contains("defines")) {
for (auto it : config["defines"]) {
defines.emplace_back(it["name"].get<std::string>(), it["value"].get<std::string>());
}
}
if(config.contains("additional_files")) {
const std::vector<std::string> tempAdditionalFiles = config["additional_files"].get<std::vector<std::string>>();
for (const std::string& file : tempAdditionalFiles) {
additionalFiles.push_back( workingDir / file);
}
}
if(config.contains("build_dir")) {
buildDir = workingDir / config["build_dir"].get<std::string>();
}
if(config.contains("output_dir")) {
outputDir = workingDir / config["output_dir"].get<std::string>();
}
if(config.contains("dependencies")) {
for (auto it : config["dependencies"]) {
std::string commit;
std::string branch;
if(it.contains("commit")){
commit = it["commit"].get<std::string>();
}
if(it.contains("branch")){
branch = it["branch"].get<std::string>();
}
dependencies.emplace_back(it["path"].get<std::string>(), it["configuration"].get<std::string>(), std::move(commit), std::move(branch));
}
}
}
}

View file

@ -0,0 +1,61 @@
/*
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 Crafter.Build:Implementation_impl;
import std;
import :Implementation;
import :Module;
import :Command;
namespace fs = std::filesystem;
namespace Crafter {
Implementation::Implementation(fs::path&& path) : path(std::move(path)) {
}
bool Implementation::Check(const fs::path& buildDir, const fs::path& pcmDir) const {
if(fs::exists((buildDir/path.filename()).string()+"_impl.o") && fs::last_write_time(path.string()+".cpp") < fs::last_write_time((buildDir/path.filename()).string()+"_impl.o")) {
for(ModulePartition* dependency : partitionDependencies) {
if(dependency->Check(pcmDir)) {
return true;
}
}
for(Module* dependency : moduleDependencies) {
if(dependency->Check(pcmDir)) {
return true;
}
}
return false;
} else {
return true;
}
}
void Implementation::Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir) const {
for(ModulePartition* dependency : partitionDependencies) {
if(!dependency->compiled.load()) {
dependency->compiled.wait(false);
}
}
for(Module* dependency : moduleDependencies) {
if(!dependency->compiled.load()) {
dependency->compiled.wait(false);
}
}
RunClang(std::format("{} {}.cpp -fprebuilt-module-path={} -fprebuilt-module-path={} -c -o {}_impl.o", clang, path.string(), pcmDir.string(), buildDir.parent_path().string(), (buildDir/path.filename()).string()));
}
}

View file

@ -0,0 +1,139 @@
/*
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 Crafter.Build:Module_impl;
import std;
import :Module;
import :Command;
namespace fs = std::filesystem;
namespace Crafter {
ModulePartition::ModulePartition(std::string&& name, fs::path&& path) : name(std::move(name)), path(std::move(path)), compiled(false) {}
bool ModulePartition::Check(const fs::path& pcmDir) {
if(!checked) {
if(fs::exists((pcmDir/path.filename()).generic_string()+".pcm") && fs::last_write_time(path.generic_string()+".cppm") < fs::last_write_time((pcmDir/path.filename()).generic_string()+".pcm")) {
for(ModulePartition* dependency : partitionDependencies) {
if(dependency->Check(pcmDir)) {
needsRecompiling = true;
checked = true;
return true;
}
}
for(Module* dependency : moduleDependencies) {
if(dependency->Check(pcmDir)) {
needsRecompiling = true;
checked = true;
return true;
}
}
needsRecompiling = false;
compiled.store(true);
checked = true;
return false;
} else {
needsRecompiling = true;
checked = true;
return true;
}
} else {
return needsRecompiling;
}
}
void ModulePartition::Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir) {
for(ModulePartition* dependency : partitionDependencies) {
if(!dependency->compiled.load()) {
//std::cout << std::format("{} is waiting on {}", name, dependency->name) << std::endl;
dependency->compiled.wait(false);
}
}
RunClang(std::format("{} {}.cppm --precompile -fprebuilt-module-path={} -fprebuilt-module-path={} -o {}.pcm", clang, path.string(), pcmDir.string(), buildDir.parent_path().string(), (pcmDir/path.filename()).string()));
RunClang(std::format("{} {}.pcm -fprebuilt-module-path={} -fprebuilt-module-path={} -c -o {}.o", clang, (pcmDir/path.filename()).string(), pcmDir.string(), buildDir.parent_path().string(), (buildDir/path.filename()).string()));
compiled.store(true);
compiled.notify_all();
}
Module::Module(std::string&& name, fs::path&& path, std::vector<std::unique_ptr<ModulePartition>>&& partitions) : name(std::move(name)), path(std::move(path)), partitions(std::move(partitions)), compiled(false) {}
Module::Module(std::string&& name, fs::path&& path) : name(std::move(name)), path(std::move(path)), compiled(false) {}
Module::Module(fs::path&& path) : path(std::move(path)), compiled(false) {
std::ifstream t(this->path);
std::stringstream buffer;
buffer << t.rdbuf();
std::string fileContent = buffer.str();
fileContent = std::regex_replace(fileContent, std::regex(R"(//[^\n]*)"), "");
fileContent = std::regex_replace(fileContent, std::regex(R"(/\*.*?\*/)"), "");
std::smatch match;
if (std::regex_search(fileContent, match, std::regex(R"(export module ([a-zA-Z0-9_.-]+);)"))) {
name = match[1].str();
} else {
throw std::runtime_error(std::format("No module declaration found in {}", this->path.string()));
}
}
bool Module::Check(const fs::path& pcmDir) {
if(!checked) {
if(fs::exists((pcmDir/path.filename()).generic_string()+".pcm") && fs::last_write_time(path.generic_string()+".cppm") < fs::last_write_time((pcmDir/path.filename()).generic_string()+".pcm")) {
for(std::unique_ptr<ModulePartition>& partition : partitions) {
if(partition->Check(pcmDir)) {
needsRecompiling = true;
checked = true;
return true;
}
}
needsRecompiling = false;
compiled.store(true);
checked = true;
return false;
} else {
for(std::unique_ptr<ModulePartition>& partition : partitions) {
partition->Check(pcmDir);
}
needsRecompiling = true;
checked = true;
return true;
}
} else {
return needsRecompiling;
}
}
void Module::Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir) {
std::vector<std::thread> threads;
for(std::unique_ptr<ModulePartition>& partition : partitions) {
if(partition->needsRecompiling) {
threads.emplace_back([&partition, clang, &pcmDir, &buildDir](){
partition->Compile(clang, pcmDir, buildDir);
});
}
}
for(std::thread& thread : threads){
thread.join();
}
RunClang(std::format("{} {}.cppm --precompile -fprebuilt-module-path={} -fprebuilt-module-path={} -o {}.pcm", clang, path.string(), pcmDir.string(), buildDir.parent_path().string(), (pcmDir/path.filename()).string()));
RunClang(std::format("{} {}.pcm -fprebuilt-module-path={} -fprebuilt-module-path={} -c -o {}.o", clang, (pcmDir/path.filename()).string(), pcmDir.string(), buildDir.parent_path().string(), (buildDir/path.filename()).string()));
compiled.store(true);
compiled.notify_all();
}
}

View file

@ -0,0 +1,384 @@
/*
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;
}

View file

@ -0,0 +1,100 @@
/*
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 <glslang/SPIRV/GlslangToSpv.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/Public/ResourceLimits.h>
#include "../lib/DirStackFileIncluder.h"
module Crafter.Build:Shader_impl;
import :Shader;
import std;
namespace fs = std::filesystem;
namespace Crafter {
Shader::Shader(fs::path&& path, std::string&& entrypoint, EShLanguage type) : path(std::move(path)), entrypoint(std::move(entrypoint)), type(type) {
}
bool Shader::Check(const fs::path& outputDir) const {
return fs::exists((outputDir/path.filename()).generic_string()+".spv") && fs::last_write_time(path.generic_string()+".glsl") < fs::last_write_time((outputDir/path.filename()).generic_string()+".spv");
}
void Shader::Compile(const fs::path& outputDir) const {
glslang::InitializeProcess();
EShMessages messages = static_cast<EShMessages>(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules);
std::ifstream fileStream(path, std::ios::in | std::ios::binary);
if (!fileStream) {
throw std::ios_base::failure("Failed to open file: " + path.string());
}
std::ostringstream contents;
contents << fileStream.rdbuf();
std::string src = contents.str();
const char *file_name_list[1] = {""};
const char *shader_source = reinterpret_cast<const char *>(src.data());
glslang::TShader shader(type);
shader.setStringsWithLengthsAndNames(&shader_source, nullptr, file_name_list, 1);
shader.setEntryPoint(entrypoint.c_str());
shader.setSourceEntryPoint(entrypoint.c_str());
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4);
DirStackFileIncluder includeDir;
includeDir.pushExternalLocalDirectory(path.parent_path().generic_string());
std::string info_log;
if (!shader.parse(GetDefaultResources(), 100, false, messages, includeDir))
{
info_log = std::string(shader.getInfoLog()) + std::string(shader.getInfoDebugLog());
}
// Add shader to new program object.
glslang::TProgram program;
program.addShader(&shader);
// Link program.
if (!program.link(messages))
{
info_log = std::string(program.getInfoLog()) + std::string(program.getInfoDebugLog());
}
// Save any info log that was generated.
if (shader.getInfoLog())
{
info_log += std::string(shader.getInfoLog()) + std::string(shader.getInfoDebugLog());
}
if (program.getInfoLog())
{
info_log += std::string(program.getInfoLog()) + std::string(program.getInfoDebugLog());
}
glslang::TIntermediate* intermediate = program.getIntermediate(type);
if (!intermediate)
{
info_log += "Failed to get shared intermediate code.";
}
spv::SpvBuildLogger logger;
std::vector<std::uint32_t> spirv;
std::cout << info_log;
glslang::GlslangToSpv(*intermediate, spirv, &logger);
std::cout << logger.getAllMessages();
glslang::FinalizeProcess();
fs::path filename = path.filename().replace_extension("spv");
std::ofstream file(outputDir/filename, std::ios::binary);
file.write(reinterpret_cast<const char*>(spirv.data()), spirv.size() * sizeof(std::uint32_t));
}
}

View file

@ -0,0 +1,34 @@
/*
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"
module Crafter.Build:Test_impl;
import :Test;
import std;
namespace fs = std::filesystem;
namespace Crafter {
Test::Test(Configuration&& config) : config(std::move(config)) {
config.type = CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY;
}
Test::Test(const nlohmann::json& configs, const nlohmann::json& config, const fs::path& workingDir): config(configs, config, workingDir) {
this->config.type = CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY;
}
}

105
implementations/main.cpp Normal file
View file

@ -0,0 +1,105 @@
/*
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
*/
#include <stdlib.h>
import Crafter.Build;
import std;
using namespace Crafter;
namespace fs = std::filesystem;
int main(int argc, char* argv[]) {
if(std::string(argv[1]) == "--help") {
std::println("--help\tDisplays this help message.\n-c The name of the configuration to build.\n-p The path to the project file. defualts to project.json\n-o Overrides the output folder.\n-r Runs the executable after building.");
return 0;
}
if(argc < 2 && std::string(argv[1]) == "build") {
std::println("Too little arguments provided, use --help for help");
return 0;
}
fs::path filepath = "project.json";
std::string configuration;
std::string outputDir;
std::string command = std::string(argv[1]);
std::string argument;
if(argc > 2) {
argument = std::string(argv[2]);
}
bool run = false;
for (std::uint_fast32_t i = 3; i < argc; i++) {
std::string arg = std::string(argv[i]);
if(arg == "-r"){
run = true;
} else if(arg == "-o"){
outputDir = argv[++i];
} else if(arg == "-p"){
filepath = fs::path(argv[++i]);
} else{
std::println("Unkown argument: {}", argv[i]);
return 1;
}
}
fs::path projectPath;
if(filepath.is_relative()){
projectPath = fs::current_path()/filepath;
}else{
projectPath = filepath;
}
Project project = Project::LoadFromJSON(projectPath);
if(command == "build") {
if(outputDir.empty()){
project.Build(argument);
} else{
project.Build(argument, fs::path(outputDir));
}
if(run){
for(Configuration& config : project.configurations) {
if(config.name == configuration) {
if(config.debug) {
system(std::format("cd {} && ./{}", (projectPath/fs::path(config.outputDir)).generic_string(), project.name).c_str());
} else {
system(std::format("cd {} && lldb -o run {}", (projectPath/fs::path(config.outputDir)).generic_string(), project.name).c_str());
}
return 0;
}
}
}
} else if(command == "test") {
if(argument.empty()) {
std::vector<TestResult> results = project.RunTests();
for(const TestResult& result : results) {
if(result.message.empty()) {
std::cout << std::format("✅ {}", result.name) << std::endl;
} else {
std::cout << std::format("❌ {}\t{}", result.name, result.message) << std::endl;
}
}
} else {
TestResult result = project.RunTest(argument);
if(result.message.empty()) {
std::cout << std::format("✅ {}", result.name) << std::endl;
} else {
std::cout << std::format("❌ {}\t{}", result.name, result.message) << std::endl;
}
}
} else {
std::println("Unkown command: {}", command);
}
}

View file

@ -0,0 +1,42 @@
/*
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
*/
export module Crafter.Build:Command;
import std;
namespace Crafter {
export struct CompileError {
std::string filename;
std::uint_fast32_t line;
std::string message;
std::string code;
};
export class CompileException : public std::exception {
public:
std::string message;
std::vector<CompileError> errors;
CompileException(std::vector<CompileError>&& errors);
const char* what() const noexcept override;
};
export std::string RunCommand(const std::string_view cmd);
export void RunCommandIgnore(const std::string_view cmd);
export void RunClang(const std::string_view cmd);
}

View file

@ -0,0 +1,72 @@
/*
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"
export module Crafter.Build:Configuration;
import std;
import :Implementation;
import :Shader;
import :Module;
namespace fs = std::filesystem;
namespace Crafter {
export struct Define {
std::string name;
std::string value;
Define(std::string&& name, std::string&& value): name(std::move(name)), value(std::move(value)) { }
};
export class Dependency {
public:
std::string path;
std::string configuration;
std::string commit;
std::string branch;
Dependency(std::string&& path, std::string&& configuration, std::string&& commit, std::string&& branch): path(std::move(path)), configuration(std::move(configuration)), commit(std::move(commit)), branch(std::move(branch)) { }
};;
export enum ConfigurationType {
CRAFTER_CONFIGURATION_TYPE_EXECUTABLE,
CRAFTER_CONFIGURATION_TYPE_LIBRARY,
CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY,
};
export class Configuration {
public:
std::string name;
std::string standard;
std::vector<std::unique_ptr<Module>> interfaces;
std::vector<Implementation> implementations;
std::vector<Dependency> dependencies;
std::vector<Shader> shaders;
std::vector<fs::path> additionalFiles;
std::vector<Define> defines;
fs::path buildDir;
fs::path outputDir;
ConfigurationType type;
std::string target;
std::string march;
bool debug;
std::vector<std::string> libs;
Configuration(std::string&& name);
Configuration(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir);
void SetDataFromJson(const nlohmann::json& configs, const nlohmann::json& config, fs::path workingDir);
};
}

View file

@ -0,0 +1,82 @@
/*
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
*/
export module Crafter.Build:FixedVector;
import std;
namespace fs = std::filesystem;
namespace Crafter {
export template <typename T>
class FixedVector {
public:
using value_type = T;
using iterator = T*;
using const_iterator = const T*;
T* values;
std::uint_fast32_t size_;
FixedVector() {}
FixedVector(std::uint_fast32_t size): values(reinterpet_cast<T*>(operator new[](size * sizeof(T)))), size_(size) {}
void Set(std::uint_fast32_t size) {
#ifdef CRAFTER_BUILD_CONFIGURATION_DEBUG
if(this->size_ != 0) {
throw std::runtime_error("FixedVector already set!");
}
#endif
values = reinterpet_cast<T*>(operator new[](size * sizeof(T)));
this->size_ = size;
}
~FixedVector() {
delete[] values;
}
FixedVector(const FixedVector&) = delete;
FixedVector& operator=(const FixedVector&) = delete;
FixedVector(FixedVector&& other) noexcept : values(other.values), size_(other.size_) {
other.values = nullptr;
other.size_ = 0;
}
FixedVector& operator=(FixedVector&& other) noexcept {
if (this != &other) {
delete[] values;
values = other.values;
size_ = other.size_;
other.values = nullptr;
other.size_ = 0;
}
return *this;
}
T& operator[](std::uint_fast32_t index) { return values[index]; }
const T& operator[](std::uint_fast32_t index) const { return values[index]; }
std::uint_fast32_t size() const { return size_; }
iterator begin() { return values; }
const_iterator begin() const { return values; }
const_iterator cbegin() const { return values; }
iterator end() { return values + size_; }
const_iterator end() const { return values + size_; }
const_iterator cend() const { return values + size_; }
};
}

View file

@ -0,0 +1,36 @@
/*
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
*/
export module Crafter.Build:Implementation;
import std;
namespace fs = std::filesystem;
namespace Crafter {
export class Module;
export class ModulePartition;
export class Implementation {
public:
std::vector<Module*> moduleDependencies;
std::vector<ModulePartition*> partitionDependencies;
fs::path path;
Implementation(fs::path&& path);
bool Check(const fs::path& buildDir, const fs::path& pcmDir) const;
void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir) const;
};
}

View file

@ -0,0 +1,54 @@
/*
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
*/
export module Crafter.Build:Module;
import std;
namespace fs = std::filesystem;
namespace Crafter {
export class Module;
export class ModulePartition {
public:
std::vector<Module*> moduleDependencies;
std::vector<ModulePartition*> partitionDependencies;
std::atomic<bool> compiled;
bool needsRecompiling;
bool checked = false;
std::string name;
fs::path path;
ModulePartition(std::string&& name, fs::path&& path);
bool Check(const fs::path& pcmDir);
void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir);
};
export class Module {
public:
std::atomic<bool> compiled;
bool needsRecompiling;
bool checked = false;
std::vector<std::unique_ptr<ModulePartition>> partitions;
std::string name;
fs::path path;
Module(fs::path&& path);
Module(std::string&& name, fs::path&& path);
Module(std::string&& name, fs::path&& path, std::vector<std::unique_ptr<ModulePartition>>&& partitions);
bool Check(const fs::path& pcmDir);
void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir);
};
}

View file

@ -0,0 +1,47 @@
/*
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
*/
export module Crafter.Build:Project;
import std;
import :Configuration;
import :Test;
namespace fs = std::filesystem;
namespace Crafter {
export class Project {
public:
std::string name;
fs::path path;
std::vector<Configuration> configurations;
std::vector<Test> tests;
Project(std::string&& name, fs::path&& path, std::vector<Configuration>&& configurations);
static Project LoadFromJSON(const fs::path& path);
Configuration& Build(std::string_view configuration);
Configuration& Build(std::string_view configuration, const fs::path& outputDir);
Configuration& Build(std::string_view configuration, const fs::path& outputDir, const fs::path& binDir);
Configuration& Build(std::string_view configuration, const fs::path& outputDir, const fs::path& binDir, const fs::path& builDir);
void Build(Configuration& configuration) const;
void Build(Configuration& configuration, const fs::path& outputDir) const;
void Build(Configuration& configuration, const fs::path& outputDir, const fs::path& binDir) const;
void Build(Configuration& configuration, const fs::path& outputDir, const fs::path& binDir, const fs::path& builDir) const;
TestResult RunTest(const std::string_view test);
TestResult RunTest(Test& test);
std::vector<TestResult> RunTests();
};
}

View file

@ -1,14 +1,13 @@
/* /*
Crafter.Build Crafter® Build
Copyright (C) 2025 Catcrafts® Copyright (C) 2025 Catcrafts®
Catcrafts.net Catcrafts.net
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License version 3.0 as published by the Free Software Foundation;
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, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
@ -19,19 +18,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
module; module;
#include <string> #include <glslang/SPIRV/GlslangToSpv.h>
#include <filesystem>
#include <glslang/Public/ShaderLang.h> #include <glslang/Public/ShaderLang.h>
#include <glslang/Public/ResourceLimits.h>
#include "../lib/DirStackFileIncluder.h"
export module Crafter.Build:Shader; export module Crafter.Build:Shader;
import std;
namespace fs = std::filesystem; namespace fs = std::filesystem;
export namespace Crafter::Build { namespace Crafter {
class Shader { export class Shader {
public: public:
fs::path path; fs::path path;
std::string entrypoint; std::string entrypoint;
EShLanguage type; EShLanguage type;
Shader(fs::path path, std::string entrypoint, EShLanguage type); Shader(fs::path&& path, std::string&& entrypoint, EShLanguage type);
void Compile(fs::path outputDir); bool Check(const fs::path& outputDir) const;
void Compile(const fs::path& outputDir) const;
}; };
} }

View file

@ -1,14 +1,13 @@
/* /*
Crafter.Build Crafter® Build
Copyright (C) 2025 Catcrafts® Copyright (C) 2025 Catcrafts®
Catcrafts.net Catcrafts.net
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License version 3.0 as published by the Free Software Foundation;
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, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
@ -17,19 +16,24 @@ You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
module; module;
#include <string> #include "../lib/json.hpp"
export module Crafter.Build:Dependency; export module Crafter.Build:Test;
import std;
import :Configuration;
export namespace Crafter::Build { namespace fs = std::filesystem;
class Dependency {
public: namespace Crafter {
std::string path; export struct TestResult {
std::string configuration;
std::string commit;
std::string branch;
std::string name; std::string name;
Dependency(std::string path, std::string configuration, std::string commit, std::string branch); std::string message;
};
export class Test {
public:
Configuration config;
Test(Configuration&& name);
Test(const nlohmann::json& configs, const nlohmann::json& config, const fs::path& workingDir);
}; };
} }

View file

@ -1,14 +1,13 @@
/* /*
Crafter.Build Crafter® Build
Copyright (C) 2025 Catcrafts® Copyright (C) 2025 Catcrafts®
Catcrafts.net Catcrafts.net
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License version 3.0 as published by the Free Software Foundation;
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, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
@ -19,10 +18,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
export module Crafter.Build; export module Crafter.Build;
export import :Dependency; export import :Command;
export import :Project; export import :Project;
export import :Module;
export import :Configuration; export import :Configuration;
export import :ModuleFile;
export import :SourceFile;
export import :Shader; export import :Shader;
export import :Bounce; export import :Implementation;
export import :FixedVector;
export import :Test;

View file

@ -1,92 +0,0 @@
/*
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
*/
#include <cstdint>
#include <print>
#include <tuple>
#include <iostream>
#include "json.hpp"
import Crafter.Build;
using namespace Crafter::Build;
namespace fs = std::filesystem;
int main(int argc, char* argv[]) {
if(argc == 1) {
std::println("No arguments provided, use --help for help");
}
fs::path filepath = "project.json";
std::string configuration;
std::string outputDir;
bool run = false;
for (std::uint_fast32_t i = 1; i < argc; i++) {
std::string arg = std::string(argv[i]);
if(arg == "--help"){
std::println("--help\tDisplays this help message.\n-c The name of the configuration to build.\n-p The path to the project file. defualts to project.json\n-o Overrides the output folder.\n-r Runs the executable after building.");
return 0;
} else if(arg == "-c"){
configuration = argv[++i];
} else if(arg == "-r"){
run = true;
} else if(arg == "-o"){
outputDir = argv[++i];
} else if(arg == "-p"){
filepath = fs::path(argv[++i]);
} else{
std::println("Unkown argument: {}", argv[i]);
return 1;
}
}
fs::path projectPath;
if(filepath.is_relative()){
projectPath = fs::current_path()/filepath;
}else{
projectPath = filepath;
}
std::vector<ClangError> errors;
Project project = Project::LoadFromJSON(projectPath);
if(outputDir.empty()){
errors = std::get<1>(project.Build(configuration));
} else{
errors = std::get<1>(project.Build(configuration, fs::path(outputDir)));
}
if(errors.size() > 0){
for (const ClangError& error : errors) {
std::cout << "Filename: " << error.filename << std::endl;
std::cout << "Line: " << error.line_number << std::endl;
std::cout << "Error Message: " << error.error_message << std::endl;
std::cout << "Code: " << error.code << std::endl << std::endl;
}
return 0;
}
if(run){
for(Configuration& config : project.configurations) {
if(config.name == configuration) {
if(config.target == "x86_64-w64-mingw64" || config.target == "x86_64-w64-mingw32") {
project.name += ".exe";
}
system(std::format("cd {} && ./{}", (projectPath/fs::path(config.outputDir)).generic_string(), project.name).c_str());
return 0;
}
}
}
}

View file

@ -3,18 +3,20 @@
"configurations": [ "configurations": [
{ {
"name": "base", "name": "base",
"standard": "c++26", "interfaces": ["interfaces/Crafter.Build-Command", "interfaces/Crafter.Build-Configuration", "interfaces/Crafter.Build-Module", "interfaces/Crafter.Build-Project", "interfaces/Crafter.Build-Shader", "interfaces/Crafter.Build", "interfaces/Crafter.Build-Implementation", "interfaces/Crafter.Build-FixedVector", "interfaces/Crafter.Build-Test"],
"source_files": ["Crafter.Build-Configuration", "Crafter.Build-Project", "Crafter.Build-Dependency", "Crafter.Build-ModuleFile", "Crafter.Build-Shader", "Crafter.Build-SourceFile"], "implementations": ["implementations/Crafter.Build-Command", "implementations/Crafter.Build-Configuration", "implementations/Crafter.Build-Module", "implementations/Crafter.Build-Project", "implementations/Crafter.Build-Shader", "implementations/Crafter.Build-Implementation", "implementations/Crafter.Build-Test"],
"module_files": ["Crafter.Build-Dependency", "Crafter.Build-Configuration", "Crafter.Build-Project", "Crafter.Build-Shader", "Crafter.Build", "Crafter.Build-ModuleFile", "Crafter.Build-SourceFile", "Crafter.Build-Bounce"], "libs": ["vulkan", "MachineIndependent", "OSDependent", "GenericCodeGen", "glslang", "glslang-default-resource-limits", "SPIRV", "tbb"]
"build_dir": "build",
"output_dir": "bin",
"libs": ["vulkan", "MachineIndependent", "OSDependent", "GenericCodeGen", "glslang", "glslang-default-resource-limits", "SPIRV", "SPVRemapper", "tbb"]
}, },
{ {
"name": "executable", "name": "executable",
"extends": ["base"], "extends": ["base"],
"type":"executable", "type":"executable",
"source_files": ["main"] "implementations": ["implementations/main"]
},
{
"name": "executable-debug",
"extends": ["executable"],
"debug": true
}, },
{ {
"name": "lib", "name": "lib",
@ -22,26 +24,21 @@
"type":"library" "type":"library"
}, },
{ {
"name": "debug", "name": "lib-debug",
"extends": ["executable"],
"optimization_level": "0",
"debug": true
},
{
"name": "release",
"extends": ["executable"],
"optimization_level": "3"
},
{
"name": "debug-lib",
"extends": ["lib"], "extends": ["lib"],
"optimization_level": "0",
"debug": true "debug": true
}, }
],
"tests": [
{ {
"name": "release-lib", "name": "should-compile",
"extends": ["lib"], "implementations": ["tests/ShouldCompile/ShouldCompile"],
"optimization_level": "3" "dependencies": [
{
"path":"./project.json",
"configuration":"lib"
}
]
} }
] ]
} }

View file

@ -1,14 +1,13 @@
/* /*
Crafter.Build Crafter® Build
Copyright (C) 2025 Catcrafts® Copyright (C) 2025 Catcrafts®
Catcrafts.net Catcrafts.net
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License version 3.0 as published by the Free Software Foundation;
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, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
@ -17,12 +16,14 @@ You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
import Crafter.Build;
import std;
using namespace Crafter;
module; extern "C" {
#include <string> std::string* RunTest() {
module Crafter.Build; return nullptr;
using namespace Crafter::Build; }
Dependency::Dependency(std::string path, std::string configuration, std::string commit, std::string branch): path(path), configuration(configuration), commit(commit), branch(branch) {
} }