diff --git a/build.cmd b/build.cmd index 33d3430..7e2f539 100644 --- a/build.cmd +++ b/build.cmd @@ -17,6 +17,7 @@ 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-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 @@ -55,6 +56,9 @@ clang++ %common_options% -fmodule-output interfaces\Crafter.Build-External.cppm 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-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 @@ -65,6 +69,7 @@ clang++ %common_options% .\implementations\Crafter.Build-External.cpp -o .\build 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-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 @@ -79,6 +84,7 @@ clang++ %useLibcLinker% -shared -std=c++26 -O3 -march=%CRAFTER_BUILD_MARCH% -mtu .\build\Crafter.Build-Clang.o ^ .\build\Crafter.Build-Test.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 ^ @@ -88,6 +94,7 @@ clang++ %useLibcLinker% -shared -std=c++26 -O3 -march=%CRAFTER_BUILD_MARCH% -mtu .\build\Crafter.Build-Clang_impl.o ^ .\build\Crafter.Build-Test_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 @@ -106,6 +113,7 @@ llvm-lib.exe /OUT:.\lib\crafter-build-static.lib ^ .\build\Crafter.Build-Clang.o ^ .\build\Crafter.Build-Test.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 ^ @@ -114,6 +122,7 @@ llvm-lib.exe /OUT:.\lib\crafter-build-static.lib ^ .\build\Crafter.Build-External_impl.o ^ .\build\Crafter.Build-Clang_impl.o ^ .\build\Crafter.Build-Test_impl.o ^ - .\build\Crafter.Build-Progress_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 ef2c886..4a51e38 100755 --- a/build.sh +++ b/build.sh @@ -16,6 +16,7 @@ cp interfaces/Crafter.Build-External.cppm share/crafter-build/ cp interfaces/Crafter.Build-Clang.cppm share/crafter-build/ cp interfaces/Crafter.Build-Test.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 @@ -52,6 +53,11 @@ clang++ $common_options -fmodule-output interfaces/Crafter.Build-External.cppm - 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-Progress.cppm -o ./build/Crafter.Build-Progress.o +# Asset partition: bootstrap compiles it WITHOUT CRAFTER_BUILD_HAS_ASSET so +# CompressAsset takes the stub branch and the impl unit doesn't try to +# `import Crafter.Asset` (no Crafter.Asset PCM exists at this stage — the +# self-host pass that follows wires in the real dep). +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 @@ -62,6 +68,7 @@ clang++ $common_options ./implementations/Crafter.Build-External.cpp -o ./build/ 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-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 ar rcs ./lib/libcrafter-build.a \ @@ -73,6 +80,7 @@ ar rcs ./lib/libcrafter-build.a \ ./build/Crafter.Build-Clang.o \ ./build/Crafter.Build-Test.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 \ @@ -81,7 +89,8 @@ ar rcs ./lib/libcrafter-build.a \ ./build/Crafter.Build-External_impl.o \ ./build/Crafter.Build-Clang_impl.o \ ./build/Crafter.Build-Test_impl.o \ - ./build/Crafter.Build-Progress_impl.o + ./build/Crafter.Build-Progress_impl.o \ + ./build/Crafter.Build-Asset_impl.o clang++ -std=c++26 -stdlib=libc++ -O3 -march=$MARCH -mtune=$MTUNE -fuse-ld=lld \ -Wl,--export-dynamic \ @@ -94,6 +103,7 @@ clang++ -std=c++26 -stdlib=libc++ -O3 -march=$MARCH -mtune=$MTUNE -fuse-ld=lld \ ./build/Crafter.Build-Clang.o \ ./build/Crafter.Build-Test.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 \ @@ -103,6 +113,7 @@ clang++ -std=c++26 -stdlib=libc++ -O3 -march=$MARCH -mtune=$MTUNE -fuse-ld=lld \ ./build/Crafter.Build-Clang_impl.o \ ./build/Crafter.Build-Test_impl.o \ ./build/Crafter.Build-Progress_impl.o \ + ./build/Crafter.Build-Asset_impl.o \ ./build/main.o \ -lSPIRV -lGenericCodeGen -lglslang -lOSDependent -lMachineIndependent -lglslang-default-resource-limits \ -ldl \ diff --git a/implementations/Crafter.Build-Asset.cpp b/implementations/Crafter.Build-Asset.cpp new file mode 100644 index 0000000..7bd227a --- /dev/null +++ b/implementations/Crafter.Build-Asset.cpp @@ -0,0 +1,55 @@ +/* +Crafter® Build +Copyright (C) 2026 Catcrafts® +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 3.0 as published by the Free Software Foundation; + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +module Crafter.Build:Asset_impl; +import :Asset; +import std; +#ifdef CRAFTER_BUILD_HAS_ASSET +import Crafter.Asset; +import Crafter.Math; +#endif +namespace fs = std::filesystem; + +namespace Crafter { + std::string CompressAsset(const fs::path& input, const fs::path& output) { + std::string ext = input.extension().string(); +#ifdef CRAFTER_BUILD_HAS_ASSET + try { + if (ext == ".png") { + auto tex = TextureAsset>::LoadPNG(input); + tex.SaveCompressed(output); + } else if (ext == ".obj") { + auto mesh = MeshAsset::LoadOBJ(input); + mesh.SaveCompressed(output); + } else { + return std::format("{}: unsupported asset extension '{}'", input.string(), ext); + } + } catch (const std::exception& e) { + return std::format("{}: {}", input.string(), e.what()); + } + return {}; +#else + return std::format( + "{}: crafter-build was bootstrapped without Crafter.Asset linkage; " + "rebuild via `./bin/crafter-build` so the self-host pass picks up the " + "Crafter.Asset dep declared in project.cpp", + input.string()); +#endif + } +} diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index 992af08..fc7e636 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -23,6 +23,7 @@ import :Clang; import :Platform; import :Test; import :Progress; +import :Asset; namespace fs = std::filesystem; using namespace Crafter; @@ -291,6 +292,38 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map std::optional { + std::string ext = asset.extension().string(); + if (ext == ".png") return fs::path(asset.filename()).replace_extension(".ctex"); + if (ext == ".obj") return fs::path(asset.filename()).replace_extension(".cmesh"); + return std::nullopt; + }; + for (const fs::path& asset : config.assets) { + std::optional outName = assetOutput(asset); + if (!outName) { + buildCancelled.store(true); + buildError = std::format("{}: unsupported asset extension (expected .png or .obj)", asset.string()); + break; + } + fs::path out = outputDir / *outName; + if (fs::exists(out) && fs::exists(asset) && fs::last_write_time(asset) <= fs::last_write_time(out)) continue; + threads.emplace_back([&asset, out = std::move(out), &buildError, &buildCancelled]() { + Progress::Task task(std::format("Compressing asset {}", asset.filename().string())); + if (buildCancelled.load(std::memory_order_relaxed)) return; + std::string result = CompressAsset(asset, out); + if (result.empty()) return; + bool expected = false; + if (buildCancelled.compare_exchange_strong(expected, true)) { + buildError = std::move(result); + } + }); + } + threads.emplace_back([&config, &outputDir, &buildCancelled, &buildError]() { Progress::Task task(std::format("Copying files for {}", config.name)); if (buildCancelled.load(std::memory_order_relaxed)) return; @@ -432,16 +465,20 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map repack(false); + // -I propagation that's valid for both C and C++ compiles. Module-only + // bits (-fprebuilt-module-path) stay on `command` only. + std::string includeFlags; { std::unordered_set seen; std::function addFlags = [&](Configuration* dep) { if (!seen.insert(dep).second) return; for (const auto& entry : fs::recursive_directory_iterator(dep->path)) { if (entry.is_directory() && entry.path().filename() == "include") { - command += " -I" + entry.path().string(); + includeFlags += " -I" + entry.path().string(); } } - command += std::format(" -I{} -fprebuilt-module-path={}", dep->path.string(), dep->PcmDir().string()); + includeFlags += std::format(" -I{}", dep->path.string()); + command += std::format(" -fprebuilt-module-path={}", dep->PcmDir().string()); for (Configuration* sub : dep->dependencies) { addFlags(sub); } @@ -450,6 +487,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map fs::last_write_time(objPath))) { - threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config]() { + threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config, &includeFlags, &defineFlags, &userFlags]() { Progress::Task task(std::format("Compiling {}.c", cFile.filename().string())); if (buildCancelled.load(std::memory_order_relaxed)) return; - std::string result = RunCommand(std::format("clang {}.c --target={} -march={} -mtune={} -O3 -c -o {}_source.o", cFile.string(), config.target, config.march, config.mtune, (buildDir / cFile.filename()).string())); + std::string result = RunCommand(std::format("clang {}.c --target={} -march={} -mtune={} -O3 -c{}{}{} -o {}_source.o", cFile.string(), config.target, config.march, config.mtune, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string())); if (result.empty()) return; bool expected = false; @@ -632,6 +678,16 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_mapassets) { + std::string ext = asset.extension().string(); + fs::path srcName = asset.filename(); + if (ext == ".png") srcName.replace_extension(".ctex"); + else if (ext == ".obj") srcName.replace_extension(".cmesh"); + else continue; + fs::path src = depBinDir / srcName; + if (!fs::exists(src)) continue; + copyTree(src, outputDir / srcName); + } for (Configuration* sub : dep->dependencies) forwardDepArtifacts(sub); }; for (Configuration* dep : config.dependencies) forwardDepArtifacts(dep); diff --git a/implementations/Crafter.Build-Platform.cpp b/implementations/Crafter.Build-Platform.cpp index 363432b..16bdc81 100644 --- a/implementations/Crafter.Build-Platform.cpp +++ b/implementations/Crafter.Build-Platform.cpp @@ -227,7 +227,7 @@ fs::path Crafter::GetCacheDir() { } namespace { - constexpr std::array kCrafterBuildModules = { + constexpr std::array kCrafterBuildModules = { "Crafter.Build-Shader", "Crafter.Build-Platform", "Crafter.Build-Interface", @@ -236,6 +236,7 @@ namespace { "Crafter.Build-Clang", "Crafter.Build-Test", "Crafter.Build-Progress", + "Crafter.Build-Asset", "Crafter.Build", }; } @@ -737,7 +738,7 @@ std::string Crafter::GetBaseCommand(const Configuration& config) { } namespace { - constexpr std::array kCrafterBuildModules = { + constexpr std::array kCrafterBuildModules = { "Crafter.Build-Shader", "Crafter.Build-Platform", "Crafter.Build-Interface", @@ -746,6 +747,7 @@ namespace { "Crafter.Build-Clang", "Crafter.Build-Test", "Crafter.Build-Progress", + "Crafter.Build-Asset", "Crafter.Build", }; diff --git a/interfaces/Crafter.Build-Asset.cppm b/interfaces/Crafter.Build-Asset.cppm new file mode 100644 index 0000000..8bb4cbe --- /dev/null +++ b/interfaces/Crafter.Build-Asset.cppm @@ -0,0 +1,39 @@ +/* +Crafter® Build +Copyright (C) 2026 Catcrafts® +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 3.0 as published by the Free Software Foundation; + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +module; +#include "Crafter.Build-Api.h" +export module Crafter.Build:Asset; +import std; +namespace fs = std::filesystem; + +namespace Crafter { + // Compress a build-time asset (.png/.obj) into its on-disk + // SaveCompressed counterpart (.ctex/.cmesh) using Crafter.Asset's + // library API in-process. Returns "" on success, an error message + // otherwise (matches Shader::Compile's contract). + // + // Real implementation lives in the impl unit and only compiles when + // CRAFTER_BUILD_HAS_ASSET is defined (i.e. when the build links + // Crafter.Asset). The bootstrap build.sh produces a stub variant + // 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); +} diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index 0e89ce1..64d9403 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -125,6 +125,15 @@ export namespace Crafter { std::vector buildFiles; std::vector defines; std::vector shaders; + // Source assets (.png, .obj) compressed via Crafter.Asset's + // SaveCompressed → .ctex/.cmesh in this configuration's bin dir. + // Requires Crafter.Asset in cfg.dependencies (transitively): the + // build engine locates it by name and uses its library API to do + // the conversion (an auxiliary host-target executable, generated + // once per build host, links Crafter.Asset and is invoked per + // asset). Forwarded to a consuming executable's bin dir alongside + // .spv shaders and cfg.files entries. + std::vector assets; std::vector externalDependencies; std::vector compileFlags; std::vector linkFlags; diff --git a/interfaces/Crafter.Build.cppm b/interfaces/Crafter.Build.cppm index faf3f72..20de855 100644 --- a/interfaces/Crafter.Build.cppm +++ b/interfaces/Crafter.Build.cppm @@ -24,4 +24,5 @@ export import :Implementation; export import :Shader; export import :External; export import :Test; -export import :Progress; \ No newline at end of file +export import :Progress; +export import :Asset; \ No newline at end of file diff --git a/project.cpp b/project.cpp index 328679a..00deb51 100644 --- a/project.cpp +++ b/project.cpp @@ -4,6 +4,30 @@ namespace fs = std::filesystem; using namespace Crafter; extern "C" Configuration CrafterBuildProject(std::span args) { + std::vector depArgs(args.begin(), args.end()); + + // Local-mode resolution for the Crafter.Math/Crafter.Asset deps that the + // self-host pass links in (so `cfg.assets` calls CompressAsset → Crafter.Asset + // in-process). `--local` points at sibling working trees instead of git; + // useful during cross-repo development so edits in ../Crafter.Asset are + // picked up without commit-and-pull. + bool useLocal = false; + for (std::string_view a : args) { + if (a == "--local") { useLocal = true; break; } + } + auto resolveDep = [&](std::string_view name, std::string_view gitUrl) -> Configuration* { + if (useLocal) { + return LocalProject({ + .projectFile = fs::path("../") / name / "project.cpp", + .args = depArgs, + }); + } + return GitProject({ + .source = { .url = std::string(gitUrl) }, + .args = depArgs, + }); + }; + static auto crafterBuildLib = std::make_unique(); crafterBuildLib->path = "./"; crafterBuildLib->name = "crafter.build-lib"; @@ -15,8 +39,16 @@ extern "C" Configuration CrafterBuildProject(std::span a crafterBuildLib->type = (crafterBuildLib->target == "x86_64-w64-mingw32" || crafterBuildLib->target == "x86_64-pc-windows-msvc") ? ConfigurationType::LibraryDynamic : ConfigurationType::LibraryStatic; + // Self-host pass links Crafter.Asset (and its Crafter.Math dep) into the + // crafter-build binary so cfg.assets can call CompressAsset in-process. + // The bootstrap (build.sh) defines no such dep — its impl unit takes the + // stub branch and cfg.assets errors at runtime until a self-host rebuild. + Configuration* math = resolveDep("Crafter.Math", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git"); + Configuration* asset = resolveDep("Crafter.Asset", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git"); + crafterBuildLib->dependencies = { math, asset }; + crafterBuildLib->defines.push_back({"CRAFTER_BUILD_HAS_ASSET", ""}); { - std::array interfaces = { + std::array interfaces = { "interfaces/Crafter.Build", "interfaces/Crafter.Build-Shader", "interfaces/Crafter.Build-Platform", @@ -26,8 +58,9 @@ extern "C" Configuration CrafterBuildProject(std::span a "interfaces/Crafter.Build-Clang", "interfaces/Crafter.Build-Test", "interfaces/Crafter.Build-Progress", + "interfaces/Crafter.Build-Asset", }; - std::array implementations = { + std::array implementations = { "implementations/Crafter.Build-Shader", "implementations/Crafter.Build-Platform", "implementations/Crafter.Build-Interface", @@ -36,6 +69,7 @@ extern "C" Configuration CrafterBuildProject(std::span a "implementations/Crafter.Build-Clang", "implementations/Crafter.Build-Test", "implementations/Crafter.Build-Progress", + "implementations/Crafter.Build-Asset", }; crafterBuildLib->GetInterfacesAndImplementations(interfaces, implementations); }