Linux-hosted cross toolchain targeting x86_64-pc-cygwin (validation phase) before morphing to x86_64-pc-msys. Goal is bringing the full MSYS2 environment up on ReactOS; ReactOS will gain whatever Win32/NT API surface MSYS2 needs (we do not fork the runtime to paper over ReactOS gaps).
Recipe transcribed from NixOS/nixpkgs PR #444470 (pkgsCross.x86_64-cygwin, head e098bea39d) into plain shell scripts (no nix on host).
Workspace: ~/git/msys-cross/{src,build,prefix,patches,scripts,logs}.
| Component | Version | Source |
|---|---|---|
| newlib-cygwin | tag cygwin-3.6.4 |
git://cygwin.com/git/newlib-cygwin.git |
| binutils | 2.46.0 | https://ftp.gnu.org/gnu/binutils/binutils-2.46.0.tar.xz |
| gcc | 15.2.0 | https://ftp.gnu.org/gnu/gcc/gcc-15.2.0/gcc-15.2.0.tar.xz |
| mingw-w64 | 13.0.0 | https://downloads.sourceforge.net/project/mingw-w64/mingw-w64/mingw-w64-release/mingw-w64-v13.0.0.tar.bz2 |
| cocom | 0.996 | https://sourceforge.net/projects/cocom/files/cocom-0.996.tar.gz/download |
newlib-cygwin (3 patches, from nixpkgs PR #444470 pkgs/os-specific/cygwin/newlib-cygwin/):
fix-winsize.patch— backport of cygwin commit 73600d6, movesstruct winsizedecl above first use inwinsup/cygwin/include/sys/termios.hstore-tls-pointer-in-win32-tls.patch— restores TLS pointer storage in Win32 TLS slot (needed for boost coroutines)fix-cross.patch— three hunks: drop_GCC_AUTOCONF_VERSION_CHECK, change-Lto-Bfor crt0.o lookup, forceEXEEXT=.exeinwinsup/configure.ac
gcc (2 patches, from sourceware mailing list, author corngood@gmail.com):
libstdc-fix-compilation-in-freestanding-win32.patch— https://inbox.sourceware.org/gcc-patches/20250922182808.2599390-2-corngood@gmail.com/rawcygwin-fix-compilation-with-inhibit_libc.patch— https://inbox.sourceware.org/gcc-patches/20250926170154.2222977-1-corngood@gmail.com/raw
No source patches required. Stock upstream builds clean for x86_64-pc-cygwin.
"$SRC_DIR/configure" \
--target=x86_64-pc-cygwin \
--prefix="$PREFIX" \
--with-sysroot="$SYSROOT" \
--disable-werror \
--disable-multilib \
--disable-nls \
--disable-gdb \
--disable-gprofng \
--enable-64-bit-bfd \
--enable-deterministic-archives$ x86_64-pc-cygwin-as --version | head -1
GNU assembler (GNU Binutils) 2.46.0.20260210
$ x86_64-pc-cygwin-ld -V 2>&1 | grep i386
i386pep
20 cross tools installed at $PREFIX/bin/x86_64-pc-cygwin-* (as, ld, ld.bfd, ar, nm, objcopy, objdump, ranlib, readelf, strip, strings, size, addr2line, c++filt, dlltool, dllwrap, elfedit, gprof, windmc, windres).
cocom is a 2003-era codebase; gcc 15 rejects K&R-style int defaults and implicit-function-declarations as errors (these used to be warnings). The autoconf-2.13 sub-configure in REGEX/ also chokes on autoconf-2.59 positional CC=/CFLAGS= forwarding.
Only msta (parser generator) is needed — that's what newlib-cygwin's build wants. SPRUT, AMMUNITION (libcocom) are msta's transitive deps. DINO/REGEX/OKA/NONA/SHILKA are skipped entirely.
src/cocom-0.996/SPRUT/yacc.h:45 — K&R prototype that gcc 15 won't accept inside SPRUT/yacc.y:
-extern yyparse ();
+extern int yyparse (void);src/cocom-0.996/REGEX/configure:666 (kept dormant — REGEX is hidden during configure, see below — but applied for completeness):
-main(){return(0);}
+int main(void){return(0);}Demote gcc 15 K&R-era diagnostics back to warnings:
LEGACY_CFLAGS="-O2 -g -fcommon \
-Wno-error=implicit-int \
-Wno-error=implicit-function-declaration \
-Wno-error=incompatible-pointer-types \
-Wno-error=int-conversion \
-Wno-implicit-int \
-Wno-implicit-function-declaration \
-Wno-incompatible-pointer-types \
-Wno-int-conversion"
LEGACY_CXXFLAGS="-O2 -g -fpermissive -Wno-write-strings"
export CC="gcc"
export CXX="g++"
export CFLAGS="$LEGACY_CFLAGS"
export CXXFLAGS="$LEGACY_CXXFLAGS"Hide REGEX/ during ./configure so its broken sub-configure isn't recursed into. Restore via EXIT trap:
if [ -d "$SRC_DIR/REGEX" ]; then
mv "$SRC_DIR/REGEX" "$SRC_DIR/REGEX.disabled"
trap 'mv "$SRC_DIR/REGEX.disabled" "$SRC_DIR/REGEX" 2>/dev/null || true' EXIT
fi
"$SRC_DIR/configure" --prefix="$PREFIX"
if [ -d "$SRC_DIR/REGEX.disabled" ]; then
mv "$SRC_DIR/REGEX.disabled" "$SRC_DIR/REGEX"
trap - EXIT
fiOnly AMMUNITION (libcocom), SPRUT (generates ird.[ch] for MSTA), and MSTA. Each -j1 because AMMUNITION's Makefile races between C and C++ on the bison-generated sgramm.c:
( cd AMMUNITION && make -j1 && make install )
( cd SPRUT && make -j1 && make install )
( cd MSTA && make -j1 && make install )Do NOT pass CFLAGS= on make's command line. That overrides the Makefile's own assignment and drops $(OPTIONS) = -DHAVE_CONFIG_H -I.. -I. -I$srcdir, which breaks #include "sgramm.c" inside earley.c. Configure already baked our flags into the Makefile, leave make alone.
$ /home/kreijstal/git/msys-cross/prefix/bin/msta
/home/kreijstal/git/msys-cross/prefix/bin/msta options:
command line: msta [ options ...] specification-file
[...]
Binary is 1010624 bytes. msta has no --version; printing usage and exit 1 with no args is the success signal.
mingw-w64-headers/configure ships an --enable-w32api shorthand specifically for the cygwin layout. Per its --help:
--enable-w32api shorthand for --includedir=PREFIX/include/w32api --disable-crt
So a plain --prefix=$SYSROOT --enable-w32api lands headers at $SYSROOT/include/w32api/ natively. No post-install mv gymnastics.
"$SRC_DIR/configure" \
--build="$($SRC_DIR/build-aux/config.guess)" \
--host=x86_64-pc-cygwin \
--prefix="$SYSROOT" \
--enable-w32api \
--enable-sdk=all \
--enable-idl \
--with-default-msvcrt=msvcrt--with-default-msvcrt=msvcrt (not the v13.0.0 default ucrt) — must stay consistent in stage 06 (w32api libs) and stages 05/08 (gcc) so __MSVCRT_VERSION__ and import-lib selection match.
$ ls $SYSROOT/include/w32api/ | wc -l
1791
$ find $SYSROOT/include/w32api -name '*.h' | wc -l
1572
Preprocessor smoke (host gcc with -D__CYGWIN__ since stage-05 target gcc isn't built yet):
printf '#include <windows.h>\nint main(void){return 0;}\n' \
| gcc -E -D__CYGWIN__ -isystem $SYSROOT/include/w32api -x c - \
> /dev/null # exit 0; ~109 055 lines of expansion, no stderrConfigure gcc-stage1 with --with-sysroot=$SYSROOT --with-native-system-header-dir=/include/w32api so #include <windows.h> resolves without callers needing explicit -isystem.
Per nixpkgs PR #444470's newlib-cygwin-headers derivation: just two cp -rs, no configure or build.
cp -r "$SRC/newlib-cygwin/newlib/libc/include/." "$SYSROOT/include/"
cp -r "$SRC/newlib-cygwin/winsup/cygwin/include/." "$SYSROOT/include/"The newlib + winsup trees have no top-level filename collisions and neither uses w32api/ as a path, so this safely coexists with stage 02's mingw-w64 headers in the same $SYSROOT/include/ directory.
- 265
.hfiles staged (excludingw32api/) CYGWIN_VERSION_API_MAJOR=0,CYGWIN_VERSION_API_MINOR=357,CYGWIN_VERSION_DLL_MAJOR=3006,CYGWIN_VERSION_DLL_MINOR=4(matchescygwin-3.6.4tag)$SYSROOT/include/w32api/windows.hintact (mtime unchanged from stage 02)
C and C++ frontends only — --without-headers --with-newlib --disable-libstdcxx. No target runtime libs (libgcc, libstdc++) built yet; this compiler exists purely to compile cygwin1.dll in stage 07. We rebuild gcc end-to-end in stage 08 once the runtime exists, against real headers + libs.
Two patches from sourceware mailing list, both author corngood@gmail.com, both required for gcc 15.2.0 against the cygwin/freestanding configuration:
libstdc-fix-compilation-in-freestanding-win32.patch— https://inbox.sourceware.org/gcc-patches/20250922182808.2599390-2-corngood@gmail.com/raw — fixes libstdc++ build in freestanding win32 mode.cygwin-fix-compilation-with-inhibit_libc.patch— https://inbox.sourceware.org/gcc-patches/20250926170154.2222977-1-corngood@gmail.com/raw — fixes cygwin target backend wheninhibit_libcis set (which is exactly what--without-headerstriggers).
Marker file $SRC_DIR/.cygwin-patches-applied keeps the script idempotent.
./contrib/download_prerequisites pulls these into the gcc source tree as in-tree subdirs (built statically, not installed separately):
| Lib | Version |
|---|---|
| gmp | 6.2.1 |
| mpfr | 4.1.0 |
| mpc | 1.2.1 |
| isl | 0.24 |
"$SRC_DIR/configure" \
--target=x86_64-pc-cygwin \
--prefix="$PREFIX" \
--with-sysroot="$SYSROOT" \
--with-native-system-header-dir=/include \
--enable-languages=c,c++ \
--disable-multilib \
--disable-nls \
--disable-shared \
--disable-libssp \
--disable-libstdcxx \
--disable-libquadmath \
--disable-threads \
--disable-tls \
--disable-bootstrap \
--without-headers \
--with-newlib \
--enable-checking=release--with-native-system-header-dir=/include is sysroot-relative — gcc will look in $SYSROOT/include/ for the runtime headers staged in stage 03 (newlib + cygwin) and in $SYSROOT/include/w32api/ (stage 02). --with-newlib here is a misnomer in our setup; it just suppresses the libgcc EH model that would assume glibc, and pairs with --without-headers for the bootstrap compiler.
make -j$JOBS all-gcc
make install-gccall-gcc / install-gcc only — skips libgcc/libstdc++/target-libs entirely. Those need a working runtime, which we don't have yet.
$ x86_64-pc-cygwin-gcc --version | head -1
x86_64-pc-cygwin-gcc (GCC) 15.2.0
$ x86_64-pc-cygwin-gcc -dumpmachine
x86_64-pc-cygwin
$ x86_64-pc-cygwin-gcc -v 2>&1 | tail -3
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 15.2.0 (GCC)
Thread model: single is expected — --disable-threads suppresses pthread/win32 thread shims since we have no runtime to host them; stage 08 turns this into posix.
Cross binaries installed at $PREFIX/bin/x86_64-pc-cygwin-{gcc,g++,c++,cpp,gcc-{ar,nm,ranlib},gcov,gcov-{dump,tool},lto-{dump}}. cc1/cc1plus/lto1/collect2 land in $PREFIX/libexec/gcc/x86_64-pc-cygwin/15.2.0/.
Headers landed in stage 02 at $SYSROOT/include/w32api/. Stage 06 builds the companion import libs / w32api CRT subset — the lib*.a files Cygwin compilers link against (libkernel32.a, libuser32.a, libgdi32.a, libws2_32.a, plus ~880 more device/SDK import libs).
Source: $SRC/mingw-w64-v13.0.0/mingw-w64-crt/ (already extracted in stage 02 from the same tarball). No autoreconf was needed — the shipped configure worked as-is.
Build dir: $BUILD/w32api-libs.
Configure (matches stage 02's headers contract; in particular --with-default-msvcrt=msvcrt so __MSVCRT_VERSION__ agrees end-to-end):
"$SRC/mingw-w64-v13.0.0/mingw-w64-crt/configure" \
--build="$($SRC/mingw-w64-v13.0.0/mingw-w64-crt/build-aux/config.guess)" \
--host=x86_64-pc-cygwin \
--prefix=$SYSROOT \
--enable-w32api \
--with-default-msvcrt=msvcrt \
--disable-dependency-tracking
--enable-w32api on the crt dir is the Cygwin-specific shorthand: it routes installs to $SYSROOT/lib/w32api/ (lib64) and $SYSROOT/lib32/w32api/ (lib32), mirroring the /include/w32api/ header layout from stage 02. Both 32-bit and 64-bit import libs are built — the cross gcc is multilib-aware via -m64/-m32 on the same x86_64-pc-cygwin-gcc.
make -j$JOBS && make install ran clean against the stage 05 cross compiler. No patches, no overrides, no extra env. Total build/install (excluding the prior stage-02 header source extract): a few minutes on a 16-core box.
$ ls $SYSROOT/lib/w32api/ | wc -l
891
$ ls $SYSROOT/lib/w32api/lib*.a | head -10
.../libacledit.a
.../libaclui.a
.../libactiveds.a
.../libadmparse.a
.../libadmwprox.a
.../libadptif.a
.../libadsiid.a
.../libadsiisex.a
.../libadsldpc.a
.../libadvapi32.a
$ ls $SYSROOT/lib/w32api/{libkernel32,libuser32,libgdi32,libws2_32}.a
.../libkernel32.a .../libuser32.a .../libgdi32.a .../libws2_32.a
$ x86_64-pc-cygwin-ar t $SYSROOT/lib/w32api/libkernel32.a | head -5
libkernel32t.o
libkernel32h.o
libkernel32s01661.o
libkernel32s01660.o
libkernel32s01659.o
$ x86_64-pc-cygwin-nm $SYSROOT/lib/w32api/libkernel32.a 2>&1 | grep -cE ' [TU] '
3435
The ar t output (*t.o, *h.o, *sNNNNN.o thunks) is the standard dlltool/-k short-import-library layout: head/tail markers plus one stub per exported symbol. 3435 T/U entries is consistent with the full kernel32 export surface (Windows 10 SDK level).
First run aborted just before the === OK === banner because ls ... | head -10 under set -o pipefail fails when head closes the pipe early (SIGPIPE → ls exit 141 → script exits). All install steps had completed; only the cosmetic verification footer was truncated. Fixed in scripts/06-w32api-libs.sh by wrapping the verification pipelines in set +o pipefail/set -o pipefail. Worth noting for stage 07+ which use the same ls | head pattern.
Builds cygwin1.dll (the cygwin POSIX runtime), libcygwin.a (its import lib), all standard cygwin libraries (libc.a, libm.a, libpthread.a, libdl.a, libacl.a, libaio.a, libresolv.a, librt.a, libssp.a, libutil.a, libg.a, libgmon.a), CRT startup objects (crt0.o, gcrt0.o, automode.o, binmode.o, textmode.o, textreadmode.o), and the standard cygwin user-tools (cygpath.exe, mount.exe, ps.exe, kill.exe, etc. — all PE/cygwin executables emitted by the cross compiler, useful as link-time validation that the toolchain works).
This is the validation gate of Phase 1: once cygwin1.dll exists, the toolchain can produce real cygwin executables.
3 patches from nixpkgs PR #444470 pkgs/os-specific/cygwin/newlib-cygwin/, applied in $SRC/newlib-cygwin/ via patch -p1 and tracked by marker .cygwin-patches-applied:
fix-winsize.patch— backport of cygwin commit 73600d6, movesstruct winsizedecl above first use inwinsup/cygwin/include/sys/termios.h.store-tls-pointer-in-win32-tls.patch— restores TLS pointer storage in Win32 TLS slot (boost coroutines depend on this).fix-cross.patch— three hunks: drop_GCC_AUTOCONF_VERSION_CHECK(newer autoconf than the source assumes), change-Lto-Bforcrt0.olookup, forceEXEEXT=.exeinwinsup/configure.ac.
fix-cross.patch touches both winsup/configure.ac and the top-level configure.ac, so both need autoreconf -fi afterward (matching nixpkgs autoreconfFlags = ["winsup" "."]). Tracked by marker .cygwin-autoreconf-done.
Both bootstrapped in-place at the start of stage 07:
(a) shilka (cocom) — winsup/cygwin/scripts/gendevices shells out to shilka to generate devices.cc. Stage 01 deliberately built only msta + sprut + AMMUNITION. Build SHILKA from the same $BUILD/cocom/SHILKA/ build dir, with -std=gnu17 injected into the configure-baked CFLAGS (gcc 15 K&R prototype error in SHILKA/yacc.h:45 form, same class as the SPRUT/yacc.h fix in stage 01) — sed -i 's|^CFLAGS = -O2 -g -fcommon|CFLAGS = -O2 -g -fcommon -std=gnu17|' Makefile, then make -j$JOBS && make install. Lands at $PREFIX/bin/shilka (≈ 330 KB).
(b) Freestanding libstdc++ headers — winsup/cygwin/cxx.cc includes <new> (for std::nothrow_t, std::size_t). Stage 05 passed --disable-libstdcxx, which dropped not just the library but also the C++ headers entirely. Reconfigure stage 5's existing $BUILD/gcc-stage1/ with --disable-hosted-libstdcxx --disable-libstdcxx-pch instead of --disable-libstdcxx, then make all-target-libstdc++-v3 && make install-target-libstdc++-v3. Lands <new> at $SYSROOT/include/c++/15.2.0/ and libstdc++.a at $SYSROOT/lib/. The --disable-hosted-libstdcxx flag is what corngood's gcc patches in stage 05 are designed for: it builds libstdc++ in freestanding mode (libsupc++-only — exception/RTTI/new-delete machinery, no iostream/locale/filesystem) without needing a target libc. Tracked by checking [ -f "$LIBSTDCXX_HDR" ] where LIBSTDCXX_HDR="$SYSROOT/include/c++/$GCC_VER/new".
export CFLAGS_FOR_TARGET="-Wl,-L$SYSROOT/lib/w32api"
export CXXFLAGS_FOR_TARGET="-Wno-error=register -Wl,-L$SYSROOT/lib/w32api"
export CFLAGS="${CFLAGS:-} -U_FORTIFY_SOURCE -fno-stack-protector"
export CXXFLAGS="${CXXFLAGS:-} -U_FORTIFY_SOURCE -fno-stack-protector"-Wno-error=register is needed because winsup/cygwin/cygtls.cc:29 uses the register storage class which gcc 15 / C++17 demotes to a warning that becomes an error under the tree's -Werror. -U_FORTIFY_SOURCE -fno-stack-protector mirrors nixpkgs hardeningDisable = ["fortify" "stackprotector"] (cygwin1.dll bootstraps before any of that hardening machinery is functional).
"$SRC/newlib-cygwin/configure" \
--build="$(uname -m)-pc-linux-gnu" \
--target=x86_64-pc-cygwin \
--prefix="$PREFIX" \
--disable-shared \
--disable-doc \
--enable-static \
--disable-dumper \
--with-cross-bootstrap \
ac_cv_prog_CC=gcc--with-cross-bootstrap is the cygwin-specific flag that tells the build it is producing the runtime for the first time (no pre-existing libcygwin.a to link against). ac_cv_prog_CC=gcc short-circuits the build-side compiler probe to host gcc (avoids it discovering and using the cross compiler for build-side tools).
configurePlatforms = [ "build" "target" ] in nixpkgs ⇒ --build= and --target= only (deliberately no --host=); the omitted --host defaults equal to --build so build-side tools compile native and target-side tools cross.
make -j$JOBS tooldir="$PREFIX"
make -j1 install tooldir="$PREFIX" # nixpkgs enableParallelInstalling=falsetooldir=$PREFIX matches nixpkgs makeFlags = [ "tooldir=${placeholder \"out\"}" ] (their out = our $PREFIX).
The newlib-cygwin top-level Makefile installs to $PREFIX/{bin,lib}/ regardless of --with-sysroot (its install paths predate the GNU sysroot convention). For stage 08 (gcc-final, configured with --with-sysroot=$SYSROOT) and for -isystem/-B lookup against the sysroot, the runtime artifacts have to live under $SYSROOT/{bin,lib}/. Copy them after install:
cp -p "$PREFIX/bin/cygwin1.dll" "$SYSROOT/bin/"
for f in libcygwin.a libc.a libm.a libpthread.a libdl.a libacl.a libaio.a \
libresolv.a librt.a libssp.a libutil.a libg.a libgmon.a \
crt0.o gcrt0.o automode.o binmode.o textmode.o textreadmode.o; do
cp -p "$PREFIX/lib/$f" "$SYSROOT/lib/$f"
done$ ls -la $SYSROOT/bin/cygwin1.dll
-rwxr-xr-x ... 5002978 ... cygwin1.dll
$ x86_64-pc-cygwin-objdump -p $SYSROOT/bin/cygwin1.dll | grep 'DLL Name'
DLL Name: KERNEL32.dll
DLL Name: ntdll.dll
$ x86_64-pc-cygwin-nm $SYSROOT/lib/libcygwin.a | grep -cE ' [TU] '
1688
$ ls $SYSROOT/lib/{libc,libm,libpthread,libdl,crt0,gcrt0}.{a,o} 2>/dev/null | wc -l
6
5 MB cygwin1.dll, imports KERNEL32 + ntdll only (the correct dependency surface for a cygwin runtime — no msvcrt/ucrt direct linkage), 1688 exported symbols in libcygwin.a.
The Phase 1 validation gate is "can the toolchain produce a real cygwin executable" — answer: yes.
$ cat > /tmp/hello.c <<'EOF'
#include <stdio.h>
int main(int argc, char **argv) { printf("hello cygwin from %s\n", argv[0]); return 0; }
EOF
$ x86_64-pc-cygwin-gcc /tmp/hello.c -o /tmp/hello.exe
$ ls -la /tmp/hello.exe
-rwxr-xr-x ... 26396 ... hello.exe
$ x86_64-pc-cygwin-objdump -p /tmp/hello.exe | grep 'DLL Name'
DLL Name: cygwin1.dll
DLL Name: KERNEL32.dll
Cross-compiled cygwin executable, imports cygwin1.dll + KERNEL32.dll exactly as a real cygwin executable should.
End-to-end execution under wine (cygwin1.dll alongside hello.exe in /tmp/):
$ cp $SYSROOT/bin/cygwin1.dll /tmp/cygwin1.dll
$ wine /tmp/hello.exe
... (wine NT-driver fixme noise) ...
Cygwin WARNING:
Couldn't compute FAST_CWD pointer. This typically occurs if you're using
an older Cygwin version on a newer Windows. ...
hello cygwin from /tmp/hello
The program runs, completes its printf, and exits cleanly. The "Couldn't compute FAST_CWD pointer" warning is cygwin's Windows-internals compatibility probe failing under wine's NT-emulation surface (different from a real Windows or ReactOS kernel) — informational, not fatal. ReactOS validation comes in stage 09 once we get a build to a ReactOS VM.
Rebuilds the gcc cross compiler end-to-end with all target libraries built against the just-installed cygwin runtime: libgcc, full hosted libstdc++ (iostream/locale/filesystem/regex/threads), libgomp, libatomic, libbacktrace. Replaces stage 5's Thread model: single stage1 compiler with Thread model: posix.
Same two corngood patches from stage 05 (libstdc-fix-compilation-in-freestanding-win32.patch, cygwin-fix-compilation-with-inhibit_libc.patch). Already applied; no new patches in stage 08.
$BUILD/gcc-final/ (fresh — not the same dir as stage 5's $BUILD/gcc-stage1/, which was contaminated by the --disable-hosted-libstdcxx reconfigure during stage 7's freestanding-headers install).
Configure flags (transcribed from corngood/nixpkgs e098bea39d, pkgs/development/compilers/gcc/common/configure-flags.nix, the !withoutTargetLibc branch + cygwin specifics)
"$SRC/gcc-15.2.0/configure" \
--target=x86_64-pc-cygwin \
--prefix="$PREFIX" \
--with-sysroot="$SYSROOT" \
--with-native-system-header-dir=/include \
--with-gxx-include-dir="$PREFIX/include/c++/15.2.0" \
--enable-languages=c,c++ \
--enable-__cxa_atexit \
--enable-long-long \
--enable-threads=posix \
--enable-tls \
--enable-shared \
--enable-static \
--enable-checking=release \
--disable-multilib \
--disable-nls \
--disable-libssp \
--disable-libquadmath \
--disable-libstdcxx-pch \
--disable-bootstrap \
--without-included-gettext \
--with-system-zlibDeviation from nixpkgs: dropped
--with-build-sysroot=/. Nixpkgs uses it for nix-store relocation, but combined with--with-native-system-header-dir=/includeit makes gcc concatenate them into a literal//includeand fail at fixinc:The directory (BUILD_SYSTEM_HEADER_DIR) that should contain system headers does not exist: //include. Without the flag, build-sysroot defaults to--with-sysroot, so gcc looks at$SYSROOT/include— the right place.
Diff vs stage 5 (05-gcc-stage1.sh):
| Flag | Stage 05 | Stage 08 | Why |
|---|---|---|---|
--without-headers |
yes | dropped | sysroot now has cygwin/newlib + w32api headers |
--with-newlib |
yes | dropped | real cygwin runtime exists now |
--disable-libstdcxx |
yes | dropped (full libstdc++) | runtime can host iostream/etc. |
--disable-shared |
yes | --enable-shared |
want libgcc_s.dll, cygstdc++-6.dll |
--disable-threads |
yes | --enable-threads=posix |
cygwin pthreads via libcygwin/cygwin1.dll |
--disable-tls |
yes | --enable-tls |
runtime supports TLS now |
--enable-__cxa_atexit |
— | yes | required by libstdc++ for static dtors |
--with-build-sysroot=/ |
— | dropped (see deviation note above) | nixpkgs flag breaks our layout |
--with-gxx-include-dir=... |
— | yes | force C++ headers under $PREFIX/include/c++/15.2.0/ |
--with-system-zlib |
— | yes | use host zlib, don't build in-tree |
--without-included-gettext |
— | yes | don't pull in-tree gettext (libintl) |
Build-sysroot in our layout: with no --with-build-sysroot, gcc uses
--with-sysroot for both build and runtime header lookups. Cygwin headers
already live at $SYSROOT/include/ (newlib + cygwin) and $SYSROOT/include/w32api/
(mingw-w64), so the default behaviour finds them. The double-prefix concern
that nixpkgs's --with-build-sysroot=/ works around does not apply here
because we install with make install directly, not via a relocatable nix
store path.
make -j$JOBS # all-targets, not just all-gcc
make installThis is the long step — host gcc + libgcc + full libstdc++-v3 + libgomp + libatomic + libbacktrace + libssp's stubs. ~30–90 min on a multicore.
$ x86_64-pc-cygwin-gcc --version | head -1
x86_64-pc-cygwin-gcc (GCC) 15.2.0
$ x86_64-pc-cygwin-gcc -dumpmachine
x86_64-pc-cygwin
$ x86_64-pc-cygwin-gcc -v 2>&1 | grep -i 'thread model'
Thread model: posix # ← was 'single' in stage 05
$ ls $SYSROOT/lib/cyg*.dll
.../lib/cygatomic-1.dll
.../lib/cyggcc_s-seh-1.dll
.../lib/cygstdc++-6.dll
$ ls $SYSROOT/bin/cyg*.dll
.../bin/cygwin1.dll # from stage 07
$ x86_64-pc-cygwin-gcc /tmp/hello.c -o /tmp/hello-final.exe
$ ( cd /tmp && cp $SYSROOT/bin/cygwin1.dll . && wine /tmp/hello-final.exe )
ok thread model: posix
$ x86_64-pc-cygwin-g++ /tmp/hello.cpp -o /tmp/hellopp.exe
$ ( cd /tmp && cp $SYSROOT/lib/cygstdc++-6.dll $SYSROOT/lib/cyggcc_s-seh-1.dll . && wine /tmp/hellopp.exe )
posix iostream ok
$ x86_64-pc-cygwin-objdump -p /tmp/hello-final.exe | grep 'DLL Name'
DLL Name: cygwin1.dll
DLL Name: KERNEL32.dll
$ x86_64-pc-cygwin-objdump -p /tmp/hellopp.exe | grep 'DLL Name'
DLL Name: cygwin1.dll
DLL Name: cygstdc++-6.dll
DLL Name: KERNEL32.dll
Notes:
- The script's C smoke pre-stages
cygwin1.dllnext to the exe; the C++ smoke does not stagecygstdc++-6.dll/cyggcc_s-seh-1.dll, so the in-script wine run for C++ fails to load. The compile/link itself is fine — verified by re-running with the DLLs co-located (output above). Future iteration: have08-gcc-final.shcopy all three DLLs. - Wine prints a
Couldn't compute FAST_CWD pointerwarning on every cygwin-runtime exec. Wine's ntdll layout doesn't match the byte sequences cygwin'sfind_fast_cwd_pointer()looks for inRtlGetCurrentDirectory_U. Same will be true on ReactOS until ntdll exposes a realfcwd_access_t **RtlpCurDirRefstatic and emits the rightlea rel,%rcx/mov rel,%rbxsequence. Tracked separately for the ReactOS-side fix; doesn't block the toolchain. - Total build time on this host: ~10 minutes (8 cores).
Goal: produce a working x86_64-pc-cygwin-gcc -specs=msys.specs hellomsys.c
that links against msys-2.0.dll instead of cygwin1.dll, while keeping the
plain (no -specs) cygwin baseline working. Scripts: 09-msys2-runtime.sh
(runtime build) and 10-msys-specs.sh (specs file generator).
Reuse the existing x86_64-pc-cygwin gcc-15.2.0 and binutils-2.46
verbatim. Don't rebuild gcc with a new triple, don't patch gcc's spec for
-lmsys-2.0, don't rebuild binutils. We build only msys2-runtime here,
install it as a nested side prefix under $PREFIX/x86_64-pc-cygwin/msys/,
and switch the dialect from a gcc -specs=msys.specs file. The compiler's
dumpmachine still says x86_64-pc-cygwin; the choice of runtime is
per-invocation. Two-runtime, one-toolchain — easier to maintain than
duplicating the whole 01..08 pipeline for *-pc-msys.
$ cd $SRC/msys2-runtime
$ git log --oneline -1
aa532e7bb Cygwin: Fix segfault when XSAVE area sizes are unaligned
$ git status
On branch msys2-3.6.9
nothing to commit, working tree clean
The msys2-3.6.9 branch already carries the 39 MSYS2-specific patches on top of cygwin-3.6.x; we don't apply MSYS-specific patches on top.
Of stage 07's three newlib-cygwin patches:
fix-winsize.patch— already applied upstream in msys2-3.6.9 (skip).store-tls-pointer-in-win32-tls.patch— intentionally skipped. It rewrites x86_64_my_tlsto use an explicit Win32 TLS slot for fiber/coroutine interop. MSYS2 itself doesn't ship that patch; reapplying it on top of msys2's own dcrt0/init/cygtls divergence is not safe to do unverified. Keeping plain msys2 behaviour.fix-cross.patch— applied. The autoconf-version unblock +EXEEXT=.execross-bootstrap fixup +-B/-isystemflag adjustments are independent of the MSYS2 diff and are needed for the same reasons here (autoreconf with current autoconf, cross link path).
$SRC/msys2-runtime/configure \
--build=x86_64-pc-linux-gnu \
--target=x86_64-pc-cygwin \
--prefix=$PREFIX/x86_64-pc-cygwin/msys \
--disable-shared \
--disable-doc \
--enable-static \
--disable-dumper \
--with-cross-bootstrap \
--with-msys2-runtime-commit=aa532e7bbf59a3ec1e29e13c9e8515d545cb2e16 \
ac_cv_prog_CC=gcc
Env block (target compile flags):
CFLAGS_FOR_TARGET="-D__MSYS__ -Wl,-L$PREFIX/x86_64-pc-cygwin/lib/w32api"
CXXFLAGS_FOR_TARGET="-D__MSYS__ -Wno-error=register -Wl,-L$PREFIX/x86_64-pc-cygwin/lib/w32api"
CFLAGS="-U_FORTIFY_SOURCE -fno-stack-protector"
CXXFLAGS="-U_FORTIFY_SOURCE -fno-stack-protector"
-D__MSYS__ matters. MSYS2's standard build uses target triplet
x86_64-pc-msys, where gcc's built-in spec defines __MSYS__ for free.
We're cross-compiling with the unmodified x86_64-pc-cygwin gcc, so we
push the macro in by hand. Without it, dll_init.cc/dcrt0.cc/cygwin.din
fall back to the cygwin spelling (cygwin_dll_init / cygwin_detach_dll)
and the link of new-msys-2.0.dll fails:
ld: cannot export msys_dll_init: symbol not defined
ld: libdll.a(sigfe.o):fake:(.text+0x1ec2): undefined reference to `msys_detach_dll'
--with-msys2-runtime-commit= is required: configure errors without it on
a non-git tree, and the runtime's uname -r reads it. SHA verified above.
make -j$JOBS all-target-newlib
( cd $BLD_DIR/x86_64-pc-cygwin/winsup/cygwin && make -j$JOBS all )
make -j1 install-target-newlib
( cd $BLD_DIR/x86_64-pc-cygwin/winsup/cygwin && make -j1 install )
We deliberately don't build winsup/cygserver, winsup/utils, or
winsup/testsuite. cygserver.exe etc. link with -lcygwin (hardcoded in
the unpatched cygwin gcc spec). At the msys2 build that resolves to the
cygwin sysroot's libcygwin.a — which has cygwin_crt0 but not
msys_crt0, and the link fails:
ld: build/.../winsup/cygwin/crt0.o:crt0.c:(.text+0x13): undefined reference to `msys_crt0'
(crt0.c was compiled with -D__MSYS__, so it calls msys_crt0;
libmsys-2.0.a defines it, but the spec asks for -lcygwin not
-lmsys-2.0.) MSYS2's normal build patches gcc's spec to emit
-lmsys-2.0; we deliberately don't patch gcc — driving the dialect via
specs is the whole point of option B. So we skip cygserver/utils. The
runtime artefacts we actually need (msys-2.0.dll, libmsys-2.0.a,
crt0.o, gcrt0.o, headers) all come out of newlib + winsup/cygwin.
$PREFIX/x86_64-pc-cygwin/msys/
bin/msys-2.0.dll # the runtime
lib/msys.specs # the specs file (stage 10)
lib/libmsys-2.0.a, libmsys-2.0.dll.a # static lib + import lib
lib/crt0.o, gcrt0.o # startup
lib/libc.a, libm.a, libpthread.a, libdl.a, libacl.a, libresolv.a, ...
include/ # msys-flavored cygwin/sys/<...> headers
cygwin/version.h, sys/cygwin.h, ...
The cygwin sysroot at $PREFIX/x86_64-pc-cygwin/{bin,lib,include} is left
untouched. Plain (no -specs) x86_64-pc-cygwin-gcc hello.c continues to
link against cygwin1.dll.
# msys.specs - drive x86_64-pc-cygwin gcc-15.2.0 to link against
# msys-2.0.dll instead of cygwin1.dll. Generated by scripts/10-msys-specs.sh.
# Usage:
# x86_64-pc-cygwin-gcc -specs=$PREFIX/x86_64-pc-cygwin/msys/lib/msys.specs hellomsys.c -o hellomsys.exe
# The compiled exe will import msys-2.0.dll, not cygwin1.dll.
*cpp:
+ -D__MSYS__
*lib:
%{pg:-lgmon} %{pthread: } -lmsys-2.0 %{mwindows:-lgdi32 -lcomdlg32} %{fvtable-verify=preinit:-lvtv -lpsapi; fvtable-verify=std:-lvtv -lpsapi} -ladvapi32 -lshell32 -luser32 -lkernel32
*self_spec:
+ -isystem $PREFIX/x86_64-pc-cygwin/msys/include -B$PREFIX/x86_64-pc-cygwin/msys/lib/
(literal $PREFIX is expanded to /home/kreijstal/git/msys-cross/prefix
at write time — specs files don't expand env vars at parse time)
How it works:
*cpp+-prepends-D__MSYS__to gcc's stock cpp builtins.__CYGWIN__is still defined by the underlying cygwin spec, and__MSYS__is now defined too — msys is a superset of cygwin so both should be visible to feature-test code.*libis a full replacement of the stock cygwin*libwith-lcygwinswapped for-lmsys-2.0. Everything else (kernel32, user32, ...) mirrors gcc-15.2's stock cygwin spec.*self_spec+-appends-isystem <msys>/include -B<msys>/lib/to every invocation.-isystemmakes the msys-flavoredcygwin/version.h/sys/cygwin.hwin over the cygwin sysroot's same-named headers;-Bmakes gcc treat<msys>/lib/as both a startfile prefix (socrt0.o/gcrt0.ocome from the msys sub-prefix) and a library prefix (so-lmsys-2.0resolves there before falling through to the cygwin sysroot).
We do not override *startfile or *cpp_unique_options directly —
attempting to redefine *cpp_unique_options with a self-reference like
-isystem X %(cpp_unique_options) segfaults gcc-15.2 (recursive macro
expansion). -B via *self_spec achieves the same startfile redirection
without that risk.
$ x86_64-pc-cygwin-gcc -specs=$PREFIX/x86_64-pc-cygwin/msys/lib/msys.specs /tmp/hellomsys.c -o /tmp/hellomsys.exe
$ x86_64-pc-cygwin-objdump -p /tmp/hellomsys.exe | grep "DLL Name"
DLL Name: KERNEL32.dll
DLL Name: msys-2.0.dll
$ ( cd /tmp && cp $PREFIX/x86_64-pc-cygwin/msys/bin/msys-2.0.dll . && wine /tmp/hellomsys.exe )
__MSYS__ defined
__CYGWIN__ defined
argv0=/tmp/hellomsys
argv0 comes back POSIX-translated (/tmp/hellomsys, not Z:\tmp\...) —
that's the msys runtime's path-conversion layer kicking in. msys-only
behaviour we wouldn't see under cygwin.
Cygwin baseline regression check (no -specs):
$ x86_64-pc-cygwin-gcc /tmp/hello.c -o /tmp/hello.exe
$ x86_64-pc-cygwin-objdump -p /tmp/hello.exe | grep "DLL Name"
DLL Name: cygwin1.dll
DLL Name: KERNEL32.dll
$ ( cd /tmp && cp $PREFIX/x86_64-pc-cygwin/bin/cygwin1.dll . && wine /tmp/hello.exe )
hello cygwin from /tmp/hello
Still cygwin1.dll, still working. The two dialects coexist.
The Cygwin WARNING: Couldn't compute FAST_CWD pointer warning shows on
both runs and is expected under wine (already documented in stage 08).
#include <stdio.h>
int main(int argc, char **argv) {
#ifdef __MSYS__
printf("__MSYS__ defined\n");
#else
printf("__MSYS__ NOT defined\n");
#endif
#ifdef __CYGWIN__
printf("__CYGWIN__ defined\n");
#else
printf("__CYGWIN__ NOT defined\n");
#endif
printf("argv0=%s\n", argv[0]);
return 0;
}- The runtime build had to be split into newlib + winsup/cygwin only;
make allwould also pull in cygserver/utils/testsuite, which fail to link without a-lmsys-2.0-aware gcc spec (see "Build targets" above). 09-msys2-runtime.shreuses an existing$BLD_DIR/config.statusif present, so re-runs of the script don't re-configure from scratch on a failed-then-fixed build. Wipe$BLD_DIRmanually to force a clean configure.10-msys-specs.shis idempotent — re-running just rewrites the specs file with current$PREFIX.- Total wall time for stage 09 on this host: ~12 min for the runtime build (fresh build dir, 16 cores) + a few seconds for stage 10 + smoke.
- 09 smoke — execute
hello.exe/hellomsys.exeon a cygwin/ReactOS host (currently only validated under wine). - 11 (option C, optional) — proper
x86_64-pc-msysretriple of the full pipeline. Not needed if option B is sufficient for the porting work at hand.
This document will be updated as each stage lands.
Target: /home/kreijstal/git/reactos/build_nt62/livecd.iso, booted under QEMU with the MSYS payload ISO and user-mode NIC forwarding enabled:
-nic user,hostfwd=tcp:127.0.0.1:7000-:7000The canonical MSYS layout works on ReactOS NT6.2:
E:\usr\bin\hellomsys.exe
hello from msys runtime on reactos
argc=1 pid=1535
Final run summary:
FAST_CWD_WARNING=False
SUCCESS=True
FATAL=False
BAD_COMMAND=False
This confirms both the ReactOS LSASS account-domain fix and the x64 FAST_CWD compatibility path are sufficient for the simple MSYS runtime hello-world smoke.
The successful ISO layout was:
E:\usr\bin\hellomsys.exe
E:\usr\bin\msys-2.0.dll
A root-level layout is expected to fail:
E:\hellomsys.exe
E:\msys-2.0.dll
Failure mode:
*** fatal error - add_item ("\??", "/", ...) failed, errno 22
Reason: MSYS computes cygheap->installation_root from the loaded runtime DLL handle (cygwin_hmodule, actually msys-2.0.dll). In init_cygheap::init_installation_root, MSYS backs up from:
...\usr\bin\msys-2.0.dll
by stripping:
msys-2.0.dll
bin
usr
If the DLL is at E:\msys-2.0.dll, backing up two MSYS layout directories collapses to bare \??, which is not a valid native root mount.
-
dll/win32/lsasrv/policy.c- Ensure
PolicyDnsDomainInformationandPolicyAccountDomainInformationreturn allocated empty strings instead of bogus NULL/garbage fields. This fixed the earlier hang in MSYS user/domain initialization.
- Ensure
-
dll/ntdll/def/ntdll.spec- On x64, export
RtlGetCurrentDirectory_Uthrough the MSYS decoy soGetProcAddress(ntdll, "RtlGetCurrentDirectory_U")exposes the byte pattern MSYS scans.
- On x64, export
-
dll/ntdll/compat/amd64/msys2.S- Decoy sequence points MSYS at
RtlpCurDirRefvia the expectedFastPebLock/RtlEnterCriticalSection/mov rel(%rip), %rbxpattern.
- Decoy sequence points MSYS at
-
sdk/include/ndk/rtltypes.h- Extended
RTLP_CURDIR_REFto be prefix/layout-compatible with MSYSfcwd_access_t:
- Extended
LONG RefCount;
HANDLE Handle;
ULONG OldDismountCount;
UNICODE_STRING Path;
LONG FSCharacteristics;
WCHAR Buffer[2] aligned(8);-
sdk/lib/rtl/path.cRtlSetCurrentDirectory_Unow allocates enough storage for the embedded FAST_CWD path buffer and populatesPath,FSCharacteristics, and the buffer.
-
dll/win32/kernel32/client/loader.c- Defensive
GetModuleFileNameWnormalization strips native drive prefix\??\X:\...toX:\...for user-visible module names.
- Defensive
-
dll/win32/kernel32/kernel32_vista/GetFinalPathNameByHandle.c- DOS final-path output avoids producing malformed
\\?\\??\X:\...when mountmgr/object names already contain a native DOS-device prefix.
- DOS final-path output avoids producing malformed
MSYS installation-root derivation is from msys-2.0.dll, not from hellomsys.exe:
msys2-runtime/winsup/cygwin/init.cc: dll_entry
msys2-runtime/winsup/cygwin/mm/cygheap.cc: init_cygheap::init_installation_root
Accepted path forms for MSYS runtime discovery include:
C:\msys64\usr\bin\msys-2.0.dll
\\?\C:\msys64\usr\bin\msys-2.0.dll
\\server\share\msys64\usr\bin\msys-2.0.dll
\\?\UNC\server\share\msys64\usr\bin\msys-2.0.dll
Malformed/problematic forms include raw NT device paths and root-level MSYS DLL placement:
\Device\HarddiskVolume1\...\msys-2.0.dll
E:\msys-2.0.dll
The current GetModuleFileNameW path normalization is a public-API boundary fix. For production-quality behavior, normalize user-visible module full names earlier in ntdll loader state:
dll/ntdll/ldr/ldrinit.c: LdrpInitializeProcess
dll/ntdll/ldr/ldrutils.c: LdrpResolveDllName / LdrpMapDll / LdrpCheckForLoadedDll
That avoids inconsistent behavior for code reading LDR_DATA_TABLE_ENTRY.FullDllName directly, loader notifications, PSAPI/debug consumers, and loader-cache comparisons.