v2 nearly done

This commit is contained in:
Jorijn van der Graaf 2026-04-27 07:04:42 +02:00
commit f13671b2be
24 changed files with 1467 additions and 314 deletions

View file

@ -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