diff --git a/PKGBUILD b/PKGBUILD index d7372ac..38299d1 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -29,7 +29,6 @@ package() { install -Dm644 lib/libcrafter-build.a "$pkgdir/usr/lib/libcrafter-build.a" install -dm755 "$pkgdir/usr/share/crafter-build" install -m644 share/crafter-build/*.cppm "$pkgdir/usr/share/crafter-build/" - install -m644 share/crafter-build/*.h "$pkgdir/usr/share/crafter-build/" install -dm755 "$pkgdir/usr/share/crafter-build/wasi-runtime" install -m644 share/crafter-build/wasi-runtime/runtime.js "$pkgdir/usr/share/crafter-build/wasi-runtime/" install -m644 share/crafter-build/wasi-runtime/index.html.in "$pkgdir/usr/share/crafter-build/wasi-runtime/" diff --git a/build.cmd b/build.cmd deleted file mode 100644 index a2f2fc5..0000000 --- a/build.cmd +++ /dev/null @@ -1,137 +0,0 @@ -@REM SPDX-License-Identifier: LGPL-3.0-only -@REM SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -@echo OFF -mkdir build 2>nul -mkdir bin 2>nul -mkdir lib 2>nul -mkdir share\crafter-build 2>nul -mkdir share\crafter-build\wasi-runtime 2>nul - -copy /Y wasi-runtime\runtime.js share\crafter-build\wasi-runtime\ -copy /Y wasi-runtime\index.html.in share\crafter-build\wasi-runtime\ - -copy /Y interfaces\Crafter.Build.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Shader.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Platform.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Interface.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Implementation.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-External.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Clang.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Test.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Lint.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Progress.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Asset.cppm share\crafter-build\ -copy /Y interfaces\Crafter.Build-Api.h share\crafter-build\ - -if not exist .\build\glslang\NUL git clone https://github.com/KhronosGroup/glslang.git .\build\glslang - -set useLibcCommon=-nostdinc++ -nostdlib++ -set useLibcLinker=-L %LIBCXX_DIR%\lib -lc++ %useLibcCommon% -set useLibcSource=-isystem %LIBCXX_DIR%\include\c++\v1 %useLibcCommon% -set buildDir=%CD%\build - -if "%CRAFTER_BUILD_MARCH%"=="" set CRAFTER_BUILD_MARCH=native -if "%CRAFTER_BUILD_MTUNE%"=="" set CRAFTER_BUILD_MTUNE=native - -cd .\build\glslang -cmake -G Ninja -B build ^ - -DCMAKE_C_COMPILER=clang ^ - -DCMAKE_CXX_COMPILER=clang++ ^ - -DCMAKE_CXX_FLAGS="%useLibcSource%" ^ - -DCMAKE_EXE_LINKER_FLAGS="%useLibcLinker%" ^ - -DCMAKE_SHARED_LINKER_FLAGS="%useLibcLinker%" ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="%buildDir%" ^ - -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="%buildDir%" ^ - -DENABLE_OPT=OFF -cmake --build build --config Release -cd ..\..\ - -set common_options=-I.\build\glslang -std=c++26 -O3 -march=%CRAFTER_BUILD_MARCH% -mtune=%CRAFTER_BUILD_MTUNE% -fprebuilt-module-path=.\build -D CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE -D _CRT_SECURE_NO_WARNINGS %useLibcSource% -c - -clang++ %useLibcSource% -std=c++26 -march=%CRAFTER_BUILD_MARCH% -mtune=%CRAFTER_BUILD_MTUNE% -O3 -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile %LIBCXX_DIR%\modules\c++\v1\std.cppm -o .\build\std.pcm - -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Shader.cppm -o .\build\Crafter.Build-Shader.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Platform.cppm -o .\build\Crafter.Build-Platform.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Interface.cppm -o .\build\Crafter.Build-Interface.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Implementation.cppm -o .\build\Crafter.Build-Implementation.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-External.cppm -o .\build\Crafter.Build-External.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Clang.cppm -o .\build\Crafter.Build-Clang.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Test.cppm -o .\build\Crafter.Build-Test.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Lint.cppm -o .\build\Crafter.Build-Lint.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Progress.cppm -o .\build\Crafter.Build-Progress.o -REM Asset partition: bootstrap compiles it WITHOUT CRAFTER_BUILD_HAS_ASSET so -REM CompressAsset takes the stub branch (no Crafter.Asset PCM exists yet). -clang++ %common_options% -fmodule-output interfaces\Crafter.Build-Asset.cppm -o .\build\Crafter.Build-Asset.o -clang++ %common_options% -fmodule-output interfaces\Crafter.Build.cppm -o .\build\Crafter.Build.o - -clang++ %common_options% .\implementations\Crafter.Build-Shader.cpp -o .\build\Crafter.Build-Shader_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Platform.cpp -o .\build\Crafter.Build-Platform_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Interface.cpp -o .\build\Crafter.Build-Interface_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Implementation.cpp -o .\build\Crafter.Build-Implementation_impl.o -clang++ %common_options% .\implementations\Crafter.Build-External.cpp -o .\build\Crafter.Build-External_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Clang.cpp -o .\build\Crafter.Build-Clang_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Test.cpp -o .\build\Crafter.Build-Test_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Lint.cpp -o .\build\Crafter.Build-Lint_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Progress.cpp -o .\build\Crafter.Build-Progress_impl.o -clang++ %common_options% .\implementations\Crafter.Build-Asset.cpp -o .\build\Crafter.Build-Asset_impl.o -clang++ %common_options% .\implementations\main.cpp -o .\build\main.o - -REM Step 1: link all impl .o files into crafter-build.dll, generating crafter-build.lib import lib -clang++ %useLibcLinker% -shared -std=c++26 -O3 -march=%CRAFTER_BUILD_MARCH% -mtune=%CRAFTER_BUILD_MTUNE% -L.\build -fuse-ld=lld ^ - -Wl,/IMPLIB:.\bin\crafter-build.lib ^ - -lSPIRV -lGenericCodeGen -lglslang -lOSDependent -lMachineIndependent -lglslang-default-resource-limits ^ - .\build\Crafter.Build-Shader.o ^ - .\build\Crafter.Build-Platform.o ^ - .\build\Crafter.Build-Interface.o ^ - .\build\Crafter.Build-Implementation.o ^ - .\build\Crafter.Build-External.o ^ - .\build\Crafter.Build-Clang.o ^ - .\build\Crafter.Build-Test.o ^ - .\build\Crafter.Build-Lint.o ^ - .\build\Crafter.Build-Progress.o ^ - .\build\Crafter.Build-Asset.o ^ - .\build\Crafter.Build.o ^ - .\build\Crafter.Build-Shader_impl.o ^ - .\build\Crafter.Build-Platform_impl.o ^ - .\build\Crafter.Build-Interface_impl.o ^ - .\build\Crafter.Build-Implementation_impl.o ^ - .\build\Crafter.Build-External_impl.o ^ - .\build\Crafter.Build-Clang_impl.o ^ - .\build\Crafter.Build-Test_impl.o ^ - .\build\Crafter.Build-Lint_impl.o ^ - .\build\Crafter.Build-Progress_impl.o ^ - .\build\Crafter.Build-Asset_impl.o ^ - -o .\bin\crafter-build.dll - -REM Step 2: link the launcher exe against crafter-build.lib -clang++ %useLibcLinker% -std=c++26 -O3 -march=%CRAFTER_BUILD_MARCH% -mtune=%CRAFTER_BUILD_MTUNE% -L.\bin -fuse-ld=lld ^ - .\build\main.o ^ - .\bin\crafter-build.lib ^ - -o .\bin\crafter-build.exe - -REM Step 3: bundle the same .o set as a static archive for downstream consumers -llvm-lib.exe /OUT:.\lib\crafter-build-static.lib ^ - .\build\Crafter.Build-Shader.o ^ - .\build\Crafter.Build-Platform.o ^ - .\build\Crafter.Build-Interface.o ^ - .\build\Crafter.Build-Implementation.o ^ - .\build\Crafter.Build-External.o ^ - .\build\Crafter.Build-Clang.o ^ - .\build\Crafter.Build-Test.o ^ - .\build\Crafter.Build-Lint.o ^ - .\build\Crafter.Build-Progress.o ^ - .\build\Crafter.Build-Asset.o ^ - .\build\Crafter.Build.o ^ - .\build\Crafter.Build-Shader_impl.o ^ - .\build\Crafter.Build-Platform_impl.o ^ - .\build\Crafter.Build-Interface_impl.o ^ - .\build\Crafter.Build-Implementation_impl.o ^ - .\build\Crafter.Build-External_impl.o ^ - .\build\Crafter.Build-Clang_impl.o ^ - .\build\Crafter.Build-Test_impl.o ^ - .\build\Crafter.Build-Lint_impl.o ^ - .\build\Crafter.Build-Progress_impl.o ^ - .\build\Crafter.Build-Asset_impl.o - -copy /Y "%LIBCXX_DIR%\lib\c++.dll" ".\bin\c++.dll" diff --git a/build.sh b/build.sh index 5ff6fb8..f0c3083 100755 --- a/build.sh +++ b/build.sh @@ -21,7 +21,6 @@ cp interfaces/Crafter.Build-Test.cppm share/crafter-build/ cp interfaces/Crafter.Build-Lint.cppm share/crafter-build/ cp interfaces/Crafter.Build-Progress.cppm share/crafter-build/ cp interfaces/Crafter.Build-Asset.cppm share/crafter-build/ -cp interfaces/Crafter.Build-Api.h share/crafter-build/ git clone https://github.com/KhronosGroup/glslang.git ./build/glslang diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index 8e34387..6aa9307 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -2,17 +2,9 @@ // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module; -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) -#include -#include -// (pulled in transitively) defines `interface` as a macro for `struct`, -// which collides with local variables named `interface` in this TU. -#undef interface -#else #include #include #include -#endif export module Crafter.Build:Clang_impl; import std; import :Clang; @@ -267,17 +259,19 @@ fs::path Crafter::StdPcmDir(const Configuration& config) { fs::path Crafter::ExpectedOutput(const Configuration& config) { const fs::path dir = config.BinDir(); - const bool windowsTarget = config.target == "x86_64-w64-mingw32" || config.target == "x86_64-pc-windows-msvc"; + const bool mingwTarget = config.target == "x86_64-w64-mingw32"; if (config.type == ConfigurationType::Executable) { if (config.target.starts_with("wasm32")) return dir / std::format("{}.wasm", config.outputName); - return config.target == "x86_64-w64-mingw32" ? dir / std::format("{}.exe", config.outputName) : dir / config.outputName; + return mingwTarget ? dir / std::format("{}.exe", config.outputName) : dir / config.outputName; } if (config.type == ConfigurationType::LibraryStatic) { - return windowsTarget ? dir / std::format("{}.lib", config.outputName) : dir / std::format("lib{}.a", config.outputName); + // The archive step writes lib.a for every target, mingw included: + // llvm-ar does not care about the object format. + return dir / std::format("lib{}.a", config.outputName); } - // LibraryDynamic — the .dll on Windows targets so an mtime check sees the + // LibraryDynamic — the .dll on the mingw target so an mtime check sees the // freshly linked library; on Unix the .so is that file. - return windowsTarget ? dir / std::format("{}.dll", config.outputName) : dir / std::format("lib{}.so", config.outputName); + return mingwTarget ? dir / std::format("{}.dll", config.outputName) : dir / std::format("lib{}.so", config.outputName); } CompileCommand Crafter::GetCompileCommand(const Configuration& config) { @@ -322,22 +316,9 @@ CompileCommand Crafter::GetCompileCommand(const Configuration& config) { } if(config.type == ConfigurationType::LibraryDynamic) { - #ifdef __linux__ out.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) - out.command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_SHARED_LIBRARY"; - #endif } else if(config.type == ConfigurationType::Executable) { out.command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE"; - // On Windows targets the API uses __declspec(dllimport) when consuming - // a DLL. Set the macro for executables so CRAFTER_API resolves to - // dllimport in their PCM cache (separate from the lib's PCM cache, - // which gets dllexport). Harmless if the exe doesn't actually link a - // crafter DLL — CRAFTER_API only matters at API call sites. - if (config.target == "x86_64-w64-mingw32" || config.target == "x86_64-pc-windows-msvc") { - out.command += " -D CRAFTER_BUILD_DLL_IMPORT"; - } } else { out.command += " -D CRAFTER_BUILD_CONFIGURATION_TYPE_LIBRARY"; } @@ -464,15 +445,12 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map libSet; @@ -1125,7 +1102,6 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map copyDepDlls = [&](Configuration* dep) { if (!dllSeen.insert(dep).second) return; if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") { - fs::path depDir = dep->BinDir(); - // The DLL itself (Windows resolves it from the - // exe's directory at load time) and the mingw - // import lib (so a downstream `crafter-build.exe` - // can link a fresh project.dll against it without - // hunting through sibling output dirs). - for (auto fname : {std::format("{}.dll", dep->outputName), std::format("lib{}.dll.a", dep->outputName)}) { - fs::path src = depDir / fname; - if (!fs::exists(src)) continue; + // Only the DLL: Windows resolves it from the exe's + // directory at load time. The import lib stays in + // the dependency's own bin dir, which is already + // on the link line through buildResult.libs. + fs::path src = dep->BinDir() / std::format("{}.dll", dep->outputName); + if (fs::exists(src)) { fs::path dest = outputDir / src.filename(); if (!fs::exists(dest) || fs::last_write_time(src) > fs::last_write_time(dest)) { fs::copy(src, dest, fs::copy_options::overwrite_existing); @@ -1163,65 +1136,22 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map dllSeen; - std::function copyDepDlls = [&](Configuration* dep) { - if (!dllSeen.insert(dep).second) return; - if (dep->type == ConfigurationType::LibraryDynamic && dep->target == "x86_64-w64-mingw32") { - fs::path depDir = dep->BinDir(); - for (auto fname : {std::format("{}.dll", dep->outputName), std::format("lib{}.dll.a", dep->outputName)}) { - fs::path src = depDir / fname; - if (!fs::exists(src)) continue; - fs::path dest = outputDir / src.filename(); - if (!fs::exists(dest) || fs::last_write_time(src) > fs::last_write_time(dest)) { - fs::copy(src, dest, fs::copy_options::overwrite_existing); - } - } - } - for (Configuration* sub : dep->dependencies) copyDepDlls(sub); - }; - for (Configuration* dep : config.dependencies) copyDepDlls(dep); - - buildResult.result = RunBuildCommand(std::format("{}{} -o {} -fuse-ld=lld{}", command, files, (outputDir/config.outputName).string(), linkExtras)); - } else { - std::system(std::format("copy \"%LIBCXX_DIR%\\lib\\c++.dll\" \"{}\\c++.dll\"", outputDir.string()).c_str()); - buildResult.result = RunBuildCommand(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 == ConfigurationType::LibraryStatic) { - #ifdef __linux__ // ThinLTO emits LLVM bitcode objects; plain `ar` writes an archive // index that omits their symbols, so a consumer's lld link can't - // pull the needed members. llvm-ar writes a bitcode-aware index. - buildResult.result = RunBuildCommand(std::format("{} rcs {}.a {}", useLto ? "llvm-ar" : "ar", (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 = RunBuildCommand(std::format("llvm-lib.exe {} /OUT:{}.lib", files, (outputDir/fs::path(config.outputName)).string())); - #endif + // pull the needed members. llvm-ar writes a bitcode-aware index + // and handles plain ELF and COFF objects just as well, so every + // build uses it. + buildResult.result = RunBuildCommand(std::format("llvm-ar rcs {}.a {}", (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files)); } else { // LibraryDynamic. Output names follow each target's convention so // consumers can link via the standard linker search: // mingw: .dll + lib.dll.a (lld --out-implib) - // msvc: .dll + .lib (lld /IMPLIB) // unix: lib.so (rpath $ORIGIN) if (config.target == "x86_64-w64-mingw32") { fs::path dll = outputDir / std::format("{}.dll", config.outputName); fs::path implib = outputDir / std::format("lib{}.dll.a", config.outputName); buildResult.result = RunBuildCommand(std::format("{}{} -shared -o {} -Wl,--out-implib,{} -fuse-ld=lld{}", command, files, dll.string(), implib.string(), linkExtras)); - } else if (config.target == "x86_64-pc-windows-msvc") { - fs::path dll = outputDir / std::format("{}.dll", config.outputName); - fs::path implib = outputDir / std::format("{}.lib", config.outputName); - buildResult.result = RunBuildCommand(std::format("{}{} -shared -o {} -Wl,/IMPLIB:{} -fuse-ld=lld{}", command, files, dll.string(), implib.string(), linkExtras)); } else { buildResult.result = RunBuildCommand(std::format("{}{} -shared -o {}.so -Wl,-rpath,'$ORIGIN' -fuse-ld=lld{}", command, files, (outputDir/(std::string("lib")+config.outputName)).string(), linkExtras)); } @@ -1544,11 +1474,9 @@ ArgQuery Crafter::ApplyStandardArgs(Configuration& cfg, std::span NUL 2>&1", exe); -#else std::string probe = std::format("command -v {} > /dev/null 2>&1", exe); -#endif return std::system(probe.c_str()) == 0; }; if (browserBuild) { @@ -1801,17 +1724,9 @@ std::int32_t Crafter::Run(std::int32_t argc, char** argv) { // user has another dev server running we shift up rather // than letting caddy/python exit with EADDRINUSE. auto findFreePort = [](std::int32_t basePort, std::int32_t span) -> std::int32_t { -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) - static const bool WsaInit = []{ WSADATA d; return WSAStartup(MAKEWORD(2, 2), &d) == 0; }(); - if (!WsaInit) return basePort; - using Sock = SOCKET; - const Sock invalid = INVALID_SOCKET; - auto closesock = [](Sock s){ closesocket(s); }; -#else using Sock = std::int32_t; const Sock invalid = -1; auto closesock = [](Sock s){ ::close(s); }; -#endif for (std::int32_t p = basePort; p < basePort + span; ++p) { Sock s = ::socket(AF_INET, SOCK_STREAM, 0); if (s == invalid) return basePort; @@ -1934,11 +1849,7 @@ std::int32_t Crafter::Run(std::int32_t argc, char** argv) { // Silence the backend's own banner/request logs so the // experience is identical regardless of which server is // picked — the line above is the only output. -#ifdef _WIN32 - cmd += " > NUL 2>&1"; -#else cmd += " > /dev/null 2>&1"; -#endif return std::system(cmd.c_str()) == 0 ? 0 : 1; } // wasi-cli wasm — needs a standalone runtime. @@ -1952,10 +1863,6 @@ std::int32_t Crafter::Run(std::int32_t argc, char** argv) { return 1; } - // Resolve to absolute — cmd.exe on Windows mishandles a leading - // "./" by trying to interpret it as a command. system() invokes - // through cmd /c, so the relative-prefixed path makes cmd error - // with "'.' is not recognized as an internal or external command". // Run from the artifact's own directory so relative file opens // (shaders, assets copied alongside the exe via cfg.files) resolve // against the bin dir rather than the user's cwd. We exit the diff --git a/implementations/Crafter.Build-External.cpp b/implementations/Crafter.Build-External.cpp index ac97844..c758a67 100644 --- a/implementations/Crafter.Build-External.cpp +++ b/implementations/Crafter.Build-External.cpp @@ -21,18 +21,6 @@ std::string DeriveName(const GitSource& source) { } std::string ShellQuote(std::string_view s) { - // Windows cmd treats single quotes as literal characters — only double - // quotes work. Paths don't contain " in practice, so a simple wrap - // suffices; backslashes inside the path stay backslashes. -#ifdef _WIN32 - std::string out = "\""; - for (char c : s) { - if (c == '"') out += "\\\""; - else out += c; - } - out += "\""; - return out; -#else std::string out = "'"; for (char c : s) { if (c == '\'') out += "'\\''"; @@ -40,7 +28,6 @@ std::string ShellQuote(std::string_view s) { } out += "'"; return out; -#endif } std::string JoinOptions(std::span options) { @@ -133,17 +120,14 @@ std::string BuildInjectedCMakeFlags(const fs::path& cmakeBuildDir, std::string_v out += " -DCMAKE_BUILD_TYPE=Release"; out += " -DCMAKE_C_COMPILER=clang"; out += " -DCMAKE_CXX_COMPILER=clang++"; - // Stdlib choice: libc++ for the Linux host build (matches how crafter-build - // itself is built with -stdlib=libc++); mingw cross-compile uses libstdc++ - // shipped by the mingw-w64 toolchain so we leave the default; Windows - // native (msvc target) similarly uses libc++ via LIBCXX_DIR. - #ifdef __linux__ + // Stdlib choice: libc++ for Linux targets (matches how crafter-build + // itself is built with -stdlib=libc++); the mingw cross-compile uses the + // libstdc++ shipped by the mingw-w64 toolchain, so it keeps the default. if (target.ends_with("-linux-gnu") || target.ends_with("-linux-musl")) { out += " -DCMAKE_CXX_FLAGS=-stdlib=libc++"; out += " -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++"; out += " -DCMAKE_SHARED_LINKER_FLAGS=-stdlib=libc++"; } - #endif if (target == "x86_64-w64-mingw32") { out += " -DCMAKE_SYSTEM_NAME=Windows"; out += std::format(" -DCMAKE_C_COMPILER_TARGET={}", target); @@ -277,9 +261,9 @@ ExternalBuildResult Crafter::BuildExternal(const ExternalDependency& dep, std::s // (runtime-loader). The rpath stays embedded in the produced // binary so it picks up shared deps from the cache without any // LD_LIBRARY_PATH gymnastics. Static deps (.a) ignore the rpath - // harmlessly. PE/COFF targets (mingw, msvc) resolve DLLs via PATH - // or adjacency rather than rpath, so lld warns if we pass it. - const bool isPe = target == "x86_64-w64-mingw32" || target == "x86_64-pc-windows-msvc"; + // harmlessly. The PE/COFF target (mingw) resolves DLLs via PATH or + // adjacency rather than rpath, so lld warns if we pass it. + const bool isPe = target == "x86_64-w64-mingw32"; std::string buildDirAbs = fs::absolute(cmakeBuildDir).string(); result.linkFlags.push_back(std::format("-L{}", buildDirAbs)); if (!isPe) result.linkFlags.push_back(std::format("-Wl,-rpath,{}", buildDirAbs)); diff --git a/implementations/Crafter.Build-Lint.cpp b/implementations/Crafter.Build-Lint.cpp index 72cf5f3..a2c5308 100644 --- a/implementations/Crafter.Build-Lint.cpp +++ b/implementations/Crafter.Build-Lint.cpp @@ -3,11 +3,7 @@ module; #include -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) -#include -#else #include -#endif export module Crafter.Build:Lint_impl; import std; import :Lint; @@ -20,20 +16,13 @@ using namespace Crafter; namespace { // ---------------- libclang ---------------- // - // libclang is loaded at runtime rather than linked. Linking -lclang would - // break the mingw and MSVC cross-builds at link time and would put a - // libclang.so.NN runtime dependency into the otherwise self-contained - // release tarballs; the clang-c header is used for its declarations only, - // and every call goes through a pointer resolved here. Failure to load is + // libclang is loaded at runtime rather than linked, so a plain build never + // needs it and the binary carries no libclang.so.NN loader dependency; the + // clang-c header is used for its declarations only, and every call goes + // through a pointer resolved here. Failure to load is // a hard error surfaced once by RunLint — there is deliberately no second, // weaker lexer to fall back to, because two engines disagreeing about what // is a comment is a worse failure than not running. -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) - using LibHandle = HMODULE; - LibHandle OpenLibrary(const std::string& name) { return LoadLibraryA(name.c_str()); } - void* LibrarySymbol(LibHandle handle, const std::string& name) { return reinterpret_cast(GetProcAddress(handle, name.c_str())); } - constexpr std::string_view LibClangNames[] = {"libclang.dll", "clang.dll"}; -#else using LibHandle = void*; LibHandle OpenLibrary(const std::string& name) { return dlopen(name.c_str(), RTLD_NOW | RTLD_LOCAL); } void* LibrarySymbol(LibHandle handle, const std::string& name) { return dlsym(handle, name.c_str()); } @@ -41,7 +30,6 @@ namespace { "libclang.so", "libclang.so.22.1", "libclang.so.21.1", "libclang.so.20.1", "libclang.so.1", "libclang.dylib", }; -#endif // Signatures come from decltype on the header's declarations, so they can // never drift from the real API. decltype is unevaluated, so naming the @@ -104,8 +92,8 @@ namespace { LibClang LoadLibClang() { LibClang lib; std::vector tried; - // CRAFTER_BUILD_LIBCLANG pins an exact path, mirroring the LIBCXX_DIR / - // CRAFTER_MINGW_DIR overrides used elsewhere. It is exclusive: pointing + // CRAFTER_BUILD_LIBCLANG pins an exact path, mirroring the + // CRAFTER_MINGW_DIR override used elsewhere. It is exclusive: pointing // it at a broken path must fail loudly rather than quietly succeed with // some other libclang, or the override is useless for diagnosing which // library is actually in play. @@ -377,8 +365,8 @@ namespace { } if (!args.empty()) args.erase(args.begin()); // argv[0] is the compiler for (std::string& arg : args) { - // Only \" unescapes. Erasing every backslash would destroy the - // Windows include paths GetBaseCommand puts on the command line. + // Only \" unescapes; a backslash anywhere else is part of the + // argument. std::string unescaped; unescaped.reserve(arg.size()); for (std::size_t i = 0; i < arg.size(); ++i) { diff --git a/implementations/Crafter.Build-Platform.cpp b/implementations/Crafter.Build-Platform.cpp index 5ffaa17..12f98a6 100644 --- a/implementations/Crafter.Build-Platform.cpp +++ b/implementations/Crafter.Build-Platform.cpp @@ -2,20 +2,15 @@ // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module; -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) -#include -#endif #include #include #include -#ifdef __linux__ #include #include #include #include #include #include -#endif module Crafter.Build:Platform_impl; import std; import :Platform; @@ -43,7 +38,6 @@ namespace { // descriptor is close-on-exec / non-inheritable so spawned clang processes // don't keep the lock alive past our own release. class CacheLock { -#ifdef __linux__ std::int32_t fd_ = -1; public: explicit CacheLock(const fs::path& cacheDir) { @@ -57,22 +51,6 @@ namespace { ::close(fd_); // closing the descriptor drops the flock } } -#else - HANDLE handle_ = INVALID_HANDLE_VALUE; - public: - explicit CacheLock(const fs::path& cacheDir) { - handle_ = CreateFileW((cacheDir / ".lock").wstring().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (handle_ != INVALID_HANDLE_VALUE) { - OVERLAPPED ov{}; - LockFileEx(handle_, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &ov); - } - } - ~CacheLock() { - if (handle_ != INVALID_HANDLE_VALUE) { - CloseHandle(handle_); // closing the handle drops the lock - } - } -#endif CacheLock(const CacheLock&) = delete; CacheLock& operator=(const CacheLock&) = delete; }; @@ -104,9 +82,6 @@ namespace { for (std::string_view name : moduleNames) { append(fs::path(std::format("{}.cppm", name))); } - // Exported through `module;` preambles, so its contents land in the PCMs - // too. - append("Crafter.Build-Api.h"); return std::format("{:016x}", std::hash{}(all)); } @@ -130,16 +105,7 @@ fs::path Crafter::GetCrafterBuildHome() { if (const char* envHome = std::getenv("CRAFTER_BUILD_HOME")) { return fs::path(envHome); } -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) - char buf[MAX_PATH]; - DWORD len = GetModuleFileNameA(nullptr, buf, MAX_PATH); - if (len == 0 || len == MAX_PATH) { - throw std::runtime_error("GetModuleFileName failed"); - } - fs::path hostExe(std::string(buf, len)); -#else fs::path hostExe = fs::read_symlink("/proc/self/exe"); -#endif // Walk up from the exe's directory looking for share/crafter-build with // a known module source present as a sentinel. Handles: @@ -560,499 +526,6 @@ bool Crafter::IsTopLevelProject() { return LoadingFrames.size() <= 1; } -#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) -CommandResult Crafter::RunCommandChecked(std::string_view cmd) { - std::array buffer; - CommandResult result{}; - - std::string with = std::format("cmd /C \"{} 2>&1\"", std::string(cmd)); - - FILE* pipe = _popen(with.c_str(), "r"); - if (!pipe) { - throw std::runtime_error("_popen() failed!"); - } - - while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != nullptr) { - result.output += buffer.data(); - } - - result.exitCode = _pclose(pipe); - return result; -} - -CommandResult Crafter::RunCommandWithTimeout(std::string_view cmd, std::chrono::seconds timeout) { - CommandResult result{}; - - SECURITY_ATTRIBUTES sa{ sizeof(sa), nullptr, TRUE }; - HANDLE readEnd = nullptr; - HANDLE writeEnd = nullptr; - if (!CreatePipe(&readEnd, &writeEnd, &sa, 0)) { - throw std::runtime_error("CreatePipe failed"); - } - SetHandleInformation(readEnd, HANDLE_FLAG_INHERIT, 0); - - // KILL_ON_JOB_CLOSE so the cmd.exe wrapper plus whatever it spawned - // (the test binary, ssh, etc.) all die when the job handle goes away — - // that's how we enforce the timeout reliably across the whole tree. - HANDLE job = CreateJobObjectA(nullptr, nullptr); - if (!job) { - CloseHandle(readEnd); - CloseHandle(writeEnd); - throw std::runtime_error("CreateJobObject failed"); - } - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli{}; - jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)); - - STARTUPINFOA si{}; - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdOutput = writeEnd; - si.hStdError = writeEnd; - si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - - PROCESS_INFORMATION pi{}; - // cmd /C "...": gives users the same shell-syntax surface (redirects, - // &&, ||) as popen("/bin/sh -c …") does on Linux. cmd's special-case - // /C parsing strips the outer quote pair when the inner text contains - // additional quotes, which is the common case here. - std::string wrapped = std::format("cmd /C \"{}\"", std::string(cmd)); - std::vector cmdBuf(wrapped.begin(), wrapped.end()); - cmdBuf.push_back('\0'); - - BOOL ok = CreateProcessA(nullptr, cmdBuf.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED, nullptr, nullptr, &si, &pi); - if (!ok) { - CloseHandle(readEnd); - CloseHandle(writeEnd); - CloseHandle(job); - throw std::runtime_error("CreateProcess failed"); - } - - AssignProcessToJobObject(job, pi.hProcess); - ResumeThread(pi.hThread); - // Drop our writer ref so the read end sees EOF once the child (its - // own dup of writeEnd) exits. - CloseHandle(writeEnd); - - std::string output; - std::jthread reader([&output, readEnd]() { - char buf[4096]; - DWORD n = 0; - while (ReadFile(readEnd, buf, sizeof(buf), &n, nullptr) && n > 0) { - output.append(buf, n); - } - }); - - DWORD waitMs = static_cast(std::min(static_cast(timeout.count()) * 1000LL, static_cast(INFINITE) - 1)); - DWORD waitResult = WaitForSingleObject(pi.hProcess, waitMs); - - if (waitResult == WAIT_TIMEOUT) { - TerminateJobObject(job, 1); - WaitForSingleObject(pi.hProcess, INFINITE); - result.timedOut = true; - result.exitCode = 124; - } else { - DWORD exit = 0; - GetExitCodeProcess(pi.hProcess, &exit); - // NTSTATUS error severity (top two bits set) means the process - // terminated by exception — STATUS_ACCESS_VIOLATION 0xC0000005, - // STATUS_STACK_OVERFLOW 0xC00000FD, etc. Surface those as crashes - // so the runner shows 💥 instead of a numeric exit code. - if ((exit & 0xC0000000) == 0xC0000000) { - result.crashed = true; - result.signal = static_cast(exit); - } - result.exitCode = static_cast(exit); - } - - reader.join(); - CloseHandle(readEnd); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - CloseHandle(job); - - result.output = std::move(output); - return result; -} - -fs::path Crafter::GetCacheDir() { - if (const char* local = std::getenv("LOCALAPPDATA")) { - return fs::path(local) / "crafter.build"; - } - - throw std::runtime_error("LOCALAPPDATA not set"); -} - -namespace { - constexpr std::array CrafterBuildModules = { - "Crafter.Build-Shader", - "Crafter.Build-Platform", - "Crafter.Build-Interface", - "Crafter.Build-Implementation", - "Crafter.Build-External", - "Crafter.Build-Clang", - "Crafter.Build-Test", - "Crafter.Build-Lint", - "Crafter.Build-Progress", - "Crafter.Build-Asset", - "Crafter.Build", - }; -} -#endif - -#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc -// Native Windows build via build.cmd: MSVC ABI + libc++ from %LIBCXX_DIR%. - -std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) { - std::string libcxx = std::getenv("LIBCXX_DIR"); - std::string stdcppm = std::format("{}\\modules\\c++\\v1\\std.cppm", libcxx); - - CacheLock lock(stdPcm.parent_path()); - if(!fs::exists(stdPcm) || fs::last_write_time(stdPcm) < fs::last_write_time(stdcppm)) { - return RunBuildCommand(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 ""; -} - -std::string Crafter::GetBaseCommand(const Configuration& config) { - return std::format("clang++ -nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1"); -} - -namespace { - void EnsureCrafterBuildPcms(const fs::path& sourceDir, const fs::path& cacheDir) { - CacheLock lock(cacheDir); - // Re-derived under the lock: another builder may have refreshed the - // cache from its own sources while we waited. - std::string stamp = CrafterBuildSourceStamp(sourceDir, CrafterBuildModules); - bool upToDate = ReadCacheStamp(cacheDir) == stamp; - for (std::string_view name : CrafterBuildModules) { - fs::path cppmPath = sourceDir / std::format("{}.cppm", name); - fs::path pcmPath = cacheDir / std::format("{}.pcm", name); - if (!fs::exists(cppmPath)) { - throw std::runtime_error(std::format("module source {} not found in {} (set CRAFTER_BUILD_HOME)", name, sourceDir.string())); - } - if (upToDate && fs::exists(pcmPath)) { - continue; - } - std::string cmd = std::format( - "clang++ --target=x86_64-pc-windows-msvc -march=native -mtune=native " - "-std=c++26 -O3 -D CRAFTER_BUILD_DLL_IMPORT " - "-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)); - } - } - // Last, so an interrupted rebuild leaves the stamp disagreeing with the - // sources and the next run starts over rather than trusting a half-built - // set of PCMs. - if (!upToDate) WriteCacheStamp(cacheDir, stamp); - } -} - -Configuration Crafter::LoadProject(const fs::path& projectFile, std::span args) { - fs::path absProject = fs::canonical(projectFile); - ProjectFileGuard guard(absProject); - fs::path buildDir = absProject.parent_path() / "build"; - if (!fs::exists(buildDir)) { - fs::create_directories(buildDir); - } - fs::path dllPath = buildDir / std::format("{}.dll", absProject.stem().string()); - - 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)); - - fs::path sourceDir = Crafter::GetCrafterBuildHome(); - - 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); - - // See the Linux LoadProject for why this sits here and runs every time. - ProjectFileDependencies preDeps = ResolveProjectFileDependencies(absProject, hostConfig); - - bool stale = ProjectFileStale(dllPath, absProject, hostExe, preDeps); - - 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 -fuse-ld=lld " - "-D CRAFTER_BUILD_DLL_IMPORT " - "-nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1 " - "-fprebuilt-module-path={}{} " - "-Wl,/EXPORT:CrafterBuildProject " - "{} {}{} -o {} -L %LIBCXX_DIR%\\lib -lc++", - cacheDir.string(), preDeps.compileFlags, - absProject.string(), crafterBuildLib.string(), preDeps.linkFlags, dllPath.string()); - - std::string result = RunBuildCommand(compileCmd); - if (!result.empty()) { - throw std::runtime_error(std::format("Failed to compile project {}: {}", absProject.string(), result)); - } - } - - // project.dll links libc++ dynamically (-lc++) and the dll typically lives - // under %LIBCXX_DIR%\lib. Add it to the loader's search path so the user - // doesn't have to put it on PATH manually. - if (const char* libcxx = std::getenv("LIBCXX_DIR"); libcxx && *libcxx) { - fs::path libcxxLib = fs::path(libcxx) / "lib"; - SetDllDirectoryA(libcxxLib.string().c_str()); - } - - 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); - auto fn = reinterpret_cast(GetProcAddress(handle, "CrafterBuildProject")); - if (!fn) { - throw std::runtime_error(std::format("CrafterBuildProject not found in {}: error {}", dllPath.string(), GetLastError())); - } - - guard.Frame().dependencies = std::move(preDeps.configs); - return fn(args); -} -#endif - -#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32 -// Linux→mingw cross-compile run on Windows: mingw ABI + libstdc++ from msys2. -// Resolves the mingw sysroot at C:\msys64\ucrt64 by default (UCRT runtime, -// matching the Arch cross-compile that produced this binary). User can -// override with CRAFTER_MINGW_DIR if they have mingw-w64 installed elsewhere -// or want the legacy MSVCRT mingw at C:\msys64\mingw64. clang itself is the -// user's existing standalone clang on PATH. - -namespace { - fs::path MingwPrefix() { - if (const char* env = std::getenv("CRAFTER_MINGW_DIR"); env && *env) { - return fs::path(env); - } - return "C:\\msys64\\ucrt64"; - } - - std::string MingwGccVersion() { - fs::path includeRoot = MingwPrefix() / "include" / "c++"; - if (!fs::exists(includeRoot)) { - throw std::runtime_error(std::format("mingw-w64 not found at {} (install msys2 mingw-w64-x86_64-toolchain or set CRAFTER_MINGW_DIR)", MingwPrefix().string())); - } - std::vector versions; - for (const auto& entry : fs::directory_iterator(includeRoot)) { - if (entry.is_directory()) versions.push_back(entry.path().filename().string()); - } - if (versions.empty()) { - throw std::runtime_error(std::format("no C++ versions under {}", includeRoot.string())); - } - std::sort(versions.begin(), versions.end(), std::greater<>()); - return versions.front(); - } -} - -std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) { - CacheLock lock(stdPcm.parent_path()); - if (config.target == "x86_64-pc-windows-msvc") { - // MSVC target on mingw host: same MSVC libc++ logic as the - // native-MSVC LoadProject path. User must have LIBCXX_DIR pointing - // at a Windows libc++ install. - const char* libcxxEnv = std::getenv("LIBCXX_DIR"); - if (!libcxxEnv || !*libcxxEnv) { - return "MSVC target requires LIBCXX_DIR pointing at a Windows libc++ install"; - } - std::string stdcppm = std::format("{}\\modules\\c++\\v1\\std.cppm", libcxxEnv); - if (fs::exists(stdPcm) && fs::last_write_time(stdPcm) >= fs::last_write_time(stdcppm)) { - return ""; - } - return RunBuildCommand(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())); - } - - // Default: mingw target. Look up mingw-w64 libstdc++ via the msys2 prefix. - fs::path prefix = MingwPrefix(); - fs::path stdCc = prefix / "include" / "c++" / MingwGccVersion() / "bits" / "std.cc"; - if (!fs::exists(stdCc)) { - return std::format("std.cc not found at {}", stdCc.string()); - } - if (fs::exists(stdPcm) && fs::last_write_time(stdPcm) >= fs::last_write_time(stdCc)) { - return ""; - } - // Copy std.cc → std.cppm in C++ rather than via cmd's `copy /Y`: keeping - // it in-process means one less shell round-trip and a std::error_code we - // can report directly. Held under the cache lock, so a plain filename is - // safe — no other builder writes this path concurrently. - fs::path stdCppm = stdPcm.parent_path() / "std.cppm"; - std::error_code ec; - fs::copy_file(stdCc, stdCppm, fs::copy_options::overwrite_existing, ec); - if (ec) { - return std::format("copy {} -> {}: {}", stdCc.string(), stdCppm.string(), ec.message()); - } - return RunBuildCommand(std::format( - "clang++ --target={} -march={} -mtune={} " - "--sysroot=\"{}\" -femulated-tls " - "-O3 -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier " - "--precompile \"{}\" -o {}", - config.target, config.march, config.mtune, - prefix.string(), - stdCppm.string(), - stdPcm.string())); -} - -std::string Crafter::GetBaseCommand(const Configuration& config) { - if (config.target == "x86_64-pc-windows-msvc") { - return std::format("clang++ -nostdinc++ -nostdlib++ -isystem %LIBCXX_DIR%\\include\\c++\\v1"); - } - return std::format("clang++ --sysroot=\"{}\" -femulated-tls", MingwPrefix().string()); -} - -namespace { - void EnsureCrafterBuildPcms(const fs::path& sourceDir, const fs::path& cacheDir) { - CacheLock lock(cacheDir); - // Re-derived under the lock: another builder may have refreshed the - // cache from its own sources while we waited. - std::string stamp = CrafterBuildSourceStamp(sourceDir, CrafterBuildModules); - bool upToDate = ReadCacheStamp(cacheDir) == stamp; - fs::path prefix = MingwPrefix(); - for (std::string_view name : CrafterBuildModules) { - fs::path cppmPath = sourceDir / std::format("{}.cppm", name); - fs::path pcmPath = cacheDir / std::format("{}.pcm", name); - if (!fs::exists(cppmPath)) { - throw std::runtime_error(std::format("module source {} not found in {} (set CRAFTER_BUILD_HOME)", name, sourceDir.string())); - } - if (upToDate && fs::exists(pcmPath)) { - continue; - } - std::string cmd = std::format( - "clang++ --target=x86_64-w64-mingw32 -march=native -mtune=native " - "--sysroot=\"{}\" -femulated-tls " - "-std=c++26 -O3 -D CRAFTER_BUILD_DLL_IMPORT " - "-Wno-reserved-identifier -Wno-reserved-module-identifier " - "-fprebuilt-module-path={} " - "--precompile {} -o {}", - prefix.string(), 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)); - } - } - // Last, so an interrupted rebuild leaves the stamp disagreeing with the - // sources and the next run starts over rather than trusting a half-built - // set of PCMs. - if (!upToDate) WriteCacheStamp(cacheDir, stamp); - } -} - -Configuration Crafter::LoadProject(const fs::path& projectFile, std::span args) { - fs::path absProject = fs::canonical(projectFile); - ProjectFileGuard guard(absProject); - fs::path buildDir = absProject.parent_path() / "build"; - if (!fs::exists(buildDir)) fs::create_directories(buildDir); - fs::path dllPath = buildDir / std::format("{}.dll", absProject.stem().string()); - - 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)); - - fs::path sourceDir = Crafter::GetCrafterBuildHome(); - - Configuration hostConfig; - hostConfig.target = "x86_64-w64-mingw32"; - 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); - - // See the Linux LoadProject for why this sits here and runs every time. - ProjectFileDependencies preDeps = ResolveProjectFileDependencies(absProject, hostConfig); - - bool stale = ProjectFileStale(dllPath, absProject, hostExe, preDeps); - - if (stale) { - // The import lib lives next to the launcher exe (Build()'s mingw - // copy step puts libcrafter-build.dll.a there) so -L - // -lcrafter-build resolves it. - fs::path prefix = MingwPrefix(); - std::string compileCmd = std::format( - "clang++ --target=x86_64-w64-mingw32 -march=native -mtune=native " - "--sysroot=\"{}\" -femulated-tls " - "-std=c++26 -shared -O3 -Wno-return-type-c-linkage -fuse-ld=lld " - "-D CRAFTER_BUILD_DLL_IMPORT " - "-fprebuilt-module-path={}{} " - // mingw-lld doesn't accept /EXPORT:NAME — use the catch-all so - // CrafterBuildProject is reachable via GetProcAddress without - // forcing project.cpp templates to add __declspec(dllexport). - "-Wl,--export-all-symbols " - // Dependency archives go before -lcrafter-build: they reference - // Crafter.Build symbols, and ld.lld resolves archives in order. - "{} -o {}{} -L\"{}\" -lcrafter-build -lstdc++exp -lpthread", - prefix.string(), - cacheDir.string(), preDeps.compileFlags, - absProject.string(), dllPath.string(), preDeps.linkFlags, - hostExe.parent_path().string()); - - std::string result = RunBuildCommand(compileCmd); - if (!result.empty()) { - throw std::runtime_error(std::format("Failed to compile project {}: {}", absProject.string(), result)); - } - } - - // project.dll is a mingw DLL linking libstdc++-6.dll / libgcc_s_seh-1.dll / - // libwinpthread-1.dll dynamically (LoadProject's compile uses -lstdc++exp - // -lpthread, not -static). We already know where they live from - // MingwPrefix(), so add bin/ to the loader's search path here instead of - // requiring the user to prepend it to PATH manually. - fs::path mingwBin = MingwPrefix() / "bin"; - SetDllDirectoryA(mingwBin.string().c_str()); - - 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); - auto fn = reinterpret_cast(GetProcAddress(handle, "CrafterBuildProject")); - if (!fn) { - throw std::runtime_error(std::format("CrafterBuildProject not found in {}: error {}", dllPath.string(), GetLastError())); - } - - guard.Frame().dependencies = std::move(preDeps.configs); - return fn(args); -} -#endif - -#ifdef __linux__ - CommandResult Crafter::RunCommandChecked(std::string_view cmd) { std::array buffer; CommandResult result{}; @@ -1191,7 +664,7 @@ std::string Crafter::BuildStdPcm(const Configuration& config, fs::path stdPcm) { // non-wasm cross builds: the driver ranks the host toolchain's own // libc++ headers (/../include/c++/v1) above --sysroot, so // the PCM would mix host libc++ with the foreign libc — force the - // sysroot's copy (same trick as the Windows LIBCXX_DIR path) + // sysroot's copy. if (!isWasm && !config.sysroot.empty()) { archFlags += std::format(" -nostdinc++ -isystem {}/usr/include/c++/v1", config.sysroot); } @@ -1348,5 +821,3 @@ Configuration Crafter::LoadProject(const fs::path& projectFile, std::span #include -#if defined(_WIN32) - #include - #include - #ifndef STDOUT_FILENO - #define STDOUT_FILENO _fileno(stdout) - #endif - #define CRAFTER_PROGRESS_ISATTY _isatty -#else - #include - #include - #define CRAFTER_PROGRESS_ISATTY isatty -#endif +#include +#include export module Crafter.Build:Progress_impl; import std; @@ -32,19 +22,10 @@ namespace { std::chrono::steady_clock::time_point StartTime = std::chrono::steady_clock::now(); std::int32_t TerminalWidth() { - #if defined(_WIN32) - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO info; - if (h != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(h, &info)) { - std::int32_t w = info.srWindow.Right - info.srWindow.Left + 1; - if (w > 0) return w; - } - #else winsize ws{}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { return ws.ws_col; } - #endif if (const char* col = std::getenv("COLUMNS")) { try { std::int32_t c = std::stoi(col); if (c > 0) return c; } catch (...) {} } @@ -86,7 +67,7 @@ namespace Crafter::Progress { void SetVerbosity(Verbosity v) { std::lock_guard lock(StateMutex); ActiveVerbosity = v; - IsTty = CRAFTER_PROGRESS_ISATTY(STDOUT_FILENO) != 0; + IsTty = isatty(STDOUT_FILENO) != 0; StartTime = std::chrono::steady_clock::now(); Total.store(0); Done.store(0); diff --git a/implementations/Crafter.Build-Test.cpp b/implementations/Crafter.Build-Test.cpp index a6e3ec8..e18b148 100644 --- a/implementations/Crafter.Build-Test.cpp +++ b/implementations/Crafter.Build-Test.cpp @@ -35,38 +35,11 @@ namespace { return out; } - // cmd.exe doesn't recognize '...' as quoting (it would pass the single - // quotes through to the executable). Wrap in "..." for cmd; embedded " - // is rare in paths but escape it to be safe. Backslash sequences before - // " don't need the MS-CRT doubling rules because we go through - // `cmd /C "..."`, which uses cmd's parser, not the CRT's argv splitter. - std::string ShellQuoteCmd(std::string_view s) { - std::string out; - out.reserve(s.size() + 2); - out.push_back('"'); - for (char c : s) { - if (c == '"') out += "\\\""; - else out.push_back(c); - } - out.push_back('"'); - return out; - } - - // Host-shell quoting: sh on Linux, cmd on Windows. For args/paths that - // hit the local shell (Local runner exec, Cmd-prefix runner exec). - std::string ShellQuoteHost(std::string_view s) { -#ifdef _WIN32 - return ShellQuoteCmd(s); -#else - return ShellQuoteSh(s); -#endif - } - std::string JoinAndQuoteArgs(std::span args) { std::string out; for (const auto& a : args) { if (!out.empty()) out.push_back(' '); - out += ShellQuoteHost(a); + out += ShellQuoteSh(a); } return out; } @@ -191,11 +164,7 @@ TestRunner TestRunner::Cmd(std::string command) { TestRunner r; r.name = std::format("cmd:{}", command); r.exec = std::format("{} {{bin}} {{args}}", command); -#ifdef _WIN32 - r.probe = std::format("where {}", command); -#else r.probe = std::format("which {}", command); -#endif return r; } @@ -203,11 +172,7 @@ TestRunner TestRunner::Wine() { TestRunner r; r.name = "wine"; r.exec = "wine {bin} {args}"; -#ifdef _WIN32 - r.probe = "where wine"; -#else r.probe = "which wine"; -#endif return r; } @@ -218,11 +183,8 @@ TestRunner TestRunner::ForTarget(const Configuration& cfg) { // case (cfg.target defaulted to HostTarget()) without any wrapper. if (target == HostTarget()) return Local(); - // Windows targets: native on a Windows host, Wine on Linux. We don't - // distinguish mingw vs msvc here — the produced .exe runs the same way. - if (TargetIsWindows(target)) { - return TargetIsWindows(HostTarget()) ? Local() : Wine(); - } + // Windows targets run under Wine; there is no Windows host. + if (TargetIsWindows(target)) return Wine(); // WASI: a .wasm file isn't directly executable; wasmtime is the canonical // runtime. wasi-cli also works but the upstream Bytecode Alliance name is @@ -311,7 +273,7 @@ TestResult Crafter::RunSingleTest(const Test& test, const fs::path& binary, std: if (test.runner.exec.empty()) { // Local runner: spawn the binary directly through the host shell. - std::string cmd = std::format("{} {}", ShellQuoteHost(binary.string()), ph["{args}"]); + std::string cmd = std::format("{} {}", ShellQuoteSh(binary.string()), ph["{args}"]); r = RunCommandWithTimeout(cmd, timeout); } else { // Prefix runner (qemu-user, wasmtime, wine, ...): templated exec @@ -337,11 +299,7 @@ TestResult Crafter::RunSingleTest(const Test& test, const fs::path& binary, std: namespace { bool ToolOnPath(std::string_view name) { -#ifdef _WIN32 - std::string cmd = std::format("where {} > nul 2>&1", name); -#else std::string cmd = std::format("which {} > /dev/null 2>&1", name); -#endif return std::system(cmd.c_str()) == 0; } diff --git a/implementations/main.cpp b/implementations/main.cpp index 346f6b6..d703cca 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -2,16 +2,7 @@ // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® #include -#if defined(_WIN32) - #include - #ifndef STDOUT_FILENO - #define STDOUT_FILENO _fileno(stdout) - #endif - #define CRAFTER_MAIN_ISATTY _isatty -#else - #include - #define CRAFTER_MAIN_ISATTY isatty -#endif +#include import std; import Crafter.Build; @@ -26,7 +17,7 @@ int main(int argc, char** argv) { // process blocks in its serve loop and never returns to flush. A TTY is // already line-buffered and its in-place redraw path flushes explicitly, so // only adjust when redirected. - if (CRAFTER_MAIN_ISATTY(STDOUT_FILENO) == 0) { + if (isatty(STDOUT_FILENO) == 0) { std::setvbuf(stdout, nullptr, _IOLBF, 0); } return Crafter::Run(argc, argv); diff --git a/interfaces/Crafter.Build-Api.h b/interfaces/Crafter.Build-Api.h deleted file mode 100644 index 334a891..0000000 --- a/interfaces/Crafter.Build-Api.h +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® - -#pragma once - -#if defined(_WIN32) - #if defined(CRAFTER_BUILD_DLL_IMPORT) - #define CRAFTER_API __declspec(dllimport) - #else - #define CRAFTER_API __declspec(dllexport) - #endif -#else - #define CRAFTER_API -#endif diff --git a/interfaces/Crafter.Build-Asset.cppm b/interfaces/Crafter.Build-Asset.cppm index 33e2bbd..7025687 100644 --- a/interfaces/Crafter.Build-Asset.cppm +++ b/interfaces/Crafter.Build-Asset.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Asset; import std; namespace fs = std::filesystem; @@ -19,7 +17,7 @@ namespace Crafter { // that errors out at runtime if cfg.assets is non-empty — full // support requires a self-host rebuild via crafter-build itself, // which resolves the Crafter.Asset dep through project.cpp. - export CRAFTER_API std::string CompressAsset(const fs::path& input, const fs::path& output); + export std::string CompressAsset(const fs::path& input, const fs::path& output); // Split a single .obj + .mtl file by material group into per-material // .cmesh files, per-unique-albedo .ctex files, and a scene.txt manifest @@ -36,7 +34,7 @@ namespace Crafter { // Idempotent: returns "" immediately if outDir/scene.txt already exists, // so repeated builds skip the expensive split+resize step. // Returns "" on success, an error string on failure. - export CRAFTER_API std::string BuildOBJBundle( + export std::string BuildOBJBundle( const fs::path& objPath, const fs::path& mtlPath, const fs::path& outDir, diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index 76b5117..2bb1b73 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Clang; import std; import :Shader; @@ -65,30 +63,28 @@ export namespace Crafter { bool IsLocal() const { return exec.empty(); } - static CRAFTER_API TestRunner Local(); + static TestRunner Local(); // Prefix runner: wraps the local binary in ` {bin} {args}`. // Used for qemu-user, wasmtime, and similar single-binary wrappers. - static CRAFTER_API TestRunner Cmd(std::string command); - // Run a Windows .exe through Wine. Probes `wine` on PATH; on a Windows - // host the wine wrapper is pointless, so callers should route to Local - // before reaching here. - static CRAFTER_API TestRunner Wine(); + static TestRunner Cmd(std::string command); + // Run a Windows .exe through Wine. Probes `wine` on PATH. + static TestRunner Wine(); // Parse a `[:]` spec used by CRAFTER_BUILD_RUNNER_ // and --runner=. Supported: "local", "cmd:". Returns nullopt // for an empty string; throws on a non-empty unrecognized spec. - static CRAFTER_API std::optional FromSpec(std::string_view spec); + static std::optional FromSpec(std::string_view spec); // Honor CRAFTER_BUILD_RUNNER_ (power-user override). Triple // dashes/dots become underscores so they're valid in env-var names. // Returns `fallback` when the env var is unset. - static CRAFTER_API TestRunner FromEnv(std::string_view target, TestRunner fallback = Local()); + static TestRunner FromEnv(std::string_view target, TestRunner fallback = Local()); // Derive a runner from a Configuration's target triple + sysroot. // Returns Local() when target equals the host, Wine() for Windows - // targets on a non-Windows host, `qemu-` (with QEMU_LD_PREFIX + // targets, `qemu-` (with QEMU_LD_PREFIX // set when cfg.sysroot is non-empty) for non-host -linux- triples, // `wasmtime` for wasm32-wasi/wasm64-wasi, and Local() as a last // resort. CRAFTER_BUILD_RUNNER_ still wins as an override // upstream of this — see FromEnv. - static CRAFTER_API TestRunner ForTarget(const struct Configuration& cfg); + static TestRunner ForTarget(const struct Configuration& cfg); }; enum class TestOutcome { Pass, Fail, Crash, Timeout, Skipped }; @@ -233,17 +229,18 @@ export namespace Crafter { }; // Per-file view handed to each LintRule's check callback. Every member - // function is out-of-line and CRAFTER_API (defined in Crafter.Build:Lint's - // implementation unit) because rule lambdas execute from the user's - // project DLL on Windows — clang does not emit module-attached in-class - // inline bodies into consumers (see ArgQuery below). + // function is out-of-line (defined in Crafter.Build:Lint's implementation + // unit) because rule lambdas execute from the user's project.so and + // resolve against the running executable: clang does not emit + // module-attached in-class inline bodies into consumers (see ArgQuery + // below). struct LintContext { fs::path file; // absolute path of the file under lint std::string content; // whole file as read from disk std::vector lines; // views into `content`, one per line, no '\n' - CRAFTER_API std::string Extension() const; // ".cppm", ".cpp", ".h", ... - CRAFTER_API std::string_view Line(std::size_t n) const; // 1-based; empty if out of range + std::string Extension() const; // ".cppm", ".cpp", ".h", ... + std::string_view Line(std::size_t n) const; // 1-based; empty if out of range // `content` with comments and string/char literal bodies blanked to // spaces, newlines preserved — offsets and line numbers stay valid. // Built from Tokens() on first call, cached per file, so raw strings, @@ -254,9 +251,9 @@ export namespace Crafter { // Convenient for a quick scan, but Tokens() is the better tool for // anything structural: this view cannot tell an identifier from a // keyword, and it has already thrown away where the literals were. - CRAFTER_API const std::string& CommentStripped(); + const std::string& CommentStripped(); // Record a finding at `line` (1-based; pass 0 for a whole-file finding). - CRAFTER_API void Report(std::size_t line, std::string message); + void Report(std::size_t line, std::string message); // Replace the file's content. Makes this rule a *transform*: // `crafter-build format` writes the result back to disk; `lint` // derives would-reformat findings from it (dry — never writes). @@ -265,7 +262,7 @@ export namespace Crafter { // pattern: build the new string, call SetContent once at the end. Do // not assign `content` directly — that bypasses the re-split. May be // combined with Report() in the same rule. - CRAFTER_API void SetContent(std::string newContent); + void SetContent(std::string newContent); // True when `rule` is suppressed at `line` (1-based; 0 = whole-file, // which only file-level directives cover) by a suppression comment: // // lint-disable-next-line [rules...] applies to the following line @@ -277,7 +274,7 @@ export namespace Crafter { // itself for every line its edit touches (the driver cannot map lines // across a count-changing rewrite). Parsed lazily from the raw lines; // re-parsed after SetContent. - CRAFTER_API bool Suppressed(std::string_view rule, std::size_t line); + bool Suppressed(std::string_view rule, std::size_t line); // The file lexed by clang, in source order. Built on first call, cached // per file, re-lexed after SetContent. Empty for extensions that are @@ -287,16 +284,16 @@ export namespace Crafter { // raw strings, line splices, digraphs and nested quoting right, and // offsets index straight into `content`, so a transform can find in // the token stream and edit in place. - CRAFTER_API std::span Tokens(); + std::span Tokens(); // The token's own bytes: content.substr(tok.offset, tok.length). - CRAFTER_API std::string_view TokenText(const LintToken& token) const; + std::string_view TokenText(const LintToken& token) const; // Tokens whose FIRST byte is on `line` (1-based). A token that starts // earlier and spans into `line` — a raw string, a block comment — is // not included; ask Tokens() directly when that matters. - CRAFTER_API std::span TokensOnLine(std::size_t line); + std::span TokensOnLine(std::size_t line); // True when a comment token starts on `line`. Replaces `Line(n).contains("//")`, // which false-positives on a `//` inside a string literal. - CRAFTER_API bool LineHasComment(std::size_t line); + bool LineHasComment(std::size_t line); // True when `line` (1-based) is touched by a token that spans more // than one line — a raw string or a block comment. A transform that // joins, splits or rewrites such a line changes what is inside that @@ -304,7 +301,7 @@ export namespace Crafter { // Replaces `Line(n).contains("R\"")`, which both misses raw strings // opened on an earlier line and fires on the characters R" appearing // inside an ordinary literal. - CRAFTER_API bool LineHasMultiLineToken(std::size_t line); + bool LineHasMultiLineToken(std::size_t line); // Declarations written in this file, flattened depth-first with // parents before children (see LintDecl). Empty when the AST is not @@ -321,11 +318,11 @@ export namespace Crafter { // slice. Declarations inside a preprocessor branch that is inactive // for the host are absent here, though Tokens() still sees them. Rules // that must cover every platform belong on tokens. - CRAFTER_API std::span Decls(); - CRAFTER_API bool AstAvailable(); + std::span Decls(); + bool AstAvailable(); // Why AstAvailable() is false — a missing PCM, a parse error, a file // that is not a translation unit. Empty when the AST is available. - CRAFTER_API std::string_view AstUnavailableReason(); + std::string_view AstUnavailableReason(); // Driver wiring — set by RunLint before each check call. Not for rules. std::string activeRule; @@ -363,7 +360,7 @@ export namespace Crafter { // Configuration::target so projects don't have to hardcode it for the // no-cross-compile case. Returns "" if clang isn't on PATH or doesn't // print a triple — those projects still need an explicit cfg.target. - CRAFTER_API std::string HostTarget(); + std::string HostTarget(); struct Configuration { fs::path path; @@ -439,7 +436,7 @@ export namespace Crafter { std::vector tests; // Lint rules for `crafter-build lint`. Populate via AddLintRule. std::vector lintRules; - CRAFTER_API void GetInterfacesAndImplementations(std::span interfaces, std::span implementations); + void GetInterfacesAndImplementations(std::span interfaces, std::span implementations); // Retry the `import X;` names GetInterfacesAndImplementations could not // place, against the dependency DAG as it stands now. Sources are // scanned when they're declared, but `dependencies` is often assigned @@ -448,30 +445,30 @@ export namespace Crafter { // change and this Configuration's objects would be silently reused // against the new layout. Build() calls this before checking mtimes; // calling it again is harmless. - CRAFTER_API void ResolvePendingImports(); + void ResolvePendingImports(); // Declare a test. Sources default to `tests//main.cpp` resolved // against this Configuration's path; target/march/mtune/sysroot/debug // are inherited from this Configuration so cross-arch projects don't // have to re-specify. Returns a builder for chaining defines, deps, // etc. Defined in Crafter.Build:Test. - CRAFTER_API struct TestBuilder AddTest(std::string_view name); + struct TestBuilder AddTest(std::string_view name); // Same as AddTest, but compiles the parent's `interfaces` directly // into this test's Configuration (rather than going through a dep). // Use when the test must rebuild those interfaces with its own // compile flags — typically per-march SIMD codegen. - CRAFTER_API struct TestBuilder AddTest(std::string_view name, std::span interfaces); + struct TestBuilder AddTest(std::string_view name, std::span interfaces); // Math-style fan-out: one Test per MarchTier, all sharing the same // `tests//main.cpp` source and the same interface set, each // compiled with the tier's `-march`/`-mtune`. Test names are // `-`. - CRAFTER_API void AddMarchVariants(std::string_view name, std::span interfaces, std::span tiers); + void AddMarchVariants(std::string_view name, std::span interfaces, std::span tiers); // Register a lint rule for `crafter-build lint` / `format`. Rules // registered on any Configuration whose path lies inside the project // root are collected (deduplicated by name, root-first) — attach them // to the lib or the exe config, either works. Rules that call // ctx.SetContent are transforms (see LintContext::SetContent). // Defined in Crafter.Build:Lint. - CRAFTER_API void AddLintRule(std::string name, std::function check); + void AddLintRule(std::string name, std::function check); // Same, for a rule that reads LintContext::Decls(). Declared // separately rather than as a flag on AddLintRule so existing // registrations keep compiling. Such a rule makes the run build the @@ -479,7 +476,7 @@ export namespace Crafter { // produced becomes an error instead of a silent pass. Prefer // report-only: a transform running after the first one forces a // re-parse of everything it changed. - CRAFTER_API void AddAstLintRule(std::string name, std::function check); + void AddAstLintRule(std::string name, std::function check); // Suffix that uniquely identifies this Configuration's compile state. // target+march+mtune are spelled out for readability; the rest // (type, debug, sysroot, defines, compileFlags) collapse into a short @@ -544,14 +541,14 @@ export namespace Crafter { // Directory holding the std module PCM for this Configuration. Keyed on // target+march because a BMI is only loadable by a TU with matching target // features, and suffixed with the wasm variant flags for the same reason. - CRAFTER_API fs::path StdPcmDir(const Configuration& config); + fs::path StdPcmDir(const Configuration& config); // Where Build leaves this Configuration's linked artifact: the executable // (with the target's suffix), `lib.a` / `.lib`, or the shared // library / DLL. The one rule every "is the consumer older than its // dependency" check reads, so a staleness test and the link step can't // disagree about the file name. - CRAFTER_API fs::path ExpectedOutput(const Configuration& config); + fs::path ExpectedOutput(const Configuration& config); // The clang invocation every C++ translation unit in this Configuration is // compiled with, before anything that depends on work having happened @@ -586,14 +583,14 @@ export namespace Crafter { fs::path stdPcmDir; fs::path pcmDir; }; - CRAFTER_API CompileCommand GetCompileCommand(const Configuration& config); + CompileCommand GetCompileCommand(const Configuration& config); - CRAFTER_API BuildResult Build(Configuration& config, std::unordered_map>& depResults, std::mutex& depMutex); + BuildResult Build(Configuration& config, std::unordered_map>& depResults, std::mutex& depMutex); // Takes main's argv verbatim so the CLI entry point is a one-liner; // the shape is inherited from the language, not chosen here. // lint-disable-next-line no-char-pointer - CRAFTER_API std::int32_t Run(std::int32_t argc, char** argv); + std::int32_t Run(std::int32_t argc, char** argv); // Delete the bin/ and build/ trees beside `projectFile`, returning the paths // that existed and were removed. Backs `crafter-build clean`. @@ -603,7 +600,7 @@ export namespace Crafter { // to compile (and its git dependencies to be present) is useless exactly // then. That means variant directories are not enumerated — the whole tree // goes, which is what the by-hand `rm -rf bin build` did anyway. - CRAFTER_API std::vector CleanProject(const fs::path& projectFile); + std::vector CleanProject(const fs::path& projectFile); // Add a small index.html + runtime.js pair next to the .wasm output so the // build can be loaded directly in a browser (just `serve` the bin dir and @@ -611,7 +608,7 @@ export namespace Crafter { // this and shouldn't carry the extra files. Call from project.cpp after // outputName is set; index.html is generated against the current // outputName so renaming the binary later requires another call. - CRAFTER_API void EnableWasiBrowserRuntime(Configuration& cfg); + void EnableWasiBrowserRuntime(Configuration& cfg); // Register the relaxed-SIMD codegen variant on cfg. Builds an additional // outputName.relaxed-simd.wasm compiled with -mrelaxed-simd (FMA, @@ -622,17 +619,17 @@ export namespace Crafter { // 2026). Idempotent. Call before EnableWasiBrowserRuntime so the emitted // variants.json manifest includes it. A no-op for non-wasm targets at build // time (no wasm is produced), but harmless to declare unconditionally. - CRAFTER_API void EnableWasiRelaxedSimdVariant(Configuration& cfg); + void EnableWasiRelaxedSimdVariant(Configuration& cfg); // View over the project's args with simple query helpers. Has(flag) for // boolean switches; Get(prefix) for valued options (e.g. Get("--prefix=") - // returns the substring after the equals). Definitions are out-of-line - // and CRAFTER_API so they cross the Windows DLL boundary cleanly — clang - // does not emit module-attached in-class inline bodies into consumers. + // returns the substring after the equals). Definitions are out-of-line so + // project.so resolves them from the executable: clang does not emit + // module-attached in-class inline bodies into consumers. struct ArgQuery { std::span args; - CRAFTER_API bool Has(std::string_view flag) const; - CRAFTER_API std::optional Get(std::string_view prefix) const; + bool Has(std::string_view flag) const; + std::optional Get(std::string_view prefix) const; }; // Apply the framework's standard CLI args + env vars onto cfg: @@ -651,5 +648,5 @@ export namespace Crafter { // pre-set defaults. Returns an ArgQuery over the same span so projects // can query their own flags (`--timing`, ...) without re-rolling the // for-arg-in-args loop. - CRAFTER_API ArgQuery ApplyStandardArgs(Configuration& cfg, std::span args); + ArgQuery ApplyStandardArgs(Configuration& cfg, std::span args); } diff --git a/interfaces/Crafter.Build-External.cppm b/interfaces/Crafter.Build-External.cppm index df5b190..fe8e229 100644 --- a/interfaces/Crafter.Build-External.cppm +++ b/interfaces/Crafter.Build-External.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:External; import std; namespace fs = std::filesystem; @@ -48,13 +46,13 @@ export namespace Crafter { // declaration and target — BuildExternal derives its own working directory // through this, so a caller that only needs to know where the headers ended // up cannot drift from where they actually are. - CRAFTER_API fs::path ExternalCloneDir(const ExternalDependency& dep, std::string_view target); + fs::path ExternalCloneDir(const ExternalDependency& dep, std::string_view target); // The -I flags this dependency contributes, from its declared includeDirs. // Also pure, which is what lets the linter's AST layer parse a source that // includes an external's headers without running a build to find out where // they are. The paths only resolve once something has fetched the clone. - CRAFTER_API std::vector ExternalIncludeFlags(const ExternalDependency& dep, std::string_view target); - CRAFTER_API ExternalBuildResult BuildExternal(const ExternalDependency& dep, std::string_view target, std::atomic& cancelled); + std::vector ExternalIncludeFlags(const ExternalDependency& dep, std::string_view target); + ExternalBuildResult BuildExternal(const ExternalDependency& dep, std::string_view target, std::atomic& cancelled); // Specification for a sibling crafter-build project to fetch and depend on. // GitSource picks the revision: leave branch + commit empty for the @@ -73,7 +71,7 @@ export namespace Crafter { // remote's project.cpp, and returns a stable Configuration* you can drop // into cfg.dependencies. The Configuration is owned by an internal cache // for the lifetime of the build; identical specs share one Configuration. - CRAFTER_API Configuration* GitProject(const GitProjectSpec& spec); + Configuration* GitProject(const GitProjectSpec& spec); // Specification for a local sibling crafter-build project (e.g. an // example folder depending on its parent project, or a workspace where @@ -87,7 +85,7 @@ export namespace Crafter { // cache copy. Resolves projectFile to its canonical absolute path and // recursively LoadProject's it. Returns a stable Configuration* owned // by the same internal cache as GitProject. - CRAFTER_API Configuration* LocalProject(const LocalProjectSpec& spec); + Configuration* LocalProject(const LocalProjectSpec& spec); // Clone (or update) the given git source into the shared external // cache and return the path to the cloned working tree. Intended for @@ -97,5 +95,5 @@ export namespace Crafter { // ExternalDependency / GitProject. Caching key = url + branch + commit; // identical calls reuse the existing clone. Pin `source.commit` to a // SHA for reproducible, offline-after-first-fetch builds. - CRAFTER_API fs::path GitFetch(const GitSource& source); + fs::path GitFetch(const GitSource& source); } diff --git a/interfaces/Crafter.Build-Implementation.cppm b/interfaces/Crafter.Build-Implementation.cppm index 95f816b..7c9c631 100644 --- a/interfaces/Crafter.Build-Implementation.cppm +++ b/interfaces/Crafter.Build-Implementation.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Implementation; import std; namespace fs = std::filesystem; @@ -19,8 +17,8 @@ namespace Crafter { // no reachable Configuration provided when the source was scanned. std::vector pendingImports; fs::path path; - CRAFTER_API Implementation(fs::path&& path); - CRAFTER_API bool Check(const fs::path& buildDir, const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()) const; - CRAFTER_API void Compile(const std::string_view clang, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError) const; + Implementation(fs::path&& path); + bool Check(const fs::path& buildDir, const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()) const; + void Compile(const std::string_view clang, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError) const; }; } diff --git a/interfaces/Crafter.Build-Interface.cppm b/interfaces/Crafter.Build-Interface.cppm index 3991c24..27eb298 100644 --- a/interfaces/Crafter.Build-Interface.cppm +++ b/interfaces/Crafter.Build-Interface.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Interface; import std; namespace fs = std::filesystem; @@ -26,9 +24,9 @@ namespace Crafter { bool checked = false; std::string name; fs::path path; - CRAFTER_API ModulePartition(std::string&& name, fs::path&& path); - CRAFTER_API bool Check(const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()); - CRAFTER_API void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError); + ModulePartition(std::string&& name, fs::path&& path); + bool Check(const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()); + void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError); }; export class Module { @@ -48,8 +46,8 @@ namespace Crafter { std::vector> partitions; std::string name; fs::path path; - CRAFTER_API Module(std::string&& name, fs::path&& path); - CRAFTER_API bool Check(const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()); - CRAFTER_API void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError); + Module(std::string&& name, fs::path&& path); + bool Check(const fs::path& pcmDir, fs::file_time_type sourceFloor = fs::file_time_type::min()); + void Compile(const std::string_view clang, const fs::path& pcmDir, const fs::path& buildDir, std::atomic& buildCancelled, std::string& buildError); }; } diff --git a/interfaces/Crafter.Build-Lint.cppm b/interfaces/Crafter.Build-Lint.cppm index a25e350..bbe9398 100644 --- a/interfaces/Crafter.Build-Lint.cppm +++ b/interfaces/Crafter.Build-Lint.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Lint; import std; import :Clang; @@ -63,5 +61,5 @@ export namespace Crafter { // final content differs from the original. Prints per-mode output: // findings compiler-style (Report), would-change paths (Check), or // formatted paths (Apply), plus a summary line. - CRAFTER_API LintSummary RunLint(Configuration& projectCfg, const RunLintOptions& opts); + LintSummary RunLint(Configuration& projectCfg, const RunLintOptions& opts); } diff --git a/interfaces/Crafter.Build-Platform.cppm b/interfaces/Crafter.Build-Platform.cppm index 0b37177..597f12c 100644 --- a/interfaces/Crafter.Build-Platform.cppm +++ b/interfaces/Crafter.Build-Platform.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Platform; import std; namespace fs = std::filesystem; @@ -30,9 +28,9 @@ namespace Crafter { // Progress::Diagnostic), otherwise the compiler's diagnostics. Callers // therefore keep the `if (!result.empty())` shape they already had. std::string RunBuildCommand(std::string_view command); - export CRAFTER_API CommandResult RunCommandWithTimeout(std::string_view command, std::chrono::seconds timeout); + export CommandResult RunCommandWithTimeout(std::string_view command, std::chrono::seconds timeout); std::string GetBaseCommand(const Configuration& config); - export CRAFTER_API Configuration LoadProject(const fs::path& projectFile, std::span args); + export Configuration LoadProject(const fs::path& projectFile, std::span args); // A dependency the project file currently running its CrafterBuildProject // declared with `#pragma crafter dependency`, so it can also be wired // into the build graph (a test that links the same module, say) without @@ -40,7 +38,7 @@ namespace Crafter { // name or any module it exports — whatever the project file `import`s. // Throws when no such dependency was declared, or when called from // anywhere other than inside LoadProject. - export CRAFTER_API Configuration* ProjectDependency(std::string_view name); + export Configuration* ProjectDependency(std::string_view name); // True while the project file crafter-build was pointed at runs its // CrafterBuildProject, and when no project file is running at all; false // inside a dependency's project file (a `#pragma crafter dependency`, @@ -49,11 +47,11 @@ namespace Crafter { // library's test links crafter-build itself, so when crafter-build loads // that library as a dependency, the test's GitProject loads crafter-build's // project file in turn and the two recurse until the cycle guard throws. - export CRAFTER_API bool IsTopLevelProject(); + export bool IsTopLevelProject(); // Resolves the directory holding distributed runtime assets (Crafter.Build // module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise // derives /share/crafter-build from the running executable's path. - export CRAFTER_API fs::path GetCrafterBuildHome(); + export fs::path GetCrafterBuildHome(); // Newest mtime among the prerequisites a compiler recorded in `depFile` // (clang's `-MD -MF .d`). This is how a build step learns which // headers its source pulled in: the module scanner only reads `import` diff --git a/interfaces/Crafter.Build-Progress.cppm b/interfaces/Crafter.Build-Progress.cppm index 1ed4bc7..123e53b 100644 --- a/interfaces/Crafter.Build-Progress.cppm +++ b/interfaces/Crafter.Build-Progress.cppm @@ -1,22 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Progress; import std; export namespace Crafter::Progress { enum class Verbosity { Quiet, Default, Verbose }; - CRAFTER_API void SetVerbosity(Verbosity v); - CRAFTER_API Verbosity GetVerbosity(); + void SetVerbosity(Verbosity v); + Verbosity GetVerbosity(); // RAII work-unit marker. Construction inflates the running total; destruction // increments completed and (on Default + TTY) redraws the status line. Move // semantics are disabled — keep these as plain stack locals inside the worker // lambda so the destructor fires when that thread's work ends. - class CRAFTER_API Task { + class Task { public: explicit Task(std::string label); ~Task(); @@ -29,18 +27,18 @@ export namespace Crafter::Progress { }; // Verbose-mode command echo. No-op outside Verbose. - CRAFTER_API void EchoCommand(std::string_view command); + void EchoCommand(std::string_view command); // Non-fatal output from a build step that succeeded — compiler and linker // warnings. Erases the status line first so the text doesn't land on top of // it, then writes to stderr. No-op on Quiet. - CRAFTER_API void Diagnostic(std::string_view text); + void Diagnostic(std::string_view text); // Erase the in-place status line so subsequent stderr writes (errors, // banners) don't collide with it. No-op when not in TTY-redraw mode. - CRAFTER_API void Clear(); + void Clear(); // Print "Built N targets in Xms" (or equivalent) and clear in-place state. // Safe to call multiple times; subsequent calls are no-ops. - CRAFTER_API void Finalize(); + void Finalize(); } diff --git a/interfaces/Crafter.Build-Shader.cppm b/interfaces/Crafter.Build-Shader.cppm index 059d19a..4b4d841 100644 --- a/interfaces/Crafter.Build-Shader.cppm +++ b/interfaces/Crafter.Build-Shader.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Shader; import std; namespace fs = std::filesystem; @@ -30,8 +28,8 @@ namespace Crafter { fs::path path; std::string entrypoint; ShaderType type; - CRAFTER_API Shader(fs::path&& path, std::string&& entrypoint, ShaderType type); - CRAFTER_API bool Check(const fs::path& outputDir) const; - CRAFTER_API std::string Compile(const fs::path& outputDir, std::span includeDirs) const; + Shader(fs::path&& path, std::string&& entrypoint, ShaderType type); + bool Check(const fs::path& outputDir) const; + std::string Compile(const fs::path& outputDir, std::span includeDirs) const; }; } diff --git a/interfaces/Crafter.Build-Test.cppm b/interfaces/Crafter.Build-Test.cppm index f26f99a..0b69256 100644 --- a/interfaces/Crafter.Build-Test.cppm +++ b/interfaces/Crafter.Build-Test.cppm @@ -1,8 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include "Crafter.Build-Api.h" export module Crafter.Build:Test; import std; import :Clang; @@ -28,7 +26,7 @@ export namespace Crafter { // avoid the double-`GetInterfacesAndImplementations` footgun (calling // it twice would re-parse the same .cppm files into duplicate Module // entries). - struct CRAFTER_API TestBuilder { + struct TestBuilder { Configuration* parent; std::size_t index; @@ -85,8 +83,8 @@ export namespace Crafter { bool AllPassed() const { return failed == 0 && crashed == 0 && timedOut == 0; } }; - CRAFTER_API TestSummary RunTests(Configuration& projectCfg, const RunTestsOptions& opts, std::span projectArgs = {}); - CRAFTER_API TestResult RunSingleTest(const Test& test, const std::filesystem::path& binary, std::chrono::seconds timeout); + TestSummary RunTests(Configuration& projectCfg, const RunTestsOptions& opts, std::span projectArgs = {}); + TestResult RunSingleTest(const Test& test, const std::filesystem::path& binary, std::chrono::seconds timeout); // Parent-project access for test fixtures. Run() sets the pointer to the // root project's Configuration before discovery; fixtures call ParentLib() @@ -94,6 +92,6 @@ export namespace Crafter { // parent itself) so they can declare it as a dependency without rebuilding // the lib config from scratch. Lookup is by Configuration::name and // recurses through the parent's dependency graph. - CRAFTER_API void SetParentProject(Configuration* parent); - CRAFTER_API Configuration* ParentLib(std::string_view name); + void SetParentProject(Configuration* parent); + Configuration* ParentLib(std::string_view name); } diff --git a/project.cpp b/project.cpp index 3d7f5a3..8ce6800 100644 --- a/project.cpp +++ b/project.cpp @@ -24,12 +24,9 @@ extern "C" Configuration CrafterBuildProject(std::span a CrafterBuildLib->name = "crafter.build-lib"; CrafterBuildLib->outputName = "crafter-build"; ApplyStandardArgs(*CrafterBuildLib, args); - // Windows builds (native msvc via build.cmd or cross-compiled mingw from - // Linux) need a DLL + import lib + launcher exe so LoadProject can - // compile project.cpp against a stable ABI boundary. Linux is monolithic. - CrafterBuildLib->type = (CrafterBuildLib->target == "x86_64-w64-mingw32" || CrafterBuildLib->target == "x86_64-pc-windows-msvc") - ? ConfigurationType::LibraryDynamic - : ConfigurationType::LibraryStatic; + // Monolithic: project.so resolves Crafter:: symbols from the running + // executable, so the library is only ever linked statically into it. + CrafterBuildLib->type = ConfigurationType::LibraryStatic; CrafterBuildLib->dependencies = { math, asset }; CrafterBuildLib->defines.push_back({"CRAFTER_BUILD_HAS_ASSET", ""}); @@ -79,12 +76,6 @@ extern "C" Configuration CrafterBuildProject(std::span a glslang.source.branch = "main"; glslang.builder = ExternalBuilder::CMake; glslang.options = { "-DENABLE_OPT=OFF" }; - // mingw cross-build: skip the standalone executable. We only consume the - // libraries, and glslang.exe pulls in libgcc_eh which needs pthread that - // mingw-w64 doesn't link by default. - if (CrafterBuildLib->target == "x86_64-w64-mingw32") { - glslang.options.push_back("-DENABLE_GLSLANG_BINARIES=OFF"); - } glslang.includeDirs = { "" }; glslang.libs = { "SPIRV", "GenericCodeGen", "glslang", "OSDependent", "MachineIndependent", "glslang-default-resource-limits" }; @@ -106,10 +97,6 @@ extern "C" Configuration CrafterBuildProject(std::span a cfg.linkFlags.push_back("-Wl,--export-dynamic"); cfg.linkFlags.push_back("-ldl"); } - if (cfg.target == "x86_64-w64-mingw32" || cfg.target == "x86_64-pc-windows-msvc") { - // winsock for the -r wasm port probe (bind/WSAStartup). - CrafterBuildLib->linkFlags.push_back("-lws2_32"); - } // Self-tests link the local crafter-build library and exercise it in // process. The harness (whichever crafter-build invokes `test`) compiles diff --git a/tests/CompilerWarning/main.cpp b/tests/CompilerWarning/main.cpp index 91bd2aa..5691f6d 100644 --- a/tests/CompilerWarning/main.cpp +++ b/tests/CompilerWarning/main.cpp @@ -71,11 +71,7 @@ namespace { app.dependencies = { lib }; // The link step reads a command's output the same way a compile does, // so cover it too: an unknown -z value makes ld.lld warn and exit 0. - // GNU-style only — lld-link spells its options differently — so the - // link case rides along on hosts that use it and is skipped elsewhere. - if (!app.target.contains("windows")) { - app.linkFlags = { "-Wl,-z,crafter-build-nonexistent-z-value" }; - } + app.linkFlags = { "-Wl,-z,crafter-build-nonexistent-z-value" }; return app; } diff --git a/tests/TestRunnerSpec/main.cpp b/tests/TestRunnerSpec/main.cpp index e2b8104..12371be 100644 --- a/tests/TestRunnerSpec/main.cpp +++ b/tests/TestRunnerSpec/main.cpp @@ -6,9 +6,7 @@ import Crafter.Build; using namespace Crafter; // POSIX env helpers — libc++ exports only std::getenv via `import std`, so -// forward-declare the setters we need against the C library directly. The -// test only runs on POSIX hosts (the project.cpp gates new tests by -// x86_64-pc-linux-gnu) so we don't need a Windows alternative here. +// forward-declare the setters we need against the C library directly. extern "C" int setenv(const char* name, const char* value, int overwrite); extern "C" int unsetenv(const char* name); @@ -87,13 +85,11 @@ int main() { Check(r.exec.find("QEMU_LD_PREFIX=/opt/alarm-sysroot") != std::string::npos, "ForTarget propagates sysroot to QEMU_LD_PREFIX"); } { - // From a Linux host the only sensible way to run x86_64-w64-mingw32 is wine. + // The only way to run x86_64-w64-mingw32 output is wine. Configuration cfg; cfg.target = "x86_64-w64-mingw32"; TestRunner r = TestRunner::ForTarget(cfg); - if (HostTarget().find("linux") != std::string::npos) { - Check(r.name == "wine", "ForTarget(mingw) on Linux host uses wine"); - } + Check(r.name == "wine", "ForTarget(mingw) uses wine"); } // FromEnv: when the env var is set, it wins over the fallback.