Crafter.Build/implementations/Crafter.Build-Command.cpp
Jorijn van der Graaf c4686ae1cc
Some checks failed
demo.yaml / full windows build? (push) Failing after 0s
full windows build?
2026-03-01 14:57:47 +01:00

328 lines
No EOL
14 KiB
C++

/*
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;
#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>
#endif
module Crafter.Build:Command_impl;
import :Command;
import :Project;
import std;
namespace fs = std::filesystem;
namespace Crafter {
std::string RunCommand(const std::string_view cmd);
std::filesystem::path GetPath();
void BuildWasmStdPcm(const Project& project, const Configuration& config) {
fs::path exeDir = GetPath();
fs::create_directories(exeDir/config.target);
const std::string stdPcm = std::format("{}/wasi-sysroot-28.0/share/libc++/v1/std.pcm", exeDir.string());
fs::path stdCc = fs::path(std::format("{}/wasi-sysroot-28.0/share/libc++/v1/std.cppm", exeDir.string()));
if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time(stdCc)) {
std::string result = RunCommand(std::format("clang++ -fno-exceptions --target=wasm32-wasi -nodefaultlibs --sysroot={}/wasi-sysroot-28.0 -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier -fno-c++-static-destructors --precompile {} -o {}", exeDir.string(), stdCc.string(), stdPcm));
if(result != "") {
throw std::runtime_error(result);
}
}
const fs::path indexPath = std::format("{}/index.html", exeDir.string());
const fs::path indexDstPath = project.binDir/"index.html";
if(!fs::exists(indexDstPath)) {
fs::copy(indexPath, indexDstPath);
} else if(fs::last_write_time(indexDstPath) < fs::last_write_time(indexPath)) {
fs::remove(indexDstPath);
fs::copy(indexPath, indexDstPath);
}
const fs::path runtimePath = std::format("{}/runtime.js", exeDir.string());
const fs::path runtimeDstPath = project.binDir/"runtime.js";
if(!fs::exists(runtimeDstPath)) {
fs::copy(runtimePath, runtimeDstPath);
} else if(fs::last_write_time(runtimeDstPath) < fs::last_write_time(runtimePath)) {
fs::remove(runtimeDstPath);
fs::copy(runtimePath, runtimeDstPath);
}
}
#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::array<char, 128> buffer;
std::string result;
// Use cmd.exe to interpret redirection
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 += buffer.data();
}
_pclose(pipe);
return result;
}
void RunCommandIgnore(const std::string_view cmd) {
std::string with = "cmd /C \"" + std::string(cmd) + " > NUL 2>&1\"";
FILE* pipe = _popen(with.c_str(), "r");
if (!pipe) {
throw std::runtime_error("_popen() failed!");
}
_pclose(pipe);
}
std::filesystem::path GetPath() {
char path[MAX_PATH];
DWORD length = GetModuleFileNameA(NULL, path, MAX_PATH);
if (length == 0) {
throw std::runtime_error("Failed to get executable path");
}
path[length] = '\0';
return fs::path(path).parent_path().parent_path();
}
void BuildGnuStdPcm(const Project& project, const Configuration& config) {
throw std::runtime_error("target x86_64-pc-linux-gnu is not supported");
}
void BuildMingwStdPcm(const Project& project, const Configuration& config) {
throw std::runtime_error("target x86_64-w64-mingw32 is not supported");
}
void BuildMsvcStdPcm(const Project& project, const Configuration& config) {
fs::path exeDir = GetPath();
fs::create_directories(exeDir/config.target);
std::string stdPcm = std::format("{}\\{}\\std.pcm", exeDir.string(), config.target);
std::string stdcppm = std::format("{}\\{}\\std.cppm", exeDir.string(), config.target);
std::string vsPath = "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe";
std::string command = vsPath + " -latest -property installationPath";
std::string directoryPath = RunCommand("C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe -latest -property installationPath") + "\\VC\\Tools";
std::vector<std::string> folders;
// Iterate through the directory and collect all subdirectories
for (const auto& entry : fs::directory_iterator(directoryPath+"\\MSVC")) {
if (entry.is_directory()) {
folders.push_back(entry.path().filename().string());
}
}
// Sort the folders by version in descending order
std::sort(folders.begin(), folders.end(), [](const std::string& a, const std::string& b) {
return std::lexicographical_compare(b.begin(), b.end(), a.begin(), a.end());
});
std::string msvcVersion = folders.front();
clDir = std::format("{}\\{}\\bin\\Hostx64\\x64", directoryPath, msvcVersion);
clangClDir = std::format("{}\\Llvm\\x64\\bin", directoryPath);
std::string sourceFilePath = directoryPath + "\\" + msvcVersion + "\\modules\\std.ixx";
if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time(sourceFilePath)) {
fs::copy(sourceFilePath, stdcppm, fs::copy_options::overwrite_existing);
std::string result = RunCommand(std::format("cd {}\\{} && {}\\clang-cl.exe /EHsc /MD /std:c++latest --target=x86_64-pc-windows-msvc -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile std.cppm -o std.pcm", exeDir.string(), config.target, clangClDir, stdPcm));
if(result != "") {
throw std::runtime_error(result);
}
result = RunCommand(std::format("cd {}\\{} && {}\\cl.exe /std:c++latest /EHsc /nologo /W4 /MD /c {}", exeDir.string(), config.target, clDir, sourceFilePath));
}
}
#else
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);
}
std::filesystem::path GetPath() {
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';
return fs::path(path).parent_path().parent_path();
}
void BuildGnuStdPcm(const Project& project, const Configuration& config) {
fs::path exeDir = GetPath();
fs::create_directories(exeDir/config.target);
const std::string stdPcm = std::format("{}/{}/std.pcm", exeDir.string(), config.target);
std::string gccVersion = RunCommand("g++ -dumpversion");
gccVersion.pop_back();
fs::path stdCc = fs::path(std::format("/usr/include/c++/{}/bits/std.cc", gccVersion));
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={} -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/{}/std.cppm -o {}", stdCc.string(), exeDir.string(), config.target, config.target, exeDir.string(), config.target, stdPcm));
if(result != "") {
throw std::runtime_error(result);
}
}
}
void BuildMingwStdPcm(const Project& project, const Configuration& config) {
fs::path exeDir = GetPath();
fs::create_directories(exeDir/config.target);
const std::string stdPcm = std::format("{}/{}/std.pcm", exeDir.string(), config.target);
std::vector<std::string> folders;
// Iterate through the directory and collect all subdirectories
for (const auto& entry : fs::directory_iterator("/usr/x86_64-w64-mingw32/include/c++")) {
if (entry.is_directory()) {
folders.push_back(entry.path().filename().string());
}
}
// Sort the folders by version in descending order
std::sort(folders.begin(), folders.end(), [](const std::string& a, const std::string& b) {
return std::lexicographical_compare(b.begin(), b.end(), a.begin(), a.end());
});
std::string mingWversion = folders.front();
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={} -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile {}/{}/std.cppm -o {}", stdCc.string(), exeDir.string(), config.target, config.target, exeDir.string(), config.target, stdPcm));
if(result != "") {
throw std::runtime_error(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 / 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) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
void BuildMsvcStdPcm(const Project& project, const Configuration& config) {
throw std::runtime_error("target x86_64-pc-windows-msvc is not supported");
}
#endif
// 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 BuildStdPcm(const Project& project, const Configuration& config) {
if(config.target == "x86_64-pc-linux-gnu") {
BuildGnuStdPcm(project, config);
} else if(config.target == "x86_64-w64-mingw32") {
BuildMingwStdPcm(project, config);
} else if(config.target == "x86_64-pc-windows-msvc") {
BuildMsvcStdPcm(project, config);
} else if(config.target == "wasm32-wasi") {
BuildWasmStdPcm(project, config);
} else {
throw std::runtime_error(std::format("Unkown target: {}", config.target));
}
}
std::string RunClang(const std::string_view cmd) {
// std::string result = RunCommand(cmd);
// // 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);
// //}
// }
std::cout << cmd << std::endl;
return RunCommand(cmd);
}
}