Commit graph

569 commits

Author SHA1 Message Date
16db37bbe6
feat(flatpak): ship lldb and the Khronos validation layers
Programs that -r and the test runner start run inside the Flatpak's
sandbox, and the Sdk it runs on has gdb but neither lldb nor the Khronos
validation layer: in the sandbox of beta 5.0.0, a vkCreateInstance that
asks for VK_LAYER_KHRONOS_validation returns VK_ERROR_LAYER_NOT_PRESENT.
flatpak run clears VK_LAYER_PATH and VK_ADD_LAYER_PATH, so the layer the
host has cannot stand in.

lldb becomes a project of the llvm module and brings lldb-server,
lldb-dap and lldb-mcp. Its Python, libedit, curses, libxml2 and lzma
support are set ON instead of being left to detection, so a dependency
that goes missing fails the configure step rather than shipping an lldb
without it. The Sdk has neither SWIG nor libedit, so both become modules
before llvm, SWIG as build-only.

The layer is Vulkan-ValidationLayers vulkan-sdk-1.4.357.0, the Vulkan
SDK release of the Sdk's loader (libvulkan.so.1.4.357), built from
Vulkan-Headers, Vulkan-Utility-Libraries, SPIRV-Headers and SPIRV-Tools
at the revisions that release's scripts/known_good.json names. Those
four are built into /app at those revisions and removed afterwards. All
five come after the LLVM and WASI modules, so that bumping them reuses
the cached LLVM and WASI builds. UPDATE_DEPS is turned off: it defaults
to ON, which has update_deps.py clone the dependencies, and of all the
modules only crafter-build's build-args share the network.
2026-11-01 19:20:22 +01:00
e2bd0b14ee
feat(review): set up Crafter.Review's pre-push review
All checks were successful
Flatpak / flatpak (push) Successful in 13m7s
Crafter.Review reviews a frozen commit range before it is pushed: the
approach, a static proof of the code's bugs, and every claim in the
messages and in the documentation the range changes, one claude process
per lens and unit, or per whole-range pass, and the owner approves the
exact frozen hash before the push. This sets it up for this repository.

.review-gates/config.json turns on three lenses in the commits mode, at
xhigh: repo-better-solution, repo-bug-prover and repo-facts. The
functional lens is off: it exercises a web application through a
browser, and this repository's product is a command-line tool. The
security lens is off by the owner's ruling: crafter-build stores no
secret of its own, and while its Flatpak runs with the user's SSH agent
(--socket=ssh-auth) and the whole host filesystem (--filesystem=host),
everything it runs is code its user chose to run. The rules CI keeps
around its token and signing key go to the approach lens's section
instead.

gate0 lists the commands run by hand before a freeze. build.sh comes
first, because bin/crafter-build compiles project.cpp against the
interfaces it was bootstrapped with, and lint comes after the self-host
build, because lint's AST rules parse with the module PCMs a build
leaves and lint builds only the std PCM itself.

.review-gates/lenses/ holds this repository's section of each lens's
charter, appended to the text Crafter.Review ships: that AGENTS.md is
absent from the frozen tree, the layers and the consumers of
interfaces/, the execution contexts a bug can hide in, where libc++ and
clang are readable from the review's sandbox, and what a "passes the
suite" claim traces to.

The /prepush skill that crafter-review init writes stays untracked. It
is generated from the installed Crafter.Review and refreshed by every
init, so a committed copy would lag the binary it describes. One init
per checkout restores it, which AGENTS.template.md now says, together
with the rules freeze holds every commit of a range to: signed
(requireSigned), with the owner's address as its author and committer
(authors), and no AI attribution trailer (aiTrailers forbid).
2026-11-01 17:33:27 +01:00
a3fe1bdda7
docs(packaging): add the 5.0.0 release entry
All checks were successful
Flatpak / flatpak (push) Successful in 11m53s
5.0.0
The fix for #43, a mingw build that saw the host's glibc headers and
Linux libraries through a prefix like /usr as clang's sysroot, and what
the review of it brought along: units that recompile after a std module
rebuilt in place, CMake externals built again when their configure
inputs change, std modules keyed on every flag they are compiled with,
and lint that fails rather than passing files it could not parse. A
major, because Toolchain::mingwSysroot is now mingwPrefix and every
build directory moves once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:55:28 +01:00
76831d6733 Merge branch 'claude/issue-43': mingw builds without the host's headers, std modules keyed on their flags
All checks were successful
Flatpak / flatpak (push) Successful in 11m41s
Brings in the fix for #43. The mingw target no longer gets a system
prefix (the host's /usr in the Flatpak, or any prefix whose include/
has a C library's features.h) as clang's --sysroot, but a view of its
mingw-w64 parts, made in the cache or in $XDG_RUNTIME_DIR; a prefix of
mingw-w64 alone stays the sysroot whole. From the review of the first
version: the std module's directory is keyed on every flag it is
compiled with and VariantId on that key, so every std PCM and build
directory moves once; units record the BMIs they import; a CMake
external whose configure inputs changed starts from an empty build
directory; lint fails a configuration that has no compile command.
Toolchain::mingwSysroot is now mingwPrefix.

The MingwSysroot test writes its stand-in std module through
std::format, because the module scanner reads string literals as code.
#45 replaces the scanner's regexes with clang-scan-deps.

The branch starts at master's tip, so the merge is a tree identical to
the branch's; that tree passes format, lint and the test suite, natively
and inside the Flatpak sandbox, and CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:35:49 +00:00
c56d98abba
docs: which mingw prefixes are viewed, and what keys the std module
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m28s
The README said every prefix reached clang as a view of its
x86_64-w64-mingw32/ and lib/gcc/, in ~/.cache. It now says a prefix is
the sysroot as it is unless it also holds a Linux system, names
lib64/gcc/ as well, gives the view's place inside the Flatpak and the
XDG_RUNTIME_DIR fallback, and says messages name headers by their path
in the prefix.

AGENTS.template.md describes MingwSysroot's two outcomes and
MingwViewToPrefix, has the std PCM keyed on its flags in the cache
rule, lists StdPcmKey among what VariantId hashes, and gains two
gotchas: anything that changes how the std module is compiled goes into
StdPcmFlags, and a CMake external whose recorded configure inputs
differ is built again from an empty directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:47 +01:00
55ad128b79
test: lint fails when a configuration's std module cannot be built
A host configuration pointed at a sysroot without libc++ cannot get its
std module built, so its files cannot be parsed. With an AST rule
registered, the run has to skip the rule and fail instead of reporting
the files clean, as it did before the fix. The cache is redirected
around this run only: the std module's directory is made before its
build fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:38 +01:00
40dcf2271d
fix(lint): fail the files whose configuration has no compile command
When GetCompileCommand threw or the std module failed to build, the
lint driver cleared the configuration's command, and an empty command
means "not a translation unit" (project.cpp, a header): every rule that
needs an AST skipped the files quietly and the run passed them as clean.
MingwSysroot made that reachable for every mingw configuration whose
view cannot be made. The reason is now kept per configuration and
reported as an AST failure for each of its files, which fails the run
and prints it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:37 +01:00
0e69e71b11
test: a CMake external configured with other inputs is built from nothing
ExternalReconfigure builds a one-file C library from a local repository
as a CMake external, then plants a file in its build directory to stand
for what an old configure left. Built again with the same inputs, the
directory is kept. With its recorded inputs rewritten the way another
crafter-build's configure would have left them, the next build has to
start from an empty directory and build the library again. Before the
fix the planted file survived the reconfigure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:23 +01:00
56b44f616a
fix(external): start a CMake build over when its configure inputs change
ConfigureCMake records the flags it injects (compiler, libc++, and for
the mingw target CMAKE_SYSROOT) and reconfigures when they differ, but
it did so in place. CMake then keeps what the old flags compiled: the
Makefiles generator writes --sysroot into build.make, which make does
not compare, so nothing was recompiled, and the check results the old
configure cached stay too. A mingw external built by a release that gave
clang the host's /usr as the sysroot kept its objects reading
__libc_single_threaded after the fix for #43, and only deleting
<cache>/external/<name>-<hash>/build by hand got rid of them.

A build directory with a CMakeCache.txt whose recorded inputs differ is
now removed before the configure. It keeps its name, since projects
point include directories at headers generated in it. The two
crafter-builds the Flatpak's branches are, sharing one cache, rebuild
such an external from nothing each time they take turns, where they
used to reuse each other's objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:22 +01:00
8e759a8410
test: a std module rebuilt in place recompiles the units importing it
StdPcmRebuild builds a project whose main.cpp imports std, then dates
the source before the object and the object before std.pcm, as a std
module rebuilt after the object leaves them, so that nothing else makes
the object stale. The next build has to compile main.cpp again, and the
idle build before it must compile nothing. Without -fmodule-file-deps
the object was only relinked. The test redirects the cache, since
redating a std PCM the real cache shares would rebuild its other users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:06 +01:00
27697d06aa
fix: recompile the units whose std module was rebuilt in place
A .cpp unit's depfile listed its source and headers, never the BMIs it
imports: clang's driver adds -module-file-deps to --precompile only. A
std module rebuilt in its directory, which happens whenever its source
changes under one compiler and one set of flags (a libc++ update, or
mingw-w64's under the same prefix), left every object that imports it
as it was, and the next build only relinked them. Implementation units
are now compiled with -fmodule-file-deps, so the std PCM and the
project's own PCMs are prerequisites of their objects like headers.

Depfiles written before this lack the edge, so a std module that moves
directories still needs VariantId to move with it (StdPcmKey).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:59:05 +01:00
5d25495621
fix: key the std module's directory on everything it is compiled with
StdPcmDir named the directory by target, march and toolchain, then
grew a case per target for the rest: the wasm flags, and with the first
fix for #43 the mingw prefix. Every other flag BuildStdPcm passes went
unkeyed, so configurations that differed only there took turns
rebuilding one std PCM in place, under the PCMs built against it: a
project with -mtune=generic against project.cpp's -mtune=native on every
run, or two sysroots for one target. And the mingw prefix was keyed only
when there was one, so a build without a prefix landed in the directory
an older release had filled with a CRAFTER_MINGW_DIR=/usr build, and
relinked its objects.

StdPcmFlags is now the one source of the flags BuildStdPcm compiles
with, and StdPcmKey names the directory by target, march and toolchain
followed by a hash of those flags and, for the mingw target, of the
prefix behind its --sysroot. VariantId hashes StdPcmKey in place of the
mingw prefix: units do not notice a std module that moved directories,
so a build has to move with it. Every std PCM directory and build
directory moves once, as after a compiler update; the old ones stay
until `clean` and the cache is cleared by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:58:44 +01:00
06b61e84f9
fix(mingw): view only a prefix that holds a Linux system
The first fix showed clang every mingw prefix through a view of its
mingw-w64 parts. That broke a prefix that holds mingw-w64 alone, where
Windows libraries installed with --prefix land in include/ and lib/:
with CRAFTER_MINGW_DIR=/opt/mingw, <SDL2/SDL.h> and -lSDL2 were no
longer found. Such a prefix is clang's sysroot whole again. Only a
prefix that also holds a Linux system gets the view
(Toolchain::mingwPrefixIsSystem): the host's /usr inside the Flatpak,
whatever its include/ holds, and a prefix whose include/ has a C
library's features.h, which every glibc and musl header includes and
mingw-w64 has no use for, as CRAFTER_MINGW_DIR=/usr has.

MakeMingwSysroot makes the view in the cache, or in
$XDG_RUNTIME_DIR/crafter.build when the cache's filesystem takes no
symbolic links (vfat, exFAT, CIFS without unix extensions): the XDG
spec promises them there, and nothing else in the cache needs them.
Its path is absolute, because CMake runs the compiler from directories
of its own and a relative cache did not resolve from there. The prefix
is resolved once, absolute and lexically normal, so /opt/mingw/ and
/opt/mingw are one prefix and not two builds.

clang names a header by the path it found it at, so every diagnostic
from a viewed mingw-w64 header named a hashed cache directory.
RunBuildCommand now writes paths into the view as paths into the
prefix, as BuildStdPcm did for its manifest messages already.

Also from the review of PR #44: SysrootCompileFlags builds a mingw
command, which can make a view, so it redirects the cache like the
other tests; the manifest filter's comment names what it still keeps
out; the C path asks MingwSysroot() once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:58:30 +01:00
5fdbf2baee
docs: the mingw sysroot view in the README and AGENTS.template.md
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m2s
The README says which directories of a mingw-w64 prefix clang sees, and
why the rest of a prefix like /usr is kept out of a Windows build.
AGENTS.template.md names MingwSysroot in the :Toolchain partition, with
the reason the prefix itself is never a sysroot, counts the views among
what goes in the cache, and adds the mingw prefix to what VariantId
hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:48:06 +01:00
b162cc0552
test: a mingw build sees no header from its prefix's include/
MingwSysroot stages a prefix shaped like a Linux system's /usr with a
mingw-w64 cross toolchain in it, without needing a real one: glibc's
<sys/single_threaded.h> in include/, gcc's per-target directory, and a
stand-in mingw-w64 tree whose libstdc++ has an empty std module. A
static library for x86_64-w64-mingw32, so that nothing is linked,
compiles a C++ and a C source that stop with #error when they see the
prefix's include/ or miss mingw-w64's or libstdc++'s headers. Against
the library before the fix, the C++ source stopped on
<sys/single_threaded.h>. The test also checks that the std module is
not cached in the directory older releases built it in against the
prefix itself. It needs only clang, so it runs in CI too, where no
mingw-w64 is installed.

The stand-in std module's source is written through std::format: the
build's import scanner reads string literals as code, and the literal
"export module std;" made the test itself a unit of a module std.

VariantId checks that a prefix gives a mingw configuration a variant of
its own, that two prefixes give two, and that other targets ignore it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:48:06 +01:00
ab998081e1
fix: give the mingw target a sysroot that holds mingw-w64 alone
Inside the Flatpak the mingw-w64 prefix is the host's /usr, and it was
clang's --sysroot as is. clang's mingw driver takes a sysroot for a tree
that holds mingw-w64 alone, and adds its include/ to the include path
and its lib/ to the library path. For /usr those are the Linux system's
own: every header mingw-w64 lacks came from glibc, and libstdc++'s
<ext/atomicity.h>, finding <sys/single_threaded.h>, had shared_ptr read
__libc_single_threaded, which nothing on Windows defines. Every program
that used one failed to link, among them every Crafter.Graphics Win32
executable, through Crafter.Asset's GLB loader. CRAFTER_MINGW_DIR=/usr
did the same outside the Flatpak (#43).

The sysroot is now MingwSysroot(): a directory in the cache, one per
prefix, holding only links to the prefix's x86_64-w64-mingw32/, lib/gcc/
and lib64/gcc/ (where openSUSE keeps gcc). Through it clang finds
mingw-w64, gcc's version and libstdc++ exactly as it does through the
mingw gcc on PATH, and nothing else. It is made by the first mingw
compile, link or CMake configure of a process, so no other command
writes to the cache for it and a failure to make it is that build's
error. The Toolchain field is now mingwPrefix, since clang never gets
the prefix itself, and it is made absolute when the toolchain is
resolved, as it ends up in link targets and directory names. Messages
keep naming paths under the prefix rather than under the view.

The prefix is also part of the mingw target's VariantId and of its std
PCM directory. Units do not track the std PCM they import: when the
fixed release built in a directory a broken build had left, the std
module was rebuilt and the objects compiled against glibc's headers
were only relinked, failing as before. As with a compiler update, a new
directory is what gets them rebuilt. It also keeps two releases that
share a cache, as the Flatpak's branches do, from rebuilding one std PCM
under each other's PCMs, and two prefixes apart. Without a prefix both
stay as they were: clang's own discovery has not changed.

Two shorter ways do not work: --sysroot=<prefix>/x86_64-w64-mingw32
loses the directory gcc's version names, so <string> is not found, and
clang ignores --gcc-toolchain and --gcc-install-dir for this target.

Fixes #43

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:48:06 +01:00
909e027698
feat(ci): leave the install commands out of the release notes
All checks were successful
Flatpak / flatpak (push) Successful in 11m5s
4.0.0
Every release page ended in the two flatpak commands that install
crafter-build, which the top of the README already gives. The notes
now stop at their last section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:36:32 +01:00
7dd9785058
docs(packaging): add the 4.0.0 release entry
Some checks failed
Flatpak / flatpak (push) Has been cancelled
AGENTS.md written from a tracked AGENTS.template.md, so the dependency
notes no longer change a file the project commits. A major, because a
project that kept 3.0.0's notes section in its committed AGENTS.md
stops getting it updated until it moves to the template: the rule is a
major whenever the notes have a Breaking section, even one that no
build fails over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:27:31 +01:00
7678286f0d
Merge branch 'claude/agents-template': AGENTS.md written from a tracked AGENTS.template.md
All checks were successful
Flatpak / flatpak (push) Successful in 11m17s
Brings in the template: a project commits AGENTS.template.md, and
`crafter-build` and `crafter-build test` write a gitignored AGENTS.md
from it with the dependency notes merged in, so a change to a
dependency's notes no longer shows up in the consumer's diff. An
AGENTS.md no build wrote is never replaced, and without a template
AGENTS.md is left alone, with a warning when it still holds a 3.0.0
section. This repository moves its own AGENTS.md to the template.

The branch starts at master's tip, so the merge is a tree identical to
the branch's; that tree passes format --check, lint and the test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:56:27 +01:00
5c3e9a874b
docs: keep this repository's agent notes in AGENTS.template.md
This repository now uses the template itself. AGENTS.md is written by
the self-host build and `test` and is gitignored, so the notes
Crafter.Math ships reach the agents working here, and a change to them
no longer leaves a tracked file modified, which also made every build
afterwards a dirty-tree version, a new crafter.build-lib variant and a
full rebuild.

The gotcha that described the old arrangement now says where to edit,
that a fresh clone or worktree has no AGENTS.md until its first build
(./build.sh does not write it), and what an older harness does to the
generated file. The paths table and the never-commit list name both
files. The CI error for a missing signing key and a manifest comment
point at the template, since the forge no longer shows an AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:56:11 +01:00
2a5f541485
docs: AGENTS.template.md in the README
The dependency notes section now starts from the template: what to
commit and what to ignore, the two commands that move a project with a
committed AGENTS.md over, what a build writes, where the section goes,
and what becomes of an AGENTS.md no build wrote and of one that still
holds a section from 3.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:53:42 +01:00
74e8370b2b
feat(agents): write AGENTS.md from a tracked AGENTS.template.md
The dependency notes were kept in a section of the AGENTS.md beside
project.cpp, the file a project commits, so a build could change a
tracked file at any time: a dependency editing its AGENTS.consumer.md,
an unpinned one moving on, a flag pulling a dependency in or out, each
showed up in the consumer's diff as a change nobody made. Users
reported it as the feature clobbering their diffs.

A project now commits its instructions as AGENTS.template.md, and a
build and a test run write AGENTS.md from it: a first line saying the
file is generated and where to edit, the template's text as written,
then the notes section, after it or where the template's marker lines
put it. AGENTS.md is build output and goes into .gitignore. It is
created when missing, since a fresh clone has none, and otherwise
written as before: only when its text changes, in place and under a
lock, so links, ownership and a read-only file are respected.

The first line is also how a build tells its own AGENTS.md from one
someone wrote. Beside a template the second is easily there, as a
committed AGENTS.md nobody has removed yet; it is never replaced, and
each build warns until it is deleted. An empty file counts as a
build's, since that is what a write cut short leaves of a new one. An
AGENTS.md linked to the template is refused: writing it would write
the committed file again. A template the notes cannot be placed in
leaves no empty AGENTS.md behind.

The in-place mode is removed rather than kept as the fallback for a
project without a template. It is the default that put generated text
into tracked files unasked, the moment any dependency started shipping
notes. Without a template a build no longer touches AGENTS.md. One
that still holds a section from 3.0.0 would go stale silently, under a
preamble promising that every build rewrites it, so it gets a warning
naming the move instead.

MergeDependencyNotes keeps its rules and now runs on the template. The
test changes in this commit rather than its own, because it pinned the
in-place behaviour this removes: it now covers the generated file's
shape, edits and markers in the template, the ownership check, a CRLF
template, a link to the template and the cases without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:53:42 +01:00
b26807f5aa
docs(packaging): add the 3.0.0 release entry
All checks were successful
Flatpak / flatpak (push) Successful in 12m53s
3.0.0
C++ exceptions in wasm builds, scripts, dependency notes, the browser
runtime's exit and park, and mingw found where it is; the fixes since
2.2.0; and the two changes that need a look when upgrading: the wasm
sysroot and engine requirements, and siblings inheriting the loading
project's args. A major, because a wasm project.cpp with
-fno-exceptions or an older sysroot, and a project.cpp whose siblings
relied on getting no args, build differently or not at all on this
release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 06:54:33 +01:00
1d52350b31
feat(ci): render the release notes as Markdown with headings and lists
The release page got the newest <release> block as the plain text of
its paragraphs, which fit two paragraphs of prose and nothing more.
From 3.0.0 the notes group the changes under New, Fixes and Breaking,
each in descending order of importance, and AppStream's description
markup has lists but no headings to carry that.

The metainfo stays the one source of the notes, since the Flatpak
installs it and the tag check reads it. A category is written there as
a paragraph that introduces a list, <p>New</p><ul>...</ul>, which is
how AppStream notes lead into a list anyway, and the CI step renders
such a paragraph as a Markdown heading over its items. A paragraph on
its own stays a paragraph, so the older entries render as before. The
step parses the XML with ElementTree instead of two regexes: entities
are decoded, <em> and <code> get their Markdown spelling, and a code
span keeps text like lib/<triple>/eh from being read as an HTML tag by
the forge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 06:54:33 +01:00
70ff11d0a1
feat(wasm): link libsetjmp, so setjmp needs no flag
All checks were successful
Flatpak / flatpak (push) Successful in 17m58s
-mllvm -wasm-enable-sjlj, in every wasm compile because setjmp.h does
not compile without it, turns setjmp and longjmp into calls to
__wasm_setjmp, __wasm_setjmp_test and __wasm_longjmp and a throw of
the __c_longjmp tag, all of which live in wasi-libc's libsetjmp. The
driver does not link it, so a program that used setjmp, or a C library
that handles its errors that way, failed to link with undefined
__wasm_setjmp until the project put -lsetjmp in its linkFlags.

Build() adds it to every wasm link, next to -lunwind. A static archive
contributes only the members something references, so a program
without setjmp gets nothing from it, and every sysroot that passes the
exception check ships it. WasmExceptions drops its own -lsetjmp, and
its C fixture now fails to link without this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:29:52 +01:00
3e125d83f0
docs: wasm exceptions as the review left them
The README called an exception escaping main a trap. It is an uncaught
WebAssembly exception, and nothing prints its type or what(); the
README says so now. It also lists what a wasm build refuses and why,
says that setjmp needs -lsetjmp and that a longjmp across a noexcept
function ends the program, that a wasmtime older than 47 needs
-W exceptions=y, and that an old wasm-opt on PATH breaks release links.
It documents exit and park in a page from EnableWasiBrowserRuntime,
with the declaration of the park import.

The wasi example still sent Arch users to wasi-libc++, which has no
exception-enabled libraries. It asks for a wasi-sdk 33 sysroot or the
Flatpak now, and names the browsers a page needs. AGENTS.md notes that
the runtime test needs node, so it runs in the native loop and not in
CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:10:41 +01:00
2a05b8e6fb
test(wasm): the cases the review found
WasmBuildRefusals checks that -flto in compileFlags and in linkFlags,
-fno-exceptions and a shared library are each refused with their
cause, and that none of them leaves bin/ or build/ behind. With the
check disabled, each of them failed on a missing std.cppm instead,
after the build directories had been made.

WasiSysrootWithoutExceptions also takes its sysroot through missing
and without the target's directory, each with a message of its own,
checks that a refused build leaves nothing behind, and that a static
library passes without libunwind.

WasmExceptions catches an exception thrown inside libc++.a (stoi).
vector::at throws from a header compiled into the fixture, so the
fixture also passed against the libc++ built without exceptions. Its C
source uses setjmp, which the old C flags did not compile. An
exception that escapes main has to end the run as wasmtime's uncaught
exception, where any nonzero exit used to pass, a trap or a timeout
included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:10:41 +01:00
45b9f5cbb7
fix(wasm): what the review found
-flto in a wasm configuration's compileFlags or linkFlags reached the
link, where LTO code generation runs without the backend options that
keep exception handling: every landing pad was dropped, and the build
succeeded with throws that escaped their catch blocks. -fno-exceptions
there made every `import std;` fail on the std module built with
exceptions, and a wasm shared library failed to link against the eh/
libc++, which is static archives built without -fPIC. Build() refuses
all three now, each with a line that says why, before it makes a
directory or starts a thread.

The sysroot check sat in BuildStdPcm, which the linter calls as well.
There the error only dropped the compile command, so lint ran no AST
rule on a wasm configuration and reported it clean. It also refused
static libraries, which never link, fired after the shader and
external threads had started, and called a missing sysroot, or one
without the target, "no C++ exception support". It is part of the same
early check now, which first says when the sysroot or the target's
directory is missing, and asks for libunwind only where something
links.

C sources got only the signal.h define. setjmp.h refused to compile in
C, and a C object given -wasm-enable-sjlj through compileFlags used the
legacy exception encoding next to C++ objects in the standard one, a
mix engines refuse to load. They get WasmCompileFlags now, like the C++
units, a variant pass's flags included.

The baseline probes include simd: V8 and SpiderMonkey switch SIMD off
on x86 CPUs without SSE4.1, which virtual machines often present, and
such an engine passed the exception-handling probe only to fail
compiling the module; the manifest tests expect it on every entry. The
Flatpak pins WASI_SDK_EXCEPTIONS=DUAL, which
wasi-sdk 34 defaults to only with clang 23 or newer, and checks that
the eh/ libunwind got installed: the wasm tests skip without it, so CI
would otherwise pass a Flatpak whose every wasm build fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:10:41 +01:00
fec206839e
test(wasi-runtime): exit and park, under node
runtime.js had no test that runs it. WasiBrowserRuntimeExit runs the
real file under node, once per scenario, against a module assembled in
the harness. Between _start and the call that ends it the module has
the frames that caught the old exit: a destructor (catch_all_ref, then
throw_ref), a catch (...) and a noexcept function, each reporting when
it runs. An exit has to run none of them and record its status; a park
has to run none of them, nor the code after it, and leave the program
callable with its state intact; an exit from a later call has to end
the program too, a return from main has to give status 0, and a crash
or an exception nothing caught has to stay an error.

With the JS throw put back into proc_exit, the exit from main and the
exit from a later call both end in the noexcept frame's terminate.

It skips without node, or with a node whose engine lacks the
standardized exception handling. The Flatpak has no node, so CI does
not run it; the native loop does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:55:32 +01:00
0dabce0b74
fix(wasi-runtime): end exit and park without running the program
runtime.js implemented proc_exit by throwing a JavaScript error through
the wasm stack, and the switch to -fwasm-exceptions made that error
catchable: every cleanup pad, catch (...) and noexcept terminate pad
catches a foreign exception. An exit therefore ran the destructors of
every frame it left, a catch (...) swallowed it and carried on, and
under a noexcept frame it ended in std::terminate. Crafter.Graphics
calls std::_Exit(0) in Window::StartSync to hand the thread to
requestAnimationFrame; once the exit's destructors had torn down
main's locals, every later frame read destroyed state.

proc_exit is now a function of a small helper module, bound directly
as the program's import, that reports to the runtime and then executes
unreachable. A trap is the one thing wasm code cannot catch, and raised
in wasm rather than from JS it does not depend on each engine keeping
a trap uncatchable when it passes back through a JS frame. Nothing of
the program runs on the way out and its shadow stack stays as it was,
which is what exit and _Exit require. window.crafter_wasi.exitCode
holds the status, also after a return from main, so that bridges which
call into the program on their own can stop.

Handing the thread to the browser for good is a separate import,
crafter_runtime.park. It ends the call the same way but leaves the
program alive, and main's locals valid for the calls that follow, so
StartSync keeps its contract that no caller code after it runs. Until
Crafter.Graphics moves to park, its _Exit behaves as before minus the
destructors: the runtime records an exit but cannot stop a bridge that
calls exports itself.

A WASI reactor build, where main returns and state lives in statics,
was the alternative. It breaks StartSync's promise never to return,
which the native targets rely on. JSPI would keep the whole stack
alive, but Safari does not have it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:55:21 +01:00
18413bfae4
docs: wasm targets and their C++ exceptions
The README described the mingw target and said nothing about wasm. It
now says where the WASI sysroot comes from, that C++ exceptions work as
on the other targets and what that takes (-fwasm-exceptions in the
standardized form, the sysroot's exception-enabled libc++, -lunwind
added to the link), which engines run the result, and that an exception
escaping main ends the program without calling std::terminate.

AGENTS.md says where the wasm flags live, that VariantId and the std
PCM directory hash them, and that the wasm tests skip where the
distro's sysroot lacks the eh/ libraries, so they run in the Flatpak.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:55:00 +01:00
8862092b63
test(wasm): exceptions in a wasm build, and a sysroot without them
WasmExceptions builds a fixture for wasm32-wasip1 and runs it under
wasmtime. It throws through four frames whose destructors have to run,
catches the exception, catches one that vector::at throws, and with an
argument lets one escape main, which has to fail the run. With the
previous flags the fixture does not compile ("cannot use 'throw' with
exceptions disabled"), and each part of turning exceptions on fails it
in its own way, from a BMI built without them to the legacy encoding
wasmtime refuses.

WasiSysrootWithoutExceptions takes a scratch sysroot through three
states, no libunwind, libunwind under eh/ and libunwind in the
triple's own directory, and checks that only the first stops the
build, with the message naming the library it looked for. It needs no
working wasm toolchain, so it also runs where the others skip.

WasmHelloRuns and WasmExceptions treat a sysroot without the
exception-enabled libraries as missing and skip. The manifest tests
expect the exception-handling probe on every entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:48 +01:00
63c4040000
feat(wasm): compile C++ with WebAssembly exception handling
wasm builds were compiled with -fno-exceptions. A throw did not
compile, libc++'s own throws (vector::at, std::format, filesystem)
aborted the program, and code that relies on exceptions could not be
built for the browser at all.

Every wasm unit and the std module now get -fwasm-exceptions, and the
link adds -lunwind, which code built that way calls into and the driver
does not add. The flag also selects the libc++ under the sysroot's
lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh
one. Those libraries use the standardized encoding (try_table, exnref)
while LLVM 23 still emits the legacy one by default, which wasmtime
refuses to load and which would put both in one binary, so the flags
ask for the standard form with -wasm-use-legacy-eh=false. That makes
the setjmp lowering loadable under wasmtime too.

The flags live in one constant, WasmBaselineFlags, which the compile
command and the std module both take through WasmCompileFlags: a BMI
is refused by a unit whose exception setting differs from its own.
VariantId and StdPcmDir hash it, so the first build after the update
starts fresh instead of importing PCMs built without exceptions, and
two releases sharing the Flatpak's cache don't take turns rebuilding
one std PCM. A sysroot without the exception-enabled libraries stops
the build with a line saying so, rather than at the link with "unable
to find library -lunwind".

The result needs an engine with exnref: Chrome 137, Firefox 131,
Safari 18.4, the bundled wasmtime. The runtime's exception-handling
probe tested the legacy encoding; it now tests try_table, every
variants.json entry carries it, and a browser without it is told which
feature it lacks instead of failing with a CompileError. There is no
switch back to -fno-exceptions: engines without exnref are old enough
not to keep a second build for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:39 +01:00
eefd1df3eb
Merge branch 'claude/issue-41': refuse a build that holds two builds of one module
All checks were successful
Flatpak / flatpak (push) Successful in 12m49s
PR #42, fixes #41. Build() now refuses a configuration whose transitive
dependencies hold two configurations exporting a module of the same
name, such as one project loaded along two paths with different arg
lists, before it builds anything, and names both paths. Until now both
builds ended up in the one program, which compiled cleanly and then
read garbage. Merged cleanly on top of claude/forward-project-args;
the suite, with claude/dependency-notes' fixtures, passes under the
new check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:30:36 +01:00
75142276ab
Merge branch 'claude/forward-project-args': sibling projects inherit the args, lint with the lite rules
PR #40. GitProject and LocalProject default their args to the loading
project's own (ProjectArgs()), this tree lints with LintRules' lite
set, and the fixes from the branch's review: a project file gets its
args only for the CrafterBuildProject call, host tooling gets project
cache entries of its own, and `--` ends crafter-build's options.

The conflicts with claude/dependency-notes are all places both sides
added to:
- Platform.cppm and Platform.cpp: PragmaDependencies() beside
  ProjectArgs() and LoadingHostTooling(), all three kept.
- LoadProject: the record PragmaDependencies() reads moves up with the
  frame's dependencies, which this branch sets before dlopen. Left
  where it was, it would have copied preDeps.configs after the vector
  was moved into the frame, and recorded nothing.
- README: the pragma section's new last sentence goes before the new
  "Dependency notes for agents" section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:27:37 +01:00
34874c7143
docs: two lists for one sibling now stop the build
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m49s
The README and the spec comment said that a project two paths load with
different lists ends up in the program twice. They now say the build
stops, with an error that names both paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:19:30 +01:00
3d2023cb5c
test: two builds of one module are refused before anything compiles
The fixture is P -> M and P -> A -> M, where M's own flag --wide
changes a struct it exports. Loaded with --wide --split, P gives M its
args without --wide while A passes them on whole: Build(P) has to fail,
name the module and both paths, and leave no bin/ anywhere. Loaded with
--wide alone, both paths give M the same list, there is one M, and the
build has to go through. Before the fix the first case built cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:19:30 +01:00
d5b80fcf7e
fix: refuse a build that holds two builds of one module
A project that two paths load with different arg lists is two
configurations, each with a variant of its own, and Build() built both.
The consumer's compile got both PCM dirs on its -fprebuilt-module-path,
and clang resolved the module by name to the first for every import,
also for the import inside the other build's BMI consumers, with no
check that it was the build they had been compiled against. With
P -> M and P -> A -> M, where the two lists change a struct M exports,
P compiled against one layout, A against the other, and the program
built cleanly, then read garbage or crashed (#41).

Each configuration now checks, before it makes a directory or builds a
dependency, that no two configurations among it and its transitive
dependencies export a module of the same name, and otherwise fails with
both paths and what tells the builds apart. Going by module name rather
than by project file also catches a checkout on one path and a clone of
the same project on the other. Per configuration, not per invocation:
two builds of one project in different programs, such as a test and a
#pragma crafter dependency tool, are fine.

Picking one of the two builds was not an option: arg lists have no merge
rule, so whichever consumer lost would silently get a different build
from the one it asked for.

Fixes #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:19:30 +01:00
84d9d66649
docs: correct what the sibling-args docs promise
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m27s
The README suggested giving one sibling a list without a flag only the
project reads, so that the sibling would not get a build directory of
its own. When another path loads the same sibling with the full list,
that filter is what gives it the second directory: two builds of one
project, both in the program (#41). It now says to build such a list
once and give it to every sibling. It also says the default is read
when the spec is made, so a spec made at namespace scope, on another
thread or in a script forwards nothing, and that cfg.projectArgs, the
part ApplyStandardArgs did not interpret, is no substitute: it drops
the target and the build type. The comments on ProjectArgs() and on
Configuration::projectArgs say the same.

The spec comment named --local as a flag the default carries down a
chain of siblings. Through a GitProject hop it breaks: the clone has no
checkouts beside it. The GitProject comment said the clone is keyed by
the args; only the Configuration is, and every arg list builds in the
one clone. AGENTS.md now notes that a stale harness gives no symptom
for a changed default member initialiser: project.cpp silently gets the
old default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
70b68b98a5
test: -- reaches neither the project args nor the script
`run -- record -- a` has to run the record script with the args `--`
and `a`: the first `--` ends the options, the second comes after the
script's name and is the script's. The project args keep only what
stood before `run`. And `run -- -x` has to look for a script called
-x, since after `--` an option-like word is an operand. Before the fix
the first `--` showed up in the project args, and `-x` joined it there
while the bare `run` listed the scripts and succeeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
368fd2c527
fix(cli): end the options at -- instead of forwarding it
The help has advertised `[options] [-- project-args...]`, but Run never
looked for `--`. It fell through to projectArgs as a word of its own,
and the words after it were still read as this tool's options and
verbs: `crafter-build -- -v` turned verbose output on. In projectArgs
`--` changed the project's variant hash, and now that siblings inherit
the args, the hash of every sibling down the chain, each building into
directories of its own that the project's clean never removes.

`--` now ends this tool's options and goes nowhere itself. Every later
word is an operand of the verb, as POSIX has it: a project arg for a
build, a glob for test, lint and format, the script's name and then its
args for run. A glob rather than a project arg after `test` because
`crafter-build test -- 'House*'` is how such a filter gets typed; a
project arg goes before the `--`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
95896a7ddd
test: a project and its pragma dependency load a shared sibling apart
The fixture app has `#pragma crafter dependency "../tool"` and loads
../shared for itself, which the tool loads too; the app is loaded with
no args, the case where the two cache keys used to match. With
CRAFTER_BUILD_MARCH=x86-64-v2, the app's copy of shared has to keep
that march and no -fPIC, and the tool's copy has to be host tooling.
Before the fix the app got the tool's copy back: one Configuration,
with march native and -fPIC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
e420c9f958
fix: keep host tooling out of the project cache the target uses
ForceHostTooling rewrites a `#pragma crafter dependency` and everything
it loads in place: host target, march native, release, -fPIC. Those are
entries of the project cache like any other, keyed by project file and
args alone. A project that loaded the same sibling as its tool, with
the same args, got the rewritten entry back. That happens whenever the
project gets no args, since host tooling gets none either: a consumer
built for x86-64-v2 then imported a sibling's BMI compiled with
-march=native, and clang refused it. Any arg on the command line split
the keys and hid it.

A thread-local count of pragma loads now marks the tool's load and
every load inside it, its project file's siblings included, and
GitProject and LocalProject put that mark in the key. The clone stays
shared; only the Configuration is separate, as for two arg lists. A
module-internal function rather than a spec field, because the
siblings of a tool are loaded by the tool's own project.cpp, which
cannot know it is host tooling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
e6363b95fe
test: specs and ProjectDependency() in a project file's static initialisers
ProjectArgs gets a sibling that makes its own sibling's spec at
namespace scope. Before the fix that spec took the args the file was
first loaded with; now it takes none on every load. The checks go
through one CheckLoaded helper, which prints what each sibling was
given, so a failure says which args a wrong frame handed over.

ProjectDependency loads a fixture whose static initialiser asks for its
pragma dependency, in a child, because a throw there terminates the
process: before the fix it found none. The check that the dependency
gets none of the consumer's args moves into the first load, which now
passes the app an arg it ignores; the separate load could not fail its
own check, and its early return skipped every later one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
060ab4380f
fix: give a project file its args only for the CrafterBuildProject call
ProjectArgs() read args that LoadProject recorded when it pushed the
frame, so they were there for everything that ran on that frame, not
only for CrafterBuildProject. Two things went wrong with that.

The pragma pre-pass runs on the frame too, so both of its specs needed
a hand-written `.args = {}` to keep the consumer's args from reaching
host tooling, and anything else built there would have needed one as
well. And dlopen runs a project file's static initialisers only on the
first load of that file: a later load gets the same mapping back. A
spec made at namespace scope therefore kept the first load's args for
every later load of the file, a silently wrong variant.

The frame now gets the args just before CrafterBuildProject is called.
The pre-pass and the static initialisers see none, on every load, so
the two opt-outs go. The dependencies the pre-pass resolved move the
other way, to just before dlopen: they are the same on every load, and
ProjectDependency() called from a static initialiser used to find none
and terminate the process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:11:41 +01:00
cc0a6aed8c
Merge branch 'claude/dependency-notes': a library's AGENTS.consumer.md in its consumers' AGENTS.md
All checks were successful
Flatpak / flatpak (push) Successful in 12m18s
Brings in dependency notes: a crafter-build project that ships an
AGENTS.consumer.md beside its project.cpp has it kept in a marked
section of each consumer's AGENTS.md, refreshed by `crafter-build` and
`crafter-build test`, chosen by cfg.dependencyNotes (Transitive,
Direct, None). The new partition is Crafter.Build:Agents; the loader
records where each GitProject and LocalProject came from and what a
project file's pragmas resolved to.

The branch started before master's four external-fetch commits. Both
touch External.cpp, the Clang partition, project.cpp, README.md and
AGENTS.md, but in different places, so the merge needed no hand
reconciliation; the merged tree passes build.sh, the self-host build,
format --check and the test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:08:02 +01:00
ae5e3b6b32
docs: dependency notes as the review left them
The README's section: notes are labelled by name, the file is written
in place, a project.cpp that fails to load gets no update, `test
--list` writes nothing, the section follows what the build loaded, and
tests' dependencies don't count. The AGENTS.md gotcha: an unpinned
dependency can change this file's section on any build here, and the
dirty tree then rebuilds crafter.build-lib under a new version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:36 +01:00
ec9790589f
test(agents): the cases the review found
Each fixed finding gets a check that fails without its fix: inline code
and a list item's fence, a section removed from between two paragraphs,
a section before an unclosed fence, a CRLF file, no error when there is
nothing to write, a hard-linked and a read-only AGENTS.md, a dependency
whose project file sits in the consumer's own directory, a test's
dependency, and `test --list`. The read-only case needs an ordinary
user, since root writes through the permission bits, and is skipped
under root.

The merge cases go through a helper that counts a throw as a failure,
so one regression no longer ends the run before the cases after it. A
passing run removes its staged copy, whose builds leave a std PCM per
configuration in a tmpfs.

Reverting the inline-code rule, the own-notes exclusion, the skipping
of tests, the `test --list` guard or the paragraph-preserving removal
each fails its check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:36 +01:00
7815856d60
fix(agents): what the review of dependency notes found
A review of the feature turned up the following, fixed here.

Writing. AGENTS.md was replaced through a temporary renamed over it,
which swaps in another file: a read-only AGENTS.md was rewritten
anyway, a hard link such as a CLAUDE.md beside it kept the old text,
ACLs and ownership were dropped, and without an fsync a power cut could
still leave it empty. The temporary was named after the PID, which
every Flatpak instance shares as PID 2. The file is now written in
place, from the first byte that differs, then truncated and synced,
under an flock on the file itself. A write that fails partway is undone
where the disk allows; with the section last, where it is appended,
only the section can be damaged. A read-only file is opened for writing
only when something would change, so it is refused then and silent
otherwise.

Reading. libc++'s filebuf reports a failed read as the end of the file,
so an I/O error came back as a short file and the section was written
over what was left. Reads go through read(2).

Code fences. ```make``` was taken for a fence, and a fence in a list
item was missed while its indented closer opened one, so markers a user
quoted in a code block could be taken for the section, or the real one
hidden and appended again. Backtick info strings, block quotes and list
items are now followed, and a block ends with its container.

Warnings with nothing to write. With no notes, nothing short of a
well-formed section is reported, so an AGENTS.md that ends in an open
code block, or a project set to None, no longer warns on every build.
An unclosed fence after a section no longer stops it being rewritten
in place.

Labels. The `from` line carried the git URL, credentials and all, or a
path relative to the consumer that changed with the machine, the cache
location and --local. It is now the repository's or the directory's
name. Notes are deduplicated by repository rather than by clone, so one
library pinned two ways is described once, as the nearer spec has it.

The walk. It no longer looks at the project's tests: a plain build
never did, and a test's dependency can point at the Configuration the
project file returned by value. It goes ring by ring over projects
rather than configurations, so what the project names itself comes
first even with the library/executable split. The project's own
AGENTS.consumer.md is excluded by path, also when a dependency's
project file sits beside the project's.

Removing a section dropped a blank line even from between two
paragraphs, joining them; only a section that ends the file takes it
now. A CRLF file gets a CRLF section.

A note was checked on its raw bytes and again, under other whitespace
rules, on the trimmed ones, so it could pass the first check and fail
the merge, which then dropped every note. It is normalised once and
checked on exactly what is merged.

`test --list` only lists, and no longer writes AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:02:28 +01:00
5cf295e1d4 docs: sibling projects forward the parent's args by default
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m57s
The sibling-project example dropped its `.args = depArgs`, which no
longer has to be written, and says what a sibling receives instead: the
args the project file was loaded with, or whatever `.args` is set to,
with `{}` for nothing. It also says that releases up to 2.2.0 forward
nothing without `.args`, which matters for a project.cpp that has to
load on both.

The pragma section says that a project file's own dependency is built
for the host and gets none of the project's args, the one place the
default does not apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:21:06 +00:00