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:
```
lld
clang 18>
clang
git
libpqxx
lldb
onetbb
boost
glslang
```

View file

@ -1,31 +1,79 @@
mkdir build
mkdir bin
clang++ -std=c++26 Crafter.Build-Bounce.cppm --precompile -fprebuilt-module-path=./build -o ./build/Crafter.Build-Bounce.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
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" ./build/std.cppm
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 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 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-FixedVector.pcm -o ./build/Crafter.Build-FixedVector.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 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Shader.pcm -o ./build/Crafter.Build-Shader.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 -O3 -fprebuilt-module-path=./build -c ./build/Crafter.Build-Implementation.pcm -o ./build/Crafter.Build-Implementation.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 -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 ./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 -lSPVRemapper -ltbb -fuse-ld=lld
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 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Test.cpp -o ./build/Crafter.Build-Test_impl.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 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Shader.cpp -o ./build/Crafter.Build-Shader_impl.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 -O3 -fprebuilt-module-path=./build -c ./implementations/Crafter.Build-Configuration.cpp -o ./build/Crafter.Build-Configuration_impl.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 -O3 -fprebuilt-module-path=./build -c ./implementations/main.cpp -o ./build/main.o
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
# 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®
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.
License version 3.0 as published by the Free Software Foundation;
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
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
@ -19,19 +18,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <string>
#include <filesystem>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/Public/ResourceLimits.h>
#include "../lib/DirStackFileIncluder.h"
export module Crafter.Build:Shader;
import std;
namespace fs = std::filesystem;
export namespace Crafter::Build {
class Shader {
namespace Crafter {
export class Shader {
public:
fs::path path;
std::string entrypoint;
EShLanguage type;
Shader(fs::path path, std::string entrypoint, EShLanguage type);
void Compile(fs::path outputDir);
Shader(fs::path&& path, std::string&& entrypoint, EShLanguage type);
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®
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.
License version 3.0 as published by the Free Software Foundation;
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
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
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
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <string>
export module Crafter.Build:Dependency;
#include "../lib/json.hpp"
export module Crafter.Build:Test;
import std;
import :Configuration;
export namespace Crafter::Build {
class Dependency {
public:
std::string path;
std::string configuration;
std::string commit;
std::string branch;
namespace fs = std::filesystem;
namespace Crafter {
export struct TestResult {
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®
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.
License version 3.0 as published by the Free Software Foundation;
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
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
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 import :Dependency;
export import :Command;
export import :Project;
export import :Module;
export import :Configuration;
export import :ModuleFile;
export import :SourceFile;
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": [
{
"name": "base",
"standard": "c++26",
"source_files": ["Crafter.Build-Configuration", "Crafter.Build-Project", "Crafter.Build-Dependency", "Crafter.Build-ModuleFile", "Crafter.Build-Shader", "Crafter.Build-SourceFile"],
"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"],
"build_dir": "build",
"output_dir": "bin",
"libs": ["vulkan", "MachineIndependent", "OSDependent", "GenericCodeGen", "glslang", "glslang-default-resource-limits", "SPIRV", "SPVRemapper", "tbb"]
"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"],
"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"],
"libs": ["vulkan", "MachineIndependent", "OSDependent", "GenericCodeGen", "glslang", "glslang-default-resource-limits", "SPIRV", "tbb"]
},
{
"name": "executable",
"extends": ["base"],
"type":"executable",
"source_files": ["main"]
"implementations": ["implementations/main"]
},
{
"name": "executable-debug",
"extends": ["executable"],
"debug": true
},
{
"name": "lib",
@ -22,26 +24,21 @@
"type":"library"
},
{
"name": "debug",
"extends": ["executable"],
"optimization_level": "0",
"debug": true
},
{
"name": "release",
"extends": ["executable"],
"optimization_level": "3"
},
{
"name": "debug-lib",
"name": "lib-debug",
"extends": ["lib"],
"optimization_level": "0",
"debug": true
},
}
],
"tests": [
{
"name": "release-lib",
"extends": ["lib"],
"optimization_level": "3"
"name": "should-compile",
"implementations": ["tests/ShouldCompile/ShouldCompile"],
"dependencies": [
{
"path":"./project.json",
"configuration":"lib"
}
]
}
]
}

View file

@ -1,14 +1,13 @@
/*
Crafter.Build
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.
License version 3.0 as published by the Free Software Foundation;
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
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
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
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import Crafter.Build;
import std;
using namespace Crafter;
module;
#include <string>
module Crafter.Build;
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) {
extern "C" {
std::string* RunTest() {
return nullptr;
}
}