fix: give the mingw target a sysroot that holds mingw-w64 alone #44

Merged
jorijnvdgraaf merged 12 commits from claude/issue-43 into master 2026-10-31 22:35:49 +00:00

2026-10-31

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
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
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
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
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
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
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
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
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
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
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
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