Compare commits

..
5 changed files with 7 additions and 54 deletions

View file

@ -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, uninterpreted project args and the toolchain (`Toolchain::Key()`).
- 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.
## Design rules
@ -170,4 +170,4 @@ To release: add a `<release>` 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/<major>/`, one `libcxx-<major>-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-<target>-<march>-*/` 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.
- `VariantId()` names the output directory, so CI globs `bin/crafter.build-exe-<target>-<march>-*/` rather than predicting the hash.

View file

@ -52,7 +52,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
}
```
Run `crafter-build` in that directory; outputs land at `bin/myapp-<target>-<march>-<mtune>-<hash>/myapp` and intermediates at `build/myapp-<target>-<march>-<mtune>-<hash>/`. 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.
Run `crafter-build` in that directory; outputs land at `bin/myapp-<target>-<march>/myapp` and intermediates at `build/myapp-<target>-<march>/`. Add `-r` to run the produced executable straight after a successful build.
## Dependencies

View file

@ -7,7 +7,6 @@ import :Shader;
import :Interface;
import :Implementation;
import :External;
import :Toolchain;
namespace fs = std::filesystem;
export namespace Crafter {
@ -489,21 +488,10 @@ export namespace Crafter {
void AddAstLintRule(std::string name, std::function<void(LintContext&)> 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, 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 {
// (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 {
std::string compileKey;
compileKey += std::to_string(static_cast<std::int32_t>(type));
compileKey += '|';
@ -533,8 +521,6 @@ export namespace Crafter {
compileKey += "|A:";
compileKey += a;
}
compileKey += "|T:";
compileKey += toolchain.Key();
std::size_t configHash = std::hash<std::string>{}(compileKey);
return std::format("{}-{}-{}-{}-{:08x}", name, target, march, mtune, configHash);
}

View file

@ -25,12 +25,6 @@
</provides>
<content_rating type="oars-1.1"/>
<releases>
<release version="2.1.0" date="2026-10-03">
<description>
<p>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.</p>
<p>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.</p>
</description>
</release>
<release version="2.0.0" date="2026-10-03">
<description>
<p>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.</p>

View file

@ -133,33 +133,6 @@ 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);