Compare commits

...
Author SHA1 Message Date
486935bce8
docs(packaging): add the 2.1.0 release entry
All checks were successful
Flatpak / flatpak (push) Successful in 19m32s
CI takes the newest <release> as the notes of a tag and refuses a tag
that does not match it. 2.1.0 covers what changed since 2.0.0: the
bundled LLVM 23.1.2, build outputs kept per compiler, and the std
module source chosen by the sysroot's libc++ release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:09:22 +02:00
03728fd4a7
docs: describe the output directory name and what a compiler update does
The README still gave the output path as bin/<name>-<target>-<march>/,
which has not been its shape since the variant hash was introduced. It
now shows the real one and says what the hash stands for, including
that a compiler update starts a new directory and `clean` removes the
old. AGENTS.md lists the toolchain among VariantId()'s inputs and notes
that a glob over variant directories matches two after an update.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:09:22 +02:00
995a40e7dd
test(incremental): a compiler update perturbs VariantId
Two toolchains that differ only in the clang release, as the Flatpak's
do across an update, must give one Configuration two variants; so must
the same release installed under another prefix. The no-argument form
is checked to be the variant under the running toolchain, and BuildDir
to be named for it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:09:22 +02:00
b5a366d060
fix(incremental): key build outputs on the compiler
A project built with one clang kept its outputs when crafter-build
started using another. The output directory is named by VariantId(),
which hashed every input that changes what is compiled except the
compiler itself, and staleness is decided by mtimes, which a compiler
update leaves alone. A PCM can only be read by the clang release that
wrote it, so after the bundled LLVM moved from 22 to 23 the first build
found nothing stale, and the first edit failed with "module file
Greeter.pcm uses an older format that is no longer supported" until the
project was cleaned by hand. A project with a `#pragma crafter
dependency` did not get that far: its project file imports the
dependency's PCM, so it failed to load, and for a git dependency that
PCM lives in the cache, where `crafter-build clean` does not reach. The
same happens natively when the distro updates clang.

The toolchain's key now goes into the hash, as the host PCM cache was
already keyed on it. An update starts an empty variant directory and
rebuilds into it; the previous one stays until `clean`, as after any
other change of variant, and two crafter-build versions can work in one
checkout without disturbing each other. VariantId() gains an overload
taking the toolchain so the dependence can be tested in-process, the
way ResolveToolchain and SysrootStdModule take theirs.

External cmake builds are not covered: their directory is fixed per
clone and keyed on the compiler's path, not its release. Objects of two
clang releases link together on the host, so nothing fails there, but
they are not rebuilt either.

Verified by replaying the update: three projects built with the 2.0.0
Flatpak, then built, edited and run with this one without cleaning,
then built with 2.0.0 again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:09:22 +02:00
5 changed files with 54 additions and 7 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 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 `<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.
- `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.

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>/myapp` and intermediates at `build/myapp-<target>-<march>/`. 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>-<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.
## Dependencies

View file

@ -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<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) 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<std::int32_t>(type));
compileKey += '|';
@ -521,6 +533,8 @@ 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,6 +25,12 @@
</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,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);