diff --git a/AGENTS.md b/AGENTS.md index 091807a..574abeb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,7 +110,7 @@ Beyond the rules: - There is no host-platform preprocessor split: the host is Linux. Target differences (mingw, wasm) branch on `cfg.target` strings at runtime, in the library as well as in `project.cpp` and tests. - A build step fails when its command exits nonzero, never because it printed. Use `RunBuildCommand` or `RunCommandChecked`; warnings go to `Progress::Diagnostic`. - Every output path goes through `Configuration::BinDir()`, `BuildDir()` and `PcmDir()`. Hand-rolled `path / "bin" / ...` clobbered variants before; do not add new ones. -- Anything that changes what gets compiled or bundled belongs in `VariantId()`. It already hashes type, debug, sysroot, defines, compile flags, wasm variant flags and uninterpreted project args. +- Anything that changes what gets compiled or bundled belongs in `VariantId()`. It already hashes type, debug, sysroot, defines, compile flags, wasm variant flags, uninterpreted project args and the toolchain (`Toolchain::Key()`). ## Design rules @@ -170,4 +170,4 @@ To release: add a `` to `packaging/flatpak/net.catcrafts.CrafterBuild.m - The bootstrap binary is compiled without `CRAFTER_BUILD_HAS_ASSET`, so `cfg.assets` only works in the self-hosted binary. Changing asset extensions means updating two maps in `Crafter.Build-Clang.cpp`: `assetOutput` and the transitive copy loop. - A warning is printed only when its unit actually recompiles. A build with nothing to do is silent, which is not evidence that a warning is gone. - A std module source only compiles against the headers of its own libc++ release, and a sysroot does not always ship one (Alpine). The Flatpak therefore carries the sources of the release next to the bundled one under `share/crafter-build/libc++-modules//`, one `libcxx--modules` manifest module each. When the bundled LLVM is bumped, keep the release it leaves behind as such a module: sysroots on the stable distro stay on it for months. The native loop has no such directory; stage one by hand under `share/crafter-build/` to test against a foreign sysroot. -- `VariantId()` names the output directory, so CI globs `bin/crafter.build-exe---*/` rather than predicting the hash. +- `VariantId()` names the output directory, so CI globs `bin/crafter.build-exe---*/` rather than predicting the hash. The hash includes the compiler, so after a toolchain update the previous variant's directories stay in `bin/` and `build/` until `clean`, and such a glob matches both. diff --git a/README.md b/README.md index c195fb2..07ae901 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ extern "C" Configuration CrafterBuildProject(std::span a } ``` -Run `crafter-build` in that directory; outputs land at `bin/myapp--/myapp` and intermediates at `build/myapp--/`. Add `-r` to run the produced executable straight after a successful build. +Run `crafter-build` in that directory; outputs land at `bin/myapp----/myapp` and intermediates at `build/myapp----/`. The hash covers everything that changes what is compiled (build type, defines, flags, sysroot, the compiler itself), so two such builds never share a directory. After a compiler update, a new crafter-build release included, the first build starts a fresh directory and recompiles; `crafter-build clean` removes the ones left behind. Add `-r` to run the produced executable straight after a successful build. ## Dependencies diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index 1149661..8a306be 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -7,6 +7,7 @@ import :Shader; import :Interface; import :Implementation; import :External; +import :Toolchain; namespace fs = std::filesystem; export namespace Crafter { @@ -488,10 +489,21 @@ export namespace Crafter { 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 - // hash so two Configurations sharing a path but with different - // compile state can't clobber each other's outputs. - std::string VariantId() const { + // (type, debug, sysroot, defines, compileFlags, the toolchain) + // collapse into a short hash so two Configurations sharing a path but + // with different compile state can't clobber each other's outputs. + std::string VariantId() const { return VariantId(GetToolchain()); } + // The variant under an explicit toolchain; VariantId() is this for + // the running one. The compiler is compile state like any flag: a + // PCM can only be read by the clang release that wrote it, and + // staleness is decided by mtimes, which a compiler update does not + // touch. Before the toolchain was hashed, a project built with clang + // 22 kept its PCMs when crafter-build moved to clang 23, and the + // first edit afterwards failed with "module file ... uses an older + // format that is no longer supported" until the project was cleaned. + // Keyed here, an update starts an empty directory and rebuilds; the + // old one stays until `crafter-build clean`. + std::string VariantId(const Toolchain& toolchain) const { std::string compileKey; compileKey += std::to_string(static_cast(type)); compileKey += '|'; @@ -521,6 +533,8 @@ export namespace Crafter { compileKey += "|A:"; compileKey += a; } + compileKey += "|T:"; + compileKey += toolchain.Key(); std::size_t configHash = std::hash{}(compileKey); return std::format("{}-{}-{}-{}-{:08x}", name, target, march, mtune, configHash); } diff --git a/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml b/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml index afe8428..e63ee54 100644 --- a/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml +++ b/packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml @@ -25,6 +25,12 @@ + + +

The bundled toolchain is now LLVM 23.1.2. Build outputs are kept per compiler, so the first build after updating recompiles each project into a fresh directory and nothing has to be cleaned by hand.

+

Cross builds pick the std module source that matches the sysroot's libc++ release. The source for libc++ 22 ships beside the bundled 23 for sysroots that carry libc++ without one, such as Alpine, and a sysroot nothing matches fails with one line naming both releases. crafter-build --version prints the libc++ release in use.

+
+

First release distributed as a Flatpak, with a bundled LLVM 22 toolchain and WASI sysroot. Windows host support is removed; crafter-build gains --version and resolves its compiler from one place.

diff --git a/tests/VariantId/main.cpp b/tests/VariantId/main.cpp index 022b0d6..e4d2a36 100644 --- a/tests/VariantId/main.cpp +++ b/tests/VariantId/main.cpp @@ -133,6 +133,33 @@ int main() { Check(a.BuildDir() != b.BuildDir(), "different VariantId yields different BuildDir"); Check(a.BinDir() != b.BinDir(), "different VariantId yields different BinDir"); } + { + // The compiler is compile state as much as any flag: a PCM can only + // be read by the clang release that wrote it, and nothing in a + // project's sources changes when the compiler does. With the + // toolchain left out of the id, a project built with clang 22 kept + // its PCMs after crafter-build moved to clang 23; the build after + // the update found nothing stale, and the first edit failed with + // "module file ... uses an older format that is no longer supported" + // until the project was cleaned by hand. + Configuration cfg = MakeBase(); + Toolchain older; + older.clangxx = "/app/bin/clang++"; + older.clangVersion = "clang version 22.1.8"; + Toolchain newer = older; + newer.clangVersion = "clang version 23.1.2"; + Check(cfg.VariantId(older) != cfg.VariantId(newer), "a compiler update at the same path perturbs VariantId"); + Check(cfg.VariantId(older) == cfg.VariantId(older), "VariantId under one toolchain is deterministic"); + + // Two installs of one release are still two toolchains: their + // libc++ need not be the same build. + Toolchain elsewhere = older; + elsewhere.clangxx = "/usr/bin/clang++"; + Check(cfg.VariantId(older) != cfg.VariantId(elsewhere), "the same release installed elsewhere perturbs VariantId"); + + Check(cfg.VariantId() == cfg.VariantId(GetToolchain()), "VariantId() is the variant under the running toolchain"); + Check(cfg.BuildDir().filename() == cfg.VariantId(GetToolchain()), "BuildDir is named for the running toolchain's variant"); + } if (Failures > 0) { std::println(std::cerr, "{} assertions failed", Failures);