v2 nearly done
This commit is contained in:
parent
5e1fcd8590
commit
f13671b2be
24 changed files with 1467 additions and 314 deletions
|
|
@ -20,12 +20,173 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
export module Crafter.Build:Clang_impl;
|
||||
import std;
|
||||
import :Clang;
|
||||
import :Platform;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& depSet, std::mutex& depMutex) {
|
||||
fs::path pwd = std::filesystem::path cwd = std::filesystem::current_path();
|
||||
fs::path buildDir = pwd/"build";
|
||||
fs::path outputDir = pwd/"bin";
|
||||
|
||||
void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> implementations) {
|
||||
auto resolveImport = [this](const std::string& importName,
|
||||
std::vector<Module*>& localDeps,
|
||||
std::vector<std::pair<Module*, fs::path>>& externalDeps) -> bool {
|
||||
for(const std::unique_ptr<Module>& interface : this->interfaces) {
|
||||
if(interface->name == importName) {
|
||||
localDeps.push_back(interface.get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(Configuration* depCfg : this->dependencies) {
|
||||
for(const std::unique_ptr<Module>& depInterface : depCfg->interfaces) {
|
||||
if(depInterface->name == importName) {
|
||||
fs::path depPcmPath = (depCfg->PcmDir() / depInterface->path.filename()).string() + ".pcm";
|
||||
externalDeps.emplace_back(depInterface.get(), std::move(depPcmPath));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>> tempModulePaths = std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>>(interfaces.size());
|
||||
for(std::uint16_t i = 0; i < interfaces.size(); i++){
|
||||
fs::path file = path / interfaces[i];
|
||||
file += ".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("");
|
||||
this->interfaces.push_back(std::make_unique<Module>(std::move(match[1].str()), std::move(std::get<0>(file))));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
for(std::uint16_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 : this->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) {
|
||||
ModulePartition* partition = std::get<2>(file);
|
||||
Module* parentModule = std::get<3>(file);
|
||||
const std::string& fileContent = std::get<1>(file);
|
||||
|
||||
std::regex partitionPattern(R"(import :([a-zA-Z_\-0-9\.]+);)");
|
||||
std::sregex_iterator currentMatch(fileContent.begin(), fileContent.end(), partitionPattern);
|
||||
std::sregex_iterator lastMatch;
|
||||
while (currentMatch != lastMatch) {
|
||||
std::smatch match = *currentMatch;
|
||||
for(std::unique_ptr<ModulePartition>& sibling : parentModule->partitions) {
|
||||
if(sibling->name == match[1]) {
|
||||
partition->partitionDependencies.push_back(sibling.get());
|
||||
goto next2;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error(std::format("imported partition {}:{} not found, referenced in {}", parentModule->name, match[1].str(), std::get<0>(file).string()));
|
||||
next2: ++currentMatch;
|
||||
}
|
||||
|
||||
std::regex modulePattern(R"(import ([a-zA-Z_0-9\.\-]+);)");
|
||||
std::sregex_iterator modCurrent(fileContent.begin(), fileContent.end(), modulePattern);
|
||||
while (modCurrent != lastMatch) {
|
||||
std::smatch match = *modCurrent;
|
||||
resolveImport(match[1].str(), partition->moduleDependencies, partition->externalModuleDependencies);
|
||||
++modCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
for(const fs::path& tempFile : implementations) {
|
||||
fs::path file = path / tempFile;
|
||||
file += ".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 = this->implementations.emplace_back(std::move(fileCopy));
|
||||
if (std::regex_search(fileContent, match, std::regex(R"(module ([a-zA-Z0-9_\.\-]+)(:[a-zA-Z0-9_\.\-]+)?\s*;)"))) {
|
||||
bool isPartitionImpl = match[2].length() > 0;
|
||||
for(const std::unique_ptr<Module>& interface : this->interfaces) {
|
||||
if(interface->name == match[1]) {
|
||||
if (!isPartitionImpl) {
|
||||
implementation.moduleDependencies.push_back(interface.get());
|
||||
}
|
||||
std::regex partitionPattern(R"(import :([a-zA-Z_\-0-9\.]+);)");
|
||||
std::sregex_iterator currentMatch(fileContent.begin(), fileContent.end(), partitionPattern);
|
||||
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;
|
||||
}
|
||||
|
||||
std::regex modulePattern(R"(import ([a-zA-Z_0-9\.\-]+);)");
|
||||
std::sregex_iterator modCurrent(fileContent.begin(), fileContent.end(), modulePattern);
|
||||
while (modCurrent != lastMatch) {
|
||||
std::smatch match2 = *modCurrent;
|
||||
if (match2[1] != match[1]) {
|
||||
resolveImport(match2[1].str(), implementation.moduleDependencies, implementation.externalModuleDependencies);
|
||||
}
|
||||
++modCurrent;
|
||||
}
|
||||
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 match2 = *currentMatch;
|
||||
resolveImport(match2[1].str(), implementation.moduleDependencies, implementation.externalModuleDependencies);
|
||||
++currentMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, std::shared_future<BuildResult>>& depResults, std::mutex& depMutex) {
|
||||
fs::path buildDir = config.path/"build"/std::format("{}-{}-{}", config.name, config.target, config.march);
|
||||
fs::path outputDir = config.path/"bin"/std::format("{}-{}-{}", config.name, config.target, config.march);
|
||||
|
||||
if (!fs::exists(buildDir)) {
|
||||
fs::create_directories(buildDir);
|
||||
|
|
@ -35,6 +196,8 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
fs::create_directories(outputDir);
|
||||
}
|
||||
|
||||
BuildResult buildResult;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(config.shaders.size() + 1 + config.interfaces.size() + config.implementations.size());
|
||||
|
||||
|
|
@ -106,7 +269,23 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
}
|
||||
});
|
||||
|
||||
fs::path stdPcmDir = GetCacheDir()/config.target/"-"/config.march;
|
||||
std::vector<ExternalBuildResult> externalResults(config.externalDependencies.size());
|
||||
std::vector<std::thread> externalThreads;
|
||||
externalThreads.reserve(config.externalDependencies.size());
|
||||
for (std::size_t i = 0; i < config.externalDependencies.size(); ++i) {
|
||||
externalThreads.emplace_back([&, i]() {
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||
externalResults[i] = BuildExternal(config.externalDependencies[i], buildCancelled);
|
||||
if (!externalResults[i].error.empty()) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = externalResults[i].error;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fs::path stdPcmDir = GetCacheDir()/(config.target+"-"+config.march);
|
||||
|
||||
if (!fs::exists(stdPcmDir)) {
|
||||
fs::create_directories(stdPcmDir);
|
||||
|
|
@ -114,7 +293,10 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
|
||||
std::string stdPcmResult = BuildStdPcm(config, stdPcmDir/"std.pcm");
|
||||
if(!stdPcmResult.empty()) {
|
||||
return {result, false, {}};
|
||||
buildCancelled.store(true);
|
||||
for(std::thread& thread : threads) thread.join();
|
||||
for(std::thread& thread : externalThreads) thread.join();
|
||||
return {stdPcmResult, false, {}};
|
||||
}
|
||||
|
||||
fs::path pcmDir;
|
||||
|
|
@ -125,58 +307,93 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
pcmDir = buildDir;
|
||||
}
|
||||
|
||||
std::string command = std::format("{} --target={} -march={} -mtune={} -std=c++26 -D CRAFTER_BUILD_CONFIGURATION_TARGET=\\\"{}\\\" -D CRAFTER_BUILD_CONFIGURATION_TARGET_{} -fprebuilt-module-path={} -fprebuilt-module-path={}", GetBaseCommand(), config.target, config.march, config.mtune, editedTarget, editedTarget, stdPcm.parent_path().string(), stdPcmDir);
|
||||
fs::copy_file(stdPcmDir/"std.pcm", pcmDir/"std.pcm", fs::copy_options::update_existing);
|
||||
|
||||
if(config.type == CRAFTER_CONFIGURATION_TYPE_SHARED_LIBRARY) {
|
||||
std::string editedTarget = config.target;
|
||||
std::replace(editedTarget.begin(), editedTarget.end(), '-', '_');
|
||||
|
||||
std::string command = std::format("{} --target={} -march={} -mtune={} -std=c++26 -D CRAFTER_BUILD_CONFIGURATION_TARGET=\\\"{}\\\" -D CRAFTER_BUILD_CONFIGURATION_TARGET_{} -fprebuilt-module-path={} -fprebuilt-module-path={}", GetBaseCommand(config), config.target, config.march, config.mtune, editedTarget, editedTarget, stdPcmDir.string(), pcmDir.string());
|
||||
|
||||
if(config.type == ConfigurationType::LibraryDynamic) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
command += " -fPIC -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY";
|
||||
#endif
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY";
|
||||
#endif
|
||||
} else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY) {
|
||||
command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_LIBRARY";
|
||||
} else {
|
||||
} else if(config.type == ConfigurationType::Executable) {
|
||||
command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE";
|
||||
} else {
|
||||
command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_LIBRARY";
|
||||
}
|
||||
|
||||
std::string files;
|
||||
std::unordered_set<std::string> libSet;
|
||||
std::mutex fileMutex;
|
||||
std::vector<std::thread> depThreads;
|
||||
depThreads.reserve(config.dependencies);
|
||||
depThreads.reserve(config.dependencies.size());
|
||||
std::atomic<bool> repack(false);
|
||||
|
||||
for(const Dependency& dep : config.dependencies) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dep.path)) {
|
||||
for(Configuration* dep : config.dependencies) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dep->path)) {
|
||||
if (entry.is_directory() && entry.path().filename() == "include") {
|
||||
command += " -I" + entry.path().string();
|
||||
}
|
||||
}
|
||||
|
||||
command += std::format(" -I{} -fprebuilt-module-path={}", dep.path.string(), (dep.path/"bin"/(config.target+config.march)).string());
|
||||
command += std::format(" -I{} -fprebuilt-module-path={}", dep->path.string(), dep->PcmDir().string());
|
||||
|
||||
depThreads.emplace_back([&, dep](){
|
||||
try {
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
std::shared_ptr<std::promise<BuildResult>> promise;
|
||||
std::shared_future<BuildResult> resultFuture;
|
||||
bool isBuilder = false;
|
||||
|
||||
depThreads.emplace_back([&](){
|
||||
if (!buildCancelled.load(std::memory_order_relaxed)) {
|
||||
depMutex.lock();
|
||||
if(!depSet.contains(dep.path)) {
|
||||
depSet.insert(dep.path);
|
||||
depMutex.unlock();
|
||||
BuildResult result = Build(dep.path, depSet, depMutex);
|
||||
fileMutex.lock();
|
||||
libSet.merge(result.libs);
|
||||
fileMutex.unlock();
|
||||
if (buildCancelled.compare_exchange_strong(false, true)) {
|
||||
buildError = std::move(result.result);
|
||||
}
|
||||
if(result.repack) {
|
||||
repack = true;
|
||||
}
|
||||
auto it = depResults.find(dep->path);
|
||||
if (it == depResults.end()) {
|
||||
isBuilder = true;
|
||||
promise = std::make_shared<std::promise<BuildResult>>();
|
||||
resultFuture = promise->get_future().share();
|
||||
depResults.emplace(dep->path, resultFuture);
|
||||
} else {
|
||||
depMutex.unlock();
|
||||
resultFuture = it->second;
|
||||
}
|
||||
depMutex.unlock();
|
||||
|
||||
if (isBuilder) {
|
||||
BuildResult built;
|
||||
try {
|
||||
built = Build(*dep, depResults, depMutex);
|
||||
} catch (...) {
|
||||
promise->set_exception(std::current_exception());
|
||||
throw;
|
||||
}
|
||||
promise->set_value(std::move(built));
|
||||
}
|
||||
|
||||
const BuildResult& result = resultFuture.get();
|
||||
fileMutex.lock();
|
||||
for (const std::string& lib : result.libs) libSet.insert(lib);
|
||||
fileMutex.unlock();
|
||||
if (!result.result.empty()) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = result.result;
|
||||
}
|
||||
}
|
||||
if (result.repack) {
|
||||
repack = true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::format("dep build for {} threw: {}", dep->path.string(), e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for(const Define& define : config.defines) {
|
||||
|
|
@ -187,6 +404,10 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
}
|
||||
}
|
||||
|
||||
for(const std::string& flag : config.compileFlags) {
|
||||
command += " " + flag;
|
||||
}
|
||||
|
||||
std::string cmakeBuildType;
|
||||
|
||||
if(config.debug) {
|
||||
|
|
@ -197,12 +418,12 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
command += " -O3";
|
||||
}
|
||||
|
||||
for (const fs::path& cFile : config.c_files) {
|
||||
for (const fs::path& cFile : config.cFiles) {
|
||||
files += std::format(" {}_source.o ", (buildDir / cFile.filename()).string());
|
||||
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
|
||||
const std::string srcPath = cFile.string() + ".c";
|
||||
if (!fs::exists(objPath) || fs::last_write_time(srcPath) > fs::last_write_time(objPath)) {
|
||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled]() {
|
||||
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config]() {
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
std::string result = RunCommand(std::format("clang {}.c --target={} -march={} -mtune={} -O3 -c -o {}_source.o", cFile.string(), config.target, config.march, config.mtune, (buildDir / cFile.filename()).string()));
|
||||
|
|
@ -238,92 +459,70 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
for(std::thread& thread : depThreads) {
|
||||
thread.join();
|
||||
}
|
||||
for(std::thread& thread : externalThreads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
if(buildCancelled.load()) {
|
||||
for(std::thread& thread : threads) thread.join();
|
||||
return {buildError, false, {}};
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Module>> interfaces;
|
||||
std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>> tempModulePaths = std::vector<std::tuple<fs::path, std::string, ModulePartition*, Module*>>(config.interfaces.size());
|
||||
for(std::uint16_t i = 0; i < config.interfaces.size(); i++){
|
||||
const std::filesystem::path file = workingDir / (config.interfaces[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};
|
||||
if(repack.load()) {
|
||||
buildResult.repack = true;
|
||||
}
|
||||
buildResult.libs = std::move(libSet);
|
||||
for(const std::string& flag : config.linkFlags) {
|
||||
buildResult.libs.insert(flag);
|
||||
}
|
||||
fs::file_time_type externalFloor = fs::file_time_type::min();
|
||||
for(const ExternalBuildResult& ext : externalResults) {
|
||||
for(const std::string& flag : ext.compileFlags) {
|
||||
command += " " + flag;
|
||||
}
|
||||
for(const std::string& flag : ext.linkFlags) {
|
||||
buildResult.libs.insert(flag);
|
||||
}
|
||||
if (ext.latestArtifact > externalFloor) externalFloor = ext.latestArtifact;
|
||||
}
|
||||
|
||||
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(std::uint16_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;
|
||||
for(std::unique_ptr<Module>& interface : config.interfaces) {
|
||||
if(interface->Check(pcmDir, externalFloor)) {
|
||||
Module* mod = interface.get();
|
||||
threads.emplace_back([mod, &command, &pcmDir, &buildDir, &buildCancelled, &buildError]() {
|
||||
try {
|
||||
mod->Compile(command, pcmDir, buildDir, buildCancelled, buildError);
|
||||
} catch (const std::exception& e) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::format("Module::Compile threw: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for(Module& interface : interfaces) {
|
||||
if(config.interfaces[i]->Check(pcmDir)) {
|
||||
threads.emplace_back(&Module::Compile, interface, command, pcmDir, buildDir, buildCancelled, buildError);
|
||||
});
|
||||
buildResult.repack = true;
|
||||
}
|
||||
files += std::format(" {}/{}.o", buildDir.string(), interface.path.filename().string());
|
||||
for(std::unique_ptr<ModulePartition>& part : interface.partitions) {
|
||||
files += std::format(" {}/{}.o", buildDir.string(), interface->path.filename().string());
|
||||
for(std::unique_ptr<ModulePartition>& part : interface->partitions) {
|
||||
files += std::format(" {}/{}.o", buildDir.string(), part->path.filename().string());
|
||||
}
|
||||
}
|
||||
|
||||
for(Implementation& interface : implementations) {
|
||||
if(config.implementations[i].Check(buildDir, pcmDir)) {
|
||||
for(Implementation& implementation : config.implementations) {
|
||||
if(implementation.Check(buildDir, pcmDir, externalFloor)) {
|
||||
buildResult.repack = true;
|
||||
threads.emplace_back(&Implementation::Compile, &config.implementations[i], command, buildDir, buildCancelled, buildError);
|
||||
Implementation* impl = &implementation;
|
||||
threads.emplace_back([impl, &command, &buildDir, &buildCancelled, &buildError]() {
|
||||
try {
|
||||
impl->Compile(command, buildDir, buildCancelled, buildError);
|
||||
} catch (const std::exception& e) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::format("Implementation::Compile threw: {}", e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
files += std::format(" {}/{}_impl.o", buildDir.string(), config.implementations[i].path.filename().string());
|
||||
files += std::format(" {}/{}_impl.o", buildDir.string(), implementation.path.filename().string());
|
||||
}
|
||||
|
||||
for(std::thread& thread : threads) {
|
||||
|
|
@ -334,29 +533,95 @@ BuildResult Build(const Configuration& config, std::unordered_set<fs::path>& dep
|
|||
return {buildError, false, {}};
|
||||
}
|
||||
|
||||
std::string linkExtras;
|
||||
for(const std::string& flag : buildResult.libs) {
|
||||
linkExtras += " " + flag;
|
||||
}
|
||||
|
||||
if(buildResult.repack) {
|
||||
if(config.type == CRAFTER_CONFIGURATION_TYPE_EXECUTABLE) {
|
||||
if(config.type == ConfigurationType::Executable) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {} -fuse-ld=lld ", command, files, (binDir/outputName).string()));
|
||||
if(config.target == "x86_64-w64-mingw32") {
|
||||
try {
|
||||
// Iterate over the source directory
|
||||
for (const auto& entry : fs::directory_iterator("/usr/x86_64-w64-mingw32/bin/")) {
|
||||
// Check if the file is a regular file and ends with ".dll"
|
||||
if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") {
|
||||
// Construct the destination file path
|
||||
fs::path dest_file = outputDir / entry.path().filename();
|
||||
|
||||
// Check if the destination file exists and if it is older than the source file
|
||||
if (!fs::exists(dest_file) || fs::last_write_time(entry.path()) > fs::last_write_time(dest_file)) {
|
||||
// Copy the file if it doesn't exist or is older
|
||||
fs::copy(entry.path(), dest_file, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
return {e.what(), false, {}};
|
||||
}
|
||||
}
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {} -fuse-ld=lld{}", command, files, (outputDir/config.outputName).string(), linkExtras));
|
||||
#endif
|
||||
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
system(std::format("copy \"%LIBCXX_DIR%\\lib\\c++.dll\" \"{}\\c++.dll\"", binDir.string()).c_str());
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {}.exe -fuse-ld=lld -L %LIBCXX_DIR%\\lib -lc++ -nostdinc++ -nostdlib++", command, files, (binDir/outputName).string()));
|
||||
std::system(std::format("copy \"%LIBCXX_DIR%\\lib\\c++.dll\" \"{}\\c++.dll\"", outputDir.string()).c_str());
|
||||
buildResult.result = RunCommand(std::format("{}{} -o {}.exe -fuse-ld=lld -L %LIBCXX_DIR%\\lib -lc++ -nostdinc++ -nostdlib++{}", command, files, (outputDir/config.outputName).string(), linkExtras));
|
||||
#endif
|
||||
} else if(config.type == CRAFTER_CONFIGURATION_TYPE_LIBRARY) {
|
||||
} else if(config.type == ConfigurationType::LibraryStatic) {
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
buildResult.result = RunCommand(std::format("ar r {}.a {}", (binDir/fs::path(std::string("lib")+outputName)).string(), files));
|
||||
buildResult.result = RunCommand(std::format("ar rcs {}.a {}", (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files));
|
||||
#endif
|
||||
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
buildResult.result = RunCommand(std::format("llvm-lib.exe {} /OUT:{}.lib", files, (binDir/fs::path(outputName)).string()));
|
||||
buildResult.result = RunCommand(std::format("llvm-lib.exe {} /OUT:{}.lib", files, (outputDir/fs::path(config.outputName)).string()));
|
||||
#endif
|
||||
} else {
|
||||
buildResult.result = RunCommand(std::format("{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld", command, files, (binDir/(std::string("lib")+outputName)).string()));
|
||||
buildResult.result = RunCommand(std::format("{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld{}", command, files, (outputDir/(std::string("lib")+config.outputName)).string(), linkExtras));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (config.type == ConfigurationType::LibraryStatic || config.type == ConfigurationType::LibraryDynamic) {
|
||||
buildResult.libs.insert(std::format("-L{}", outputDir.string()));
|
||||
buildResult.libs.insert(std::format("-l{}", config.outputName));
|
||||
}
|
||||
|
||||
return buildResult;
|
||||
}
|
||||
}
|
||||
int Crafter::Run(int argc, char** argv) {
|
||||
try {
|
||||
fs::path projectFile = "./project.cpp";
|
||||
std::vector<std::string_view> projectArgs;
|
||||
projectArgs.reserve(argc);
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string_view arg = argv[i];
|
||||
if (arg.starts_with("--project=")) {
|
||||
projectFile = arg.substr(std::string_view("--project=").size());
|
||||
} else {
|
||||
projectArgs.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs::exists(projectFile)) {
|
||||
std::println(std::cerr, "No project file at {}", projectFile.string());
|
||||
return 1;
|
||||
}
|
||||
|
||||
Configuration config = LoadProject(projectFile, projectArgs);
|
||||
|
||||
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
||||
std::mutex depMutex;
|
||||
BuildResult result = Build(config, depResults, depMutex);
|
||||
|
||||
if (!result.result.empty()) {
|
||||
std::println(std::cerr, "{}", result.result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::println(std::cerr, "{}", e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
251
implementations/Crafter.Build-External.cpp
Normal file
251
implementations/Crafter.Build-External.cpp
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
Crafter® Build
|
||||
Copyright (C) 2026 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:External_impl;
|
||||
import std;
|
||||
import :External;
|
||||
import :Platform;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string DeriveName(const GitSource& source) {
|
||||
std::string url = source.url;
|
||||
while (!url.empty() && (url.back() == '/' || url.back() == '\\')) url.pop_back();
|
||||
if (url.ends_with(".git")) url.resize(url.size() - 4);
|
||||
auto slash = url.find_last_of("/\\");
|
||||
return slash == std::string::npos ? url : url.substr(slash + 1);
|
||||
}
|
||||
|
||||
std::string ShellQuote(std::string_view s) {
|
||||
std::string out = "'";
|
||||
for (char c : s) {
|
||||
if (c == '\'') out += "'\\''";
|
||||
else out += c;
|
||||
}
|
||||
out += "'";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string JoinOptions(std::span<const std::string> options) {
|
||||
std::string out;
|
||||
for (const std::string& opt : options) {
|
||||
if (!out.empty()) out += ' ';
|
||||
out += ShellQuote(opt);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string FetchGit(const GitSource& source, const fs::path& cloneDir) {
|
||||
auto runGit = [](std::string_view cmd) -> std::optional<std::string> {
|
||||
CommandResult r = RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) return std::format("`{}` failed (exit {}): {}", cmd, r.exitCode, r.output);
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (fs::exists(cloneDir)) {
|
||||
CommandResult remote = RunCommandChecked(std::format("git -C {} remote get-url origin", ShellQuote(cloneDir.string())));
|
||||
std::string currentUrl = remote.output;
|
||||
while (!currentUrl.empty() && (currentUrl.back() == '\n' || currentUrl.back() == '\r')) currentUrl.pop_back();
|
||||
if (remote.exitCode != 0 || currentUrl != source.url) {
|
||||
fs::remove_all(cloneDir);
|
||||
} else if (!source.commit.empty()) {
|
||||
CommandResult head = RunCommandChecked(std::format("git -C {} rev-parse HEAD", ShellQuote(cloneDir.string())));
|
||||
std::string currentHead = head.output;
|
||||
while (!currentHead.empty() && (currentHead.back() == '\n' || currentHead.back() == '\r')) currentHead.pop_back();
|
||||
if (head.exitCode == 0 && currentHead != source.commit) {
|
||||
if (auto err = runGit(std::format("git -C {} fetch origin", ShellQuote(cloneDir.string())))) return *err;
|
||||
if (auto err = runGit(std::format("git -C {} checkout {}", ShellQuote(cloneDir.string()), ShellQuote(source.commit)))) return *err;
|
||||
}
|
||||
return "";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
fs::path tmpDir = cloneDir;
|
||||
tmpDir += ".tmp";
|
||||
if (fs::exists(tmpDir)) fs::remove_all(tmpDir);
|
||||
|
||||
if (auto err = runGit(std::format("git clone --recursive {} {}", ShellQuote(source.url), ShellQuote(tmpDir.string())))) {
|
||||
if (fs::exists(tmpDir)) fs::remove_all(tmpDir);
|
||||
return *err;
|
||||
}
|
||||
if (!source.branch.empty()) {
|
||||
if (auto err = runGit(std::format("git -C {} switch {}", ShellQuote(tmpDir.string()), ShellQuote(source.branch)))) {
|
||||
fs::remove_all(tmpDir);
|
||||
return *err;
|
||||
}
|
||||
}
|
||||
if (!source.commit.empty()) {
|
||||
if (auto err = runGit(std::format("git -C {} checkout {}", ShellQuote(tmpDir.string()), ShellQuote(source.commit)))) {
|
||||
fs::remove_all(tmpDir);
|
||||
return *err;
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
fs::rename(tmpDir, cloneDir, ec);
|
||||
if (ec) {
|
||||
fs::remove_all(tmpDir);
|
||||
return std::format("rename {} -> {} failed: {}", tmpDir.string(), cloneDir.string(), ec.message());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BuildInjectedCMakeFlags(const fs::path& cmakeBuildDir) {
|
||||
std::string out;
|
||||
out += " -DCMAKE_C_COMPILER=clang";
|
||||
out += " -DCMAKE_CXX_COMPILER=clang++";
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
out += " -DCMAKE_CXX_FLAGS=-stdlib=libc++";
|
||||
out += " -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++";
|
||||
out += " -DCMAKE_SHARED_LINKER_FLAGS=-stdlib=libc++";
|
||||
#endif
|
||||
fs::path absBuildDir = fs::absolute(cmakeBuildDir);
|
||||
out += std::format(" -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}", ShellQuote(absBuildDir.string()));
|
||||
out += std::format(" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}", ShellQuote(absBuildDir.string()));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ConfigureCMake(const fs::path& cloneDir, const fs::path& cmakeBuildDir, std::span<const std::string> options) {
|
||||
fs::path optionsFile = cmakeBuildDir / ".crafter-options";
|
||||
std::string optionsKey = JoinOptions(options);
|
||||
|
||||
bool needsConfigure = !fs::exists(cmakeBuildDir / "CMakeCache.txt");
|
||||
if (!needsConfigure) {
|
||||
std::ifstream existing(optionsFile);
|
||||
if (!existing) {
|
||||
needsConfigure = true;
|
||||
} else {
|
||||
std::stringstream buf;
|
||||
buf << existing.rdbuf();
|
||||
if (buf.str() != optionsKey) needsConfigure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsConfigure) return "";
|
||||
|
||||
if (!fs::exists(cmakeBuildDir)) fs::create_directories(cmakeBuildDir);
|
||||
|
||||
std::string cmd = std::format("cmake -S {} -B {}{}",
|
||||
ShellQuote(fs::absolute(cloneDir).string()),
|
||||
ShellQuote(fs::absolute(cmakeBuildDir).string()),
|
||||
BuildInjectedCMakeFlags(cmakeBuildDir));
|
||||
if (!options.empty()) {
|
||||
cmd += " ";
|
||||
cmd += optionsKey;
|
||||
}
|
||||
|
||||
CommandResult r = RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) {
|
||||
return std::format("cmake configure failed (exit {}): {}", r.exitCode, r.output);
|
||||
}
|
||||
|
||||
std::ofstream(optionsFile) << optionsKey;
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BuildCMake(const fs::path& cmakeBuildDir) {
|
||||
std::string cmd = std::format("cmake --build {}", ShellQuote(fs::absolute(cmakeBuildDir).string()));
|
||||
CommandResult r = RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) {
|
||||
return std::format("cmake --build failed (exit {}): {}", r.exitCode, r.output);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ExternalBuildResult Crafter::BuildExternal(
|
||||
const ExternalDependency& dep,
|
||||
std::atomic<bool>& cancelled) {
|
||||
ExternalBuildResult result;
|
||||
|
||||
if (cancelled.load(std::memory_order_relaxed)) return result;
|
||||
|
||||
std::string name = dep.name.empty() ? DeriveName(dep.source) : dep.name;
|
||||
if (name.empty()) {
|
||||
result.error = std::format("Could not derive name for external dependency from URL '{}'", dep.source.url);
|
||||
return result;
|
||||
}
|
||||
|
||||
fs::path externalRoot = GetCacheDir() / "external";
|
||||
if (!fs::exists(externalRoot)) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(externalRoot, ec);
|
||||
if (ec) {
|
||||
result.error = std::format("Failed to create {}: {}", externalRoot.string(), ec.message());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::string keyMaterial = std::format("{}|{}|{}|{}",
|
||||
dep.source.url, dep.source.branch, dep.source.commit, JoinOptions(dep.options));
|
||||
std::size_t key = std::hash<std::string>{}(keyMaterial);
|
||||
fs::path cloneDir = externalRoot / std::format("{}-{:016x}", name, key);
|
||||
|
||||
std::string fetchErr = FetchGit(dep.source, cloneDir);
|
||||
if (!fetchErr.empty()) {
|
||||
result.error = std::format("git fetch for '{}': {}", name, fetchErr);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cancelled.load(std::memory_order_relaxed)) return result;
|
||||
|
||||
fs::path cmakeBuildDir;
|
||||
if (dep.builder == ExternalBuilder::CMake) {
|
||||
cmakeBuildDir = cloneDir / "build";
|
||||
if (std::string err = ConfigureCMake(cloneDir, cmakeBuildDir, dep.options); !err.empty()) {
|
||||
result.error = std::format("cmake configure for '{}': {}", name, err);
|
||||
return result;
|
||||
}
|
||||
if (cancelled.load(std::memory_order_relaxed)) return result;
|
||||
if (std::string err = BuildCMake(cmakeBuildDir); !err.empty()) {
|
||||
result.error = std::format("cmake build for '{}': {}", name, err);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (const fs::path& include : dep.includeDirs) {
|
||||
fs::path full = include.empty() ? cloneDir : cloneDir / include;
|
||||
result.compileFlags.push_back(std::format("-I{}", fs::absolute(full).string()));
|
||||
}
|
||||
|
||||
if (dep.builder == ExternalBuilder::CMake) {
|
||||
result.linkFlags.push_back(std::format("-L{}", fs::absolute(cmakeBuildDir).string()));
|
||||
for (const std::string& lib : dep.libs) {
|
||||
result.linkFlags.push_back(std::format("-l{}", lib));
|
||||
}
|
||||
|
||||
if (fs::exists(cmakeBuildDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(cmakeBuildDir)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
const fs::path& p = entry.path();
|
||||
if (p.extension() != ".a" && p.extension() != ".so") continue;
|
||||
std::error_code ec;
|
||||
fs::file_time_type t = entry.last_write_time(ec);
|
||||
if (!ec && t > result.latestArtifact) result.latestArtifact = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -20,49 +20,55 @@ 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;
|
||||
import :Interface;
|
||||
import :Platform;
|
||||
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 {
|
||||
bool Implementation::Check(const fs::path& buildDir, const fs::path& pcmDir, fs::file_time_type sourceFloor) const {
|
||||
std::string objPath = (buildDir/path.filename()).string()+"_impl.o";
|
||||
std::string cppPath = path.string()+".cpp";
|
||||
if(!fs::exists(objPath) || std::max(fs::last_write_time(cppPath), sourceFloor) >= fs::last_write_time(objPath)) {
|
||||
return true;
|
||||
}
|
||||
fs::file_time_type objTime = fs::last_write_time(objPath);
|
||||
for(ModulePartition* dependency : partitionDependencies) {
|
||||
if(dependency->Check(pcmDir, sourceFloor)) return true;
|
||||
}
|
||||
for(Module* dependency : moduleDependencies) {
|
||||
if(dependency->Check(pcmDir, sourceFloor)) return true;
|
||||
}
|
||||
for(const auto& [externalMod, externalPcmPath] : externalModuleDependencies) {
|
||||
std::error_code ec;
|
||||
fs::file_time_type pcmTime = fs::last_write_time(externalPcmPath, ec);
|
||||
if (!ec && pcmTime >= objTime) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void Implementation::Compile(const std::string_view clang, const fs::path& buildDir, std::atomic<bool>& buildCancelled, std::string& buildError) const {
|
||||
for(ModulePartition* dependency : partitionDependencies) {
|
||||
if(!dependency->compiled.load()) {
|
||||
dependency->compiled.wait(true);
|
||||
dependency->compiled.wait(false);
|
||||
}
|
||||
}
|
||||
for(Module* dependency : moduleDependencies) {
|
||||
if(!dependency->compiled.load()) {
|
||||
dependency->compiled.wait(true);
|
||||
dependency->compiled.wait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!buildCancelled.load(std::memory_order_relaxed)) {
|
||||
if (buildCancelled.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string result = RunCommand(std::format("{} {}.cpp -c -o {}_impl.o", clang, path.string(), (buildDir/path.filename()).string()));
|
||||
|
||||
|
||||
bool expected = false;
|
||||
if(!result.empty() && buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::move(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,30 +20,41 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
module Crafter.Build:Interface_impl;
|
||||
import std;
|
||||
import :Interface;
|
||||
import :Command;
|
||||
import :Platform;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Crafter {
|
||||
ModulePartition::ModulePartition(std::string&& name, fs::path&& path) : name(std::move(name)), path(std::move(path)), compiled(false), checked(false) {}
|
||||
|
||||
bool ModulePartition::Check(const fs::path& pcmDir) {
|
||||
bool ModulePartition::Check(const fs::path& pcmDir, fs::file_time_type sourceFloor) {
|
||||
if(!checked) {
|
||||
checked = true;
|
||||
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")) {
|
||||
std::string pcmPath = (pcmDir/path.filename()).generic_string()+".pcm";
|
||||
std::string cppmPath = path.generic_string()+".cppm";
|
||||
if(fs::exists(pcmPath) && std::max(fs::last_write_time(cppmPath), sourceFloor) < fs::last_write_time(pcmPath)) {
|
||||
fs::file_time_type pcmTime = fs::last_write_time(pcmPath);
|
||||
for(ModulePartition* dependency : partitionDependencies) {
|
||||
if(dependency->Check(pcmDir)) {
|
||||
if(dependency->Check(pcmDir, sourceFloor)) {
|
||||
needsRecompiling = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(Module* dependency : moduleDependencies) {
|
||||
if(dependency->Check(pcmDir)) {
|
||||
if(dependency->Check(pcmDir, sourceFloor)) {
|
||||
needsRecompiling = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(const auto& [externalMod, externalPcmPath] : externalModuleDependencies) {
|
||||
std::error_code ec;
|
||||
fs::file_time_type t = fs::last_write_time(externalPcmPath, ec);
|
||||
if (!ec && t >= pcmTime) {
|
||||
needsRecompiling = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
needsRecompiling = false;
|
||||
compiled.store(CRAFTER_COMPILE_STATUS_COMPLETED);
|
||||
compiled.store(true);
|
||||
return false;
|
||||
} else {
|
||||
needsRecompiling = true;
|
||||
|
|
@ -54,10 +65,15 @@ namespace Crafter {
|
|||
}
|
||||
}
|
||||
|
||||
void ModulePartition::Compile(const std::string_view clang, const fs::path& pcmDir, std::atomic<bool>& buildCancelled, std::string& buildError) {
|
||||
void ModulePartition::Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic<bool>& buildCancelled, std::string& buildError) {
|
||||
for(ModulePartition* dependency : partitionDependencies) {
|
||||
if(!dependency->compiled.load()) {
|
||||
dependency->compiled.wait(true);
|
||||
dependency->compiled.wait(false);
|
||||
}
|
||||
}
|
||||
for(Module* dependency : moduleDependencies) {
|
||||
if(!dependency->compiled.load()) {
|
||||
dependency->compiled.wait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +86,8 @@ namespace Crafter {
|
|||
std::string result = RunCommand(std::format("{} {}.cppm --precompile -o {}.pcm", clang, path.string(), (pcmDir/path.filename()).string()));
|
||||
|
||||
if (!result.empty()) {
|
||||
if (buildCancelled.compare_exchange_strong(false, true)) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::move(result);
|
||||
compiled.store(true);
|
||||
compiled.notify_all();
|
||||
|
|
@ -81,10 +98,11 @@ namespace Crafter {
|
|||
compiled.store(true);
|
||||
compiled.notify_all();
|
||||
|
||||
result = RunClang(std::format("{} -Wno-unused-command-line-argument {}.pcm -c -o {}.o", clang, (pcmDir/path.filename()).string(), (buildDir/path.filename()).string()));
|
||||
result = RunCommand(std::format("{} -Wno-unused-command-line-argument {}.pcm -c -o {}.o", clang, (pcmDir/path.filename()).string(), (buildDir/path.filename()).string()));
|
||||
|
||||
if (!result.empty()) {
|
||||
if (buildCancelled.compare_exchange_strong(false, true)) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::move(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -92,13 +110,15 @@ namespace Crafter {
|
|||
|
||||
Module::Module(std::string&& name, fs::path&& path) : name(std::move(name)), path(std::move(path)), compiled(false), checked(false) {}
|
||||
|
||||
bool Module::Check(const fs::path& pcmDir) {
|
||||
bool Module::Check(const fs::path& pcmDir, fs::file_time_type sourceFloor) {
|
||||
if(!checked) {
|
||||
checked = true;
|
||||
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")) {
|
||||
std::string pcmPath = (pcmDir/path.filename()).generic_string()+".pcm";
|
||||
std::string cppmPath = path.generic_string()+".cppm";
|
||||
if(fs::exists(pcmPath) && std::max(fs::last_write_time(cppmPath), sourceFloor) < fs::last_write_time(pcmPath)) {
|
||||
bool depCheck = false;
|
||||
for(std::unique_ptr<ModulePartition>& partition : partitions) {
|
||||
if(partition->Check(pcmDir)) {
|
||||
if(partition->Check(pcmDir, sourceFloor)) {
|
||||
depCheck = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +132,7 @@ namespace Crafter {
|
|||
}
|
||||
} else {
|
||||
for(std::unique_ptr<ModulePartition>& partition : partitions) {
|
||||
partition->Check(pcmDir);
|
||||
partition->Check(pcmDir, sourceFloor);
|
||||
}
|
||||
needsRecompiling = true;
|
||||
return true;
|
||||
|
|
@ -124,10 +144,10 @@ namespace Crafter {
|
|||
|
||||
void Module::Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic<bool>& buildCancelled, std::string& buildError) {
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(interface.partitions.size());
|
||||
for(std::unique_ptr<ModulePartition>& part : interface.partitions) {
|
||||
if(part.needsRecompiling) {
|
||||
threads.emplace_back(&ModulePartition::Compile, part.get(), clang, pcmDir, buildDir, buildCancelled, buildError);
|
||||
threads.reserve(partitions.size());
|
||||
for(std::unique_ptr<ModulePartition>& part : partitions) {
|
||||
if(part->needsRecompiling) {
|
||||
threads.emplace_back(&ModulePartition::Compile, part.get(), clang, pcmDir, buildDir, std::ref(buildCancelled), std::ref(buildError));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +164,8 @@ namespace Crafter {
|
|||
std::string result = RunCommand(std::format("{} {}.cppm --precompile -o {}.pcm", clang, path.string(), (pcmDir/path.filename()).string()));
|
||||
|
||||
if (!result.empty()) {
|
||||
if (buildCancelled.compare_exchange_strong(false, true)) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::move(result);
|
||||
compiled.store(true);
|
||||
compiled.notify_all();
|
||||
|
|
@ -158,7 +179,8 @@ namespace Crafter {
|
|||
result = RunCommand(std::format("{} -Wno-unused-command-line-argument {}.pcm -c -o {}.o", clang, (pcmDir/path.filename()).string(), (buildDir/path.filename()).string()));
|
||||
|
||||
if (!result.empty()) {
|
||||
if (buildCancelled.compare_exchange_strong(false, true)) {
|
||||
bool expected = false;
|
||||
if (buildCancelled.compare_exchange_strong(expected, true)) {
|
||||
buildError = std::move(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,19 @@ 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:Platform_impl;
|
||||
module;
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
module Crafter.Build:Platform_impl;
|
||||
import std;
|
||||
import :Platform;
|
||||
import :Clang;
|
||||
|
|
@ -26,7 +37,7 @@ using namespace Crafter;
|
|||
|
||||
|
||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||
std::string RunCommand(const std::string_view cmd) {
|
||||
std::string Crafter::RunCommand(const std::string_view cmd) {
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
|
||||
|
|
@ -46,17 +57,36 @@ std::string RunCommand(const std::string_view cmd) {
|
|||
return result;
|
||||
}
|
||||
|
||||
fs::path BuildStdPcm(Configuration& config, fs::path stdPcm) {
|
||||
CommandResult Crafter::RunCommandChecked(std::string_view cmd) {
|
||||
std::array<char, 128> buffer;
|
||||
CommandResult result{0, ""};
|
||||
|
||||
std::string with = "cmd /C \"" + std::string(cmd) + " 2>&1\"";
|
||||
|
||||
FILE* pipe = _popen(with.c_str(), "r");
|
||||
if (!pipe) {
|
||||
throw std::runtime_error("_popen() failed!");
|
||||
}
|
||||
|
||||
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
|
||||
result.output += buffer.data();
|
||||
}
|
||||
|
||||
result.exitCode = _pclose(pipe);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
||||
std::string libcxx = std::getenv("LIBCXX_DIR");
|
||||
std::string stdPcm = std::format("{}\\{}{}\\std.pcm", exeDir.string(), config.target, config.march);
|
||||
std::string stdcppm = std::format("{}\\modules\\c++\\v1\\std.cppm", libcxx);
|
||||
|
||||
if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time(stdcppm)) {
|
||||
return RunCommand(std::format("clang++ --target={} -march={} -mtune={} -isystem %LIBCXX_DIR%\\include\\c++\\v1 -nostdinc++ -nostdlib++ -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile %LIBCXX_DIR%\\modules\\c++\\v1\\std.cppm -o {}", config.target, config.march, config.mtune, stdPcm));
|
||||
return RunCommand(std::format("clang++ --target={} -march={} -mtune={} -isystem %LIBCXX_DIR%\\include\\c++\\v1 -nostdinc++ -nostdlib++ -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile %LIBCXX_DIR%\\modules\\c++\\v1\\std.cppm -o {}", config.target, config.march, config.mtune, stdPcm.string()));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
fs::path GetCacheDir() {
|
||||
fs::path Crafter::GetCacheDir() {
|
||||
if (const char* local = std::getenv("LOCALAPPDATA")) {
|
||||
return fs::path(local) / "crafter.build";
|
||||
}
|
||||
|
|
@ -64,14 +94,121 @@ fs::path GetCacheDir() {
|
|||
throw std::runtime_error("LOCALAPPDATA not set");
|
||||
}
|
||||
|
||||
std::string GetBaseCommand(Configuration& config) {
|
||||
std::string Crafter::GetBaseCommand(const Configuration& config) {
|
||||
return std::format("clang++ -nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1");
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::string_view, 7> kCrafterBuildModules = {
|
||||
"Crafter.Build-Shader",
|
||||
"Crafter.Build-Platform",
|
||||
"Crafter.Build-Interface",
|
||||
"Crafter.Build-Implementation",
|
||||
"Crafter.Build-External",
|
||||
"Crafter.Build-Clang",
|
||||
"Crafter.Build",
|
||||
};
|
||||
|
||||
void EnsureCrafterBuildPcms(const fs::path& sourceDir, const fs::path& cacheDir) {
|
||||
for (std::string_view name : kCrafterBuildModules) {
|
||||
fs::path cppmPath = sourceDir / (std::string(name) + ".cppm");
|
||||
fs::path pcmPath = cacheDir / (std::string(name) + ".pcm");
|
||||
if (!fs::exists(cppmPath)) {
|
||||
throw std::runtime_error(std::format("module source {} not found in {} (set CRAFTER_BUILD_HOME)", name, sourceDir.string()));
|
||||
}
|
||||
if (fs::exists(pcmPath) && fs::last_write_time(cppmPath) < fs::last_write_time(pcmPath)) {
|
||||
continue;
|
||||
}
|
||||
std::string cmd = std::format(
|
||||
"clang++ --target=x86_64-pc-windows-msvc -march=native -mtune=native "
|
||||
"-std=c++26 -O3 "
|
||||
"-nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1 "
|
||||
"-Wno-reserved-identifier -Wno-reserved-module-identifier "
|
||||
"-fprebuilt-module-path={} "
|
||||
"--precompile {} -o {}",
|
||||
cacheDir.string(), cppmPath.string(), pcmPath.string());
|
||||
CommandResult r = Crafter::RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) {
|
||||
throw std::runtime_error(std::format("Failed to precompile {} (exit {}): {}", name, r.exitCode, r.output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const std::string_view> args) {
|
||||
fs::path absProject = fs::canonical(projectFile);
|
||||
fs::path buildDir = absProject.parent_path() / "build";
|
||||
if (!fs::exists(buildDir)) {
|
||||
fs::create_directories(buildDir);
|
||||
}
|
||||
fs::path dllPath = buildDir / (absProject.stem().string() + ".dll");
|
||||
|
||||
char hostExeBuf[MAX_PATH];
|
||||
DWORD hostExeLen = GetModuleFileNameA(nullptr, hostExeBuf, MAX_PATH);
|
||||
if (hostExeLen == 0 || hostExeLen == MAX_PATH) {
|
||||
throw std::runtime_error("GetModuleFileName failed");
|
||||
}
|
||||
fs::path hostExe(std::string(hostExeBuf, hostExeLen));
|
||||
|
||||
const char* envHome = std::getenv("CRAFTER_BUILD_HOME");
|
||||
fs::path sourceDir = envHome
|
||||
? fs::path(envHome)
|
||||
: hostExe.parent_path().parent_path() / "share" / "crafter-build";
|
||||
|
||||
Configuration hostConfig;
|
||||
hostConfig.target = "x86_64-pc-windows-msvc";
|
||||
hostConfig.march = "native";
|
||||
hostConfig.mtune = "native";
|
||||
fs::path cacheDir = GetCacheDir() / std::format("{}-{}", hostConfig.target, hostConfig.march);
|
||||
if (!fs::exists(cacheDir)) fs::create_directories(cacheDir);
|
||||
|
||||
std::string stdResult = BuildStdPcm(hostConfig, cacheDir / "std.pcm");
|
||||
if (!stdResult.empty()) {
|
||||
throw std::runtime_error(std::format("Failed to build std.pcm: {}", stdResult));
|
||||
}
|
||||
|
||||
EnsureCrafterBuildPcms(sourceDir, cacheDir);
|
||||
|
||||
bool stale = !fs::exists(dllPath)
|
||||
|| fs::last_write_time(dllPath) < fs::last_write_time(absProject)
|
||||
|| fs::last_write_time(dllPath) < fs::last_write_time(hostExe);
|
||||
|
||||
if (stale) {
|
||||
fs::path crafterBuildLib = hostExe.parent_path() / "crafter-build.lib";
|
||||
|
||||
std::string compileCmd = std::format(
|
||||
"clang++ --target=x86_64-pc-windows-msvc -march=native -mtune=native "
|
||||
"-std=c++26 -shared -O3 -Wno-return-type-c-linkage "
|
||||
"-nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1 "
|
||||
"-fprebuilt-module-path={} "
|
||||
"{} {} -o {} -L %LIBCXX_DIR%\\lib -lc++",
|
||||
cacheDir.string(),
|
||||
absProject.string(), crafterBuildLib.string(), dllPath.string());
|
||||
|
||||
std::string result = RunCommand(compileCmd);
|
||||
if (!result.empty()) {
|
||||
throw std::runtime_error(std::format("Failed to compile project {}: {}", absProject.string(), result));
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE handle = LoadLibraryA(dllPath.string().c_str());
|
||||
if (!handle) {
|
||||
throw std::runtime_error(std::format("Failed to load project {}: error {}", dllPath.string(), GetLastError()));
|
||||
}
|
||||
|
||||
using ProjectFn = Configuration (*)(std::span<const std::string_view>);
|
||||
auto fn = reinterpret_cast<ProjectFn>(GetProcAddress(handle, "CrafterBuildProject"));
|
||||
if (!fn) {
|
||||
throw std::runtime_error(std::format("CrafterBuildProject not found in {}: error {}", dllPath.string(), GetLastError()));
|
||||
}
|
||||
|
||||
return fn(args);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
|
||||
|
||||
std::string RunCommand(const std::string_view cmd) {
|
||||
std::string Crafter::RunCommand(const std::string_view cmd) {
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
|
||||
|
|
@ -90,7 +227,30 @@ std::string RunCommand(const std::string_view cmd) {
|
|||
return result;
|
||||
}
|
||||
|
||||
fs::path BuildStdPcm(Configuration& config, fs::path stdPcm) {
|
||||
CommandResult Crafter::RunCommandChecked(std::string_view cmd) {
|
||||
std::array<char, 128> buffer;
|
||||
CommandResult result{0, ""};
|
||||
|
||||
std::string with = std::string(cmd) + " 2>&1";
|
||||
FILE* pipe = popen(with.c_str(), "r");
|
||||
if (!pipe) throw std::runtime_error("popen() failed!");
|
||||
|
||||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
||||
result.output += buffer.data();
|
||||
}
|
||||
|
||||
int status = pclose(pipe);
|
||||
if (WIFEXITED(status)) {
|
||||
result.exitCode = WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
result.exitCode = 128 + WTERMSIG(status);
|
||||
} else {
|
||||
result.exitCode = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) {
|
||||
if(config.target == "x86_64-w64-mingw32") {
|
||||
std::vector<std::string> folders;
|
||||
|
||||
|
|
@ -111,57 +271,139 @@ fs::path BuildStdPcm(Configuration& config, fs::path stdPcm) {
|
|||
fs::path stdCc = fs::path(std::format("/usr/x86_64-w64-mingw32/include/c++/{}/bits/std.cc", mingWversion));
|
||||
|
||||
if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time(stdCc)) {
|
||||
std::string result = RunCommand(std::format("cp {} {}/{}{}/std.cppm\nclang++ --target={} -march={} -mtune={} -O3 -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/{}{}/std.cppm -o {}", stdCc.string(), exeDir.string(), config.target, config.march, config.target, config.march, config.mtune, exeDir.string(), config.target, config.march, stdPcm));
|
||||
if(!result.empty()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Iterate over the source directory
|
||||
for (const auto& entry : fs::directory_iterator("/usr/x86_64-w64-mingw32/bin/")) {
|
||||
// Check if the file is a regular file and ends with ".dll"
|
||||
if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") {
|
||||
// Construct the destination file path
|
||||
fs::path dest_file = project.binDir / config.name / entry.path().filename();
|
||||
|
||||
// Check if the destination file exists and if it is older than the source file
|
||||
if (!fs::exists(dest_file) || fs::last_write_time(entry.path()) > fs::last_write_time(dest_file)) {
|
||||
// Copy the file if it doesn't exist or is older
|
||||
fs::copy(entry.path(), dest_file, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
return e.what();
|
||||
return RunCommand(std::format("cp {} {}/std.cppm\nclang++ --target={} -march={} -mtune={} -O3 -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/std.cppm -o {}", stdCc.string(), stdPcm.parent_path().string(), config.target, config.march, config.mtune, stdPcm.parent_path().string(), stdPcm.string()));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time("/usr/share/libc++/v1/std.cppm")) {
|
||||
return RunCommand(std::format("clang++ --target={} -std=c++26 -stdlib=libc++ -march={} -mtune={} -O3 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile /usr/share/libc++/v1/std.cppm -o {}", config.target, config.march, config.mtune, stdPcm));
|
||||
return RunCommand(std::format("clang++ --target={} -std=c++26 -stdlib=libc++ -march={} -mtune={} -O3 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile /usr/share/libc++/v1/std.cppm -o {}", config.target, config.march, config.mtune, stdPcm.string()));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::path GetCacheDir() {
|
||||
fs::path Crafter::GetCacheDir() {
|
||||
if (const char* xdg = std::getenv("XDG_CACHE_HOME"); xdg && *xdg) {
|
||||
return fs::path(xdg) / "crafter.build";
|
||||
}
|
||||
|
||||
|
||||
if (const char* home = std::getenv("HOME")) {
|
||||
return fs::path(home) / ".cache" / "crafter.build";
|
||||
}
|
||||
|
||||
|
||||
throw std::runtime_error("Neither XDG_CACHE_HOME nor HOME set");
|
||||
}
|
||||
|
||||
std::string GetBaseCommand(Configuration& config) {
|
||||
std::string Crafter::GetBaseCommand(const Configuration& config) {
|
||||
std::string stdlib;
|
||||
if(config.target == "x86_64-w64-mingw32") {
|
||||
stdlib = "";
|
||||
} else {
|
||||
stdlib = "-stdlib=libc++";
|
||||
}
|
||||
std::string command = std::format("clang++ {}", stdlib);
|
||||
return std::format("clang++ {}", stdlib);
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::string_view, 7> kCrafterBuildModules = {
|
||||
"Crafter.Build-Shader",
|
||||
"Crafter.Build-Platform",
|
||||
"Crafter.Build-Interface",
|
||||
"Crafter.Build-Implementation",
|
||||
"Crafter.Build-External",
|
||||
"Crafter.Build-Clang",
|
||||
"Crafter.Build",
|
||||
};
|
||||
|
||||
void EnsureCrafterBuildPcms(const fs::path& sourceDir, const fs::path& cacheDir) {
|
||||
for (std::string_view name : kCrafterBuildModules) {
|
||||
fs::path cppmPath = sourceDir / (std::string(name) + ".cppm");
|
||||
fs::path pcmPath = cacheDir / (std::string(name) + ".pcm");
|
||||
if (!fs::exists(cppmPath)) {
|
||||
throw std::runtime_error(std::format("module source {} not found in {} (set CRAFTER_BUILD_HOME)", name, sourceDir.string()));
|
||||
}
|
||||
if (fs::exists(pcmPath) && fs::last_write_time(cppmPath) < fs::last_write_time(pcmPath)) {
|
||||
continue;
|
||||
}
|
||||
std::string cmd = std::format(
|
||||
"clang++ --target=x86_64-pc-linux-gnu -march=native -mtune=native "
|
||||
"-std=c++26 -stdlib=libc++ -O3 "
|
||||
"-Wno-reserved-identifier -Wno-reserved-module-identifier "
|
||||
"-fprebuilt-module-path={} "
|
||||
"--precompile {} -o {}",
|
||||
cacheDir.string(), cppmPath.string(), pcmPath.string());
|
||||
CommandResult r = Crafter::RunCommandChecked(cmd);
|
||||
if (r.exitCode != 0) {
|
||||
throw std::runtime_error(std::format("Failed to precompile {} (exit {}): {}", name, r.exitCode, r.output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const std::string_view> args) {
|
||||
fs::path absProject = fs::canonical(projectFile);
|
||||
fs::path buildDir = absProject.parent_path() / "build";
|
||||
if (!fs::exists(buildDir)) {
|
||||
fs::create_directories(buildDir);
|
||||
}
|
||||
fs::path soPath = buildDir / (absProject.stem().string() + ".so");
|
||||
|
||||
fs::path hostExe = fs::read_symlink("/proc/self/exe");
|
||||
|
||||
const char* envHome = std::getenv("CRAFTER_BUILD_HOME");
|
||||
fs::path sourceDir = envHome
|
||||
? fs::path(envHome)
|
||||
: hostExe.parent_path().parent_path() / "share" / "crafter-build";
|
||||
|
||||
Configuration hostConfig;
|
||||
hostConfig.target = "x86_64-pc-linux-gnu";
|
||||
hostConfig.march = "native";
|
||||
hostConfig.mtune = "native";
|
||||
fs::path cacheDir = GetCacheDir() / std::format("{}-{}", hostConfig.target, hostConfig.march);
|
||||
if (!fs::exists(cacheDir)) fs::create_directories(cacheDir);
|
||||
|
||||
std::string stdResult = BuildStdPcm(hostConfig, cacheDir / "std.pcm");
|
||||
if (!stdResult.empty()) {
|
||||
throw std::runtime_error(std::format("Failed to build std.pcm: {}", stdResult));
|
||||
}
|
||||
|
||||
EnsureCrafterBuildPcms(sourceDir, cacheDir);
|
||||
|
||||
bool stale = !fs::exists(soPath)
|
||||
|| fs::last_write_time(soPath) < fs::last_write_time(absProject)
|
||||
|| fs::last_write_time(soPath) < fs::last_write_time(hostExe);
|
||||
|
||||
if (stale) {
|
||||
std::string compileCmd = std::format(
|
||||
"clang++ --target=x86_64-pc-linux-gnu -march=native -mtune=native "
|
||||
"-std=c++26 -stdlib=libc++ -shared -fPIC -O3 "
|
||||
"-Wno-return-type-c-linkage "
|
||||
"-fprebuilt-module-path={} "
|
||||
"{} -o {}",
|
||||
cacheDir.string(),
|
||||
absProject.string(), soPath.string());
|
||||
|
||||
std::string result = RunCommand(compileCmd);
|
||||
if (!result.empty()) {
|
||||
throw std::runtime_error(std::format("Failed to compile project {}: {}", absProject.string(), result));
|
||||
}
|
||||
}
|
||||
|
||||
void* handle = dlopen(soPath.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle) {
|
||||
throw std::runtime_error(std::format("Failed to load project {}: {}", soPath.string(), dlerror()));
|
||||
}
|
||||
|
||||
using ProjectFn = Configuration (*)(std::span<const std::string_view>);
|
||||
dlerror();
|
||||
auto fn = reinterpret_cast<ProjectFn>(dlsym(handle, "CrafterBuildProject"));
|
||||
if (const char* err = dlerror()) {
|
||||
throw std::runtime_error(std::format("CrafterBuildProject not found in {}: {}", soPath.string(), err));
|
||||
}
|
||||
|
||||
return fn(args);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -27,14 +27,37 @@ import :Shader;
|
|||
import std;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
EShLanguage ToEShLanguage(Crafter::ShaderType t) {
|
||||
switch (t) {
|
||||
case Crafter::ShaderType::Vertex: return EShLangVertex;
|
||||
case Crafter::ShaderType::TessControl: return EShLangTessControl;
|
||||
case Crafter::ShaderType::TessEvaluation: return EShLangTessEvaluation;
|
||||
case Crafter::ShaderType::Geometry: return EShLangGeometry;
|
||||
case Crafter::ShaderType::Fragment: return EShLangFragment;
|
||||
case Crafter::ShaderType::Compute: return EShLangCompute;
|
||||
case Crafter::ShaderType::RayGen: return EShLangRayGen;
|
||||
case Crafter::ShaderType::Intersect: return EShLangIntersect;
|
||||
case Crafter::ShaderType::AnyHit: return EShLangAnyHit;
|
||||
case Crafter::ShaderType::ClosestHit: return EShLangClosestHit;
|
||||
case Crafter::ShaderType::Miss: return EShLangMiss;
|
||||
case Crafter::ShaderType::Callable: return EShLangCallable;
|
||||
case Crafter::ShaderType::Task: return EShLangTask;
|
||||
case Crafter::ShaderType::Mesh: return EShLangMesh;
|
||||
}
|
||||
return EShLangVertex;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crafter {
|
||||
Shader::Shader(fs::path&& path, std::string&& entrypoint, EShLanguage type) : path(std::move(path)), entrypoint(std::move(entrypoint)), type(type) {
|
||||
Shader::Shader(fs::path&& path, std::string&& entrypoint, ShaderType 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");
|
||||
}
|
||||
std::string Shader::Compile(const fs::path& outputDir) const {
|
||||
EShLanguage glslangType = ToEShLanguage(type);
|
||||
glslang::InitializeProcess();
|
||||
EShMessages messages = static_cast<EShMessages>(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules);
|
||||
std::ifstream fileStream(path, std::ios::in | std::ios::binary);
|
||||
|
|
@ -44,11 +67,11 @@ namespace Crafter {
|
|||
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);
|
||||
|
||||
glslang::TShader shader(glslangType);
|
||||
shader.setStringsWithLengthsAndNames(&shader_source, nullptr, file_name_list, 1);
|
||||
shader.setEntryPoint(entrypoint.c_str());
|
||||
shader.setSourceEntryPoint(entrypoint.c_str());
|
||||
|
|
@ -63,30 +86,30 @@ namespace Crafter {
|
|||
// 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);
|
||||
|
||||
glslang::TIntermediate* intermediate = program.getIntermediate(glslangType);
|
||||
if (!intermediate)
|
||||
{
|
||||
info_log += "Failed to get shared intermediate code.";
|
||||
}
|
||||
|
||||
|
||||
spv::SpvBuildLogger logger;
|
||||
std::vector<std::uint32_t> spirv;
|
||||
glslang::GlslangToSpv(*intermediate, spirv, &logger);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Crafter® Build
|
||||
Copyright (C) 2026 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
|
||||
*/
|
||||
|
||||
import std;
|
||||
import Crafter.Build;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return Crafter::Run(argc, argv);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue