-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflake.nix
More file actions
541 lines (499 loc) · 33.5 KB
/
Copy pathflake.nix
File metadata and controls
541 lines (499 loc) · 33.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
{
description = "Swift compiler development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
llvm = pkgs.llvmPackages_20;
# llbuild's CMake (FindCurses) links the tools with a bare `-lcurses`,
# but nixpkgs ncurses only ships libncursesw.so (+ a libncurses.so
# symlink), not libcurses.so -> `cannot find -lcurses`. Provide a compat
# package whose lib/ MIRRORS all of ncurses' libs AND adds libcurses.so.
# As a buildInput its lib dir lands on both NIX_LDFLAGS (-L, so the link
# finds -lcurses) AND the linked binaries' RUNPATH. It must therefore
# also contain libncursesw.so.6 itself: the `-lcurses` link records
# NEEDED=libncursesw.so.6 (ncursesw's soname), so the SAME RUNPATH dir has
# to satisfy that at runtime, else binaries fail to load with
# "libncursesw.so.6: cannot open shared object file".
cursesCompat = pkgs.runCommandLocal "curses-compat" { } ''
mkdir -p $out/lib
ln -s ${pkgs.ncurses}/lib/* $out/lib/
ln -s ${pkgs.ncurses}/lib/libncursesw.so $out/lib/libcurses.so
'';
# COMPLETE host swift toolchain. When the swift build runs the just-built
# swift-frontend/swiftc to compile the stdlib, it bakes
# `LD_LIBRARY_PATH=<host-toolchain>/lib/swift/linux` into its ninja rules.
# nixpkgs' DT_RUNPATH is non-transitive, so the just-built tools can't
# resolve transitively-needed runtime libs (libdispatch.so, then Foundation)
# via rpath; they rely on that LD_LIBRARY_PATH. But the nixpkgs swift
# wrapper's lib/swift/linux LACKS libdispatch.so/libFoundation.so (they sit
# in swiftPackages.Dispatch/Foundation's top-level /lib). Present a toolchain
# whose lib/swift/linux ALSO contains those .so's. The wrapper's bin/ scripts
# use absolute store paths internally, so symlinking them into a new prefix
# keeps them working. Put this first on PATH (shellHook) so build-script
# detects it as the host toolchain and bakes a COMPLETE LD_LIBRARY_PATH.
bootstrapSwift = pkgs.runCommandLocal "swift-bootstrap-complete" { } ''
mkdir -p $out/bin $out/lib/swift/linux $out/lib/swift
for f in ${pkgs.swift}/bin/*; do ln -s "$f" "$out/bin/"; done
for d in ${pkgs.swift}/lib/*; do
n=$(basename "$d"); [ "$n" = swift ] && continue
ln -s "$d" "$out/lib/$n"
done
for d in ${pkgs.swift}/lib/swift/*; do
n=$(basename "$d"); [ "$n" = linux ] && continue
ln -s "$d" "$out/lib/swift/$n"
done
for f in ${pkgs.swift}/lib/swift/linux/*; do ln -s "$f" "$out/lib/swift/linux/"; done
# Dispatch ships its .so's in top-level /lib; Foundation in lib/swift/linux.
for f in ${pkgs.swiftPackages.Dispatch}/lib/*.so; do ln -sf "$f" "$out/lib/swift/linux/"; done
for f in ${pkgs.swiftPackages.Foundation}/lib/swift/linux/*.so; do ln -sf "$f" "$out/lib/swift/linux/"; done
'';
# A minimal Linux sysroot (usr/include -> glibc headers, usr/lib -> glibc
# libs) for the just-built stdlib swiftc. Swift's ClangImporter detects
# the target libc by asking the clang TOOLCHAIN for its system include
# paths (sysroot-based) and checking for inttypes.h/unistd.h/stdint.h; it
# does NOT consult -Xcc -idirafter. With the build's `-sdk /`, SysRoot=/,
# and NixOS has no /usr/include -> "libc not found" -> SwiftGlibc/Glibc
# overlay fails. Passing `-Xcc --sysroot=${swiftSysroot}` (which takes
# precedence in ClangIncludePaths.cpp) puts glibc on the toolchain's system
# include path so libc is found.
# Sysroot is ENRICHED with the gcc toolchain too (c++ headers + gcc install
# dir), so clang's GCC-under-sysroot detection finds BOTH glibc (libc, for the
# Glibc overlay) AND libstdc++ (for the CxxStdlib C++ overlay) purely via
# `-sdk ${swiftSysroot}`, with no -Xcc --gcc-toolchain needed. Crucially `-sdk`
# IS recorded in the emitted .swiftinterface, so the verify-emitted-module-
# interface recompile also finds the modules (a bare --gcc-toolchain is NOT
# recorded and fails verification). Verified: CxxStdlib emit-module builds
# against this sysroot with no --gcc-toolchain. Per-entry symlinks (not
# `cp -as`) so the dirs stay writable to add the c++/gcc entries.
# usr/lib needs the glibc libs (clang's gcc/libstdc++ detection for the
# CxxStdlib overlay only succeeds when the sysroot has a usable libc) AND the
# gcc install dir. BUT glibc's `libc.so`/`libm.so` are TEXT linker scripts
# with ABSOLUTE GROUP() paths; inside a sysroot, GNU ld prefixes the sysroot
# onto them when linking with -sdk/--sysroot ->
# "cannot open <sysroot>/nix/store/<glibc>/lib/libc.so.6". Fix: symlink all
# glibc libs, then OVERWRITE libc.so/libm.so with equivalent scripts that use
# BARE names, which ld resolves from this same usr/lib (no prefixing). This
# satisfies BOTH the overlay compile (libstdc++ found) and the link.
# GLIBC-ONLY sysroot (headers + libs), used as the stdlib swiftc's `-sdk`.
# It deliberately does NOT contain gcc's c++ headers. C++ interop is enabled,
# but libstdc++ is delivered to swiftc a different way: via `-Xcc
# --gcc-toolchain=<nix-gcc>` (see dobuild.sh: SWIFT_STDLIB_EXTRA_SWIFT_COMPILE_FLAGS
# + the Cxx overlay flags). This is REQUIRED for correctness: if the sysroot
# ALSO carried c++ headers, libstdc++ would be reachable via TWO paths: the
# sysroot path (where Swift's ClangImporter injects the `std` clang modulemap)
# and the real <nix-gcc> path (where textual #includes + the gcc-toolchain
# resolve). clang then sees bits/stl_pair.h, parse_numbers.h, … under two file
# identities and pulls them into BOTH the `std` and `SwiftGlibc` modules ->
# "included multiple times" / "redefinition of 'piecewise_construct_t'" etc.
# Keeping c++ out of the sysroot routes ALL libstdc++ resolution through the
# single <nix-gcc> path (verified in isolation 2026-06-11: import CxxStdlib +
# Glibc + std.string() compiles cleanly this way; fails with c++ in sysroot).
#
# glibc's libc.so/libm.so are kept as their ORIGINAL upstream linker scripts
# (the Glibc overlay's libc detection wants a real glibc). Those scripts have
# ABSOLUTE /nix/store/<glibc>/lib/... GROUP() paths; when the bare clang LINKS
# a stdlib C++ shared lib (e.g. libswiftRemoteMirror.so) with -sdk (= --sysroot),
# GNU ld PREFIXES the sysroot onto them -> tries to open
# <sysroot>/nix/store/<glibc>/lib/libc.so.6 (absent) -> "ld.gold: error: cannot
# open .../libc.so.6". FIX (verified in isolation): a SYMLINK FARM that mirrors the
# glibc store dir UNDER the sysroot at its own absolute path, so the prefixed
# path resolves. Keeps the original scripts (compile OK) AND makes the link
# resolve (link OK) without rewriting them.
swiftSysroot = pkgs.runCommandLocal "swift-sysroot" { } ''
mkdir -p $out/usr/include $out/usr/lib $out/nix/store
for f in ${pkgs.glibc.dev}/include/*; do ln -s "$f" $out/usr/include/; done
for f in ${pkgs.glibc}/lib/*; do ln -s "$f" $out/usr/lib/; done
# Symlink farm: make ld's sysroot-prefixed absolute GROUP paths resolve.
ln -s ${pkgs.glibc} $out/nix/store/$(basename ${pkgs.glibc})
'';
# Shared package set: every "real" dev shell (full, compiler) uses this
# exact list; the stub shells (sourcekit, swiftpm) don't need a toolchain.
commonPackages = with pkgs; [
# Build system
cmake
ninja
pkg-config
# NOTE: glibc.dev is deliberately NOT a buildInput. As a buildInput
# the stdenv prepends `-isystem ${glibc.dev}/include` to the FRONT of
# the C++ include path; clang then dedups and drops the cc-wrapper's
# correctly-placed *trailing* glibc, leaving glibc before gcc's
# libstdc++ headers and breaking libstdc++'s `#include_next <math.h>`
# (-> "'math.h' file not found" when compiling llbuild's <cmath>).
# The bare compiler-rt clang still gets glibc via C_INCLUDE_PATH in
# the shellHook, which references the store path directly.
# Bootstrap Swift compiler (≥5.9 required to compile swift-syntax and
# the stdlib's macro plugins). nixpkgs 5.10.1 is pre-built in the
# Hydra cache so this just downloads a binary, it does not rebuild Swift.
swift
# The bootstrap swift (5.10.1) ships only the core stdlib modules, NOT
# Foundation/Dispatch. llbuild's Swift bindings (llbuildSwift) do
# `import Foundation`, so add them: the swift-wrapper setup-hook adds
# each buildInput's lib/swift dir to NIX_SWIFTFLAGS_COMPILE (-I) and
# NIX_LDFLAGS (-L), making the modules visible to the build's swiftc.
swiftPackages.Foundation
swiftPackages.Dispatch
# Provides libcurses.so for llbuild's `-lcurses` link (see let block).
cursesCompat
# Bootstrap compiler (clang is required; swift is optional if already built)
llvm.clang
llvm.llvm
llvm.lld
# Python (build-script + utils)
(python3.withPackages (ps: with ps; [ six ]))
# Required libraries
libedit
libxml2
zlib
icu
util-linux # provides libuuid / uuid.h
curl
sqlite
# Optional but common
cmark
swig
rsync
git
# Debugging / tooling
gdb
ccache
];
# --- shellHook, split into composable parts --------------------------
# baseHook : the toolchain wiring every build needs (the 5 NixOS
# fixes from HACKING.md §1), used by BOTH full & compiler.
# foundationHook : the augmented corelibs sysroot ($SWIFT_CORELIBS_SDK /
# $SWIFT_GCC_LIB / $SWIFT_RUNTIME_LIB), needed ONLY to
# build Foundation, so it's added to the full shell only.
# testHook : SWIFT_DRIVER_TEST_OPTIONS for the lit suite (both).
baseHook = ''
export SWIFT_SOURCE_ROOT="$(pwd)"
export SWIFT_BUILD_ROOT="$(pwd)/build"
# Make the COMPLETE host swift toolchain (see bootstrapSwift) the one
# build-script detects, so the LD_LIBRARY_PATH it bakes for running the
# just-built compiler against the stdlib includes libdispatch.so/Foundation.
export PATH="${bootstrapSwift}/bin:$PATH"
# Build with clang, not the gcc stdenv default. llbuild (built by
# swift-driver's helper via CMake) uses the environment CC/CXX, and the
# mkShell gcc stdenv defaults them to gcc/g++, which chokes on llbuild's
# clang-only warning flags (-Wbool-conversion, -Wdocumentation, …).
# Force clang so llbuild (and anything else honouring CC/CXX) matches the
# rest of the toolchain. (cmark/llvm/swift set CMAKE_*_COMPILER
# explicitly, so this only affects the env-driven builds.)
export CC=clang
export CXX=clang++
# The nix clang wrapper always adds --gcc-toolchain=<gcc>, which is
# "unused during compilation" on -c steps. swift-corelibs-libdispatch
# compiles its C with -Werror, turning that into a fatal
# -Wunused-command-line-argument. Disable that one warning (it only
# affects the nix-wrapped clang; pure warning flag, no include-order
# effect, so it can't disturb the libstdc++ #include_next fix).
export NIX_CFLAGS_COMPILE="-Wno-unused-command-line-argument''${NIX_CFLAGS_COMPILE:+ $NIX_CFLAGS_COMPILE}"
# Prefer ccache if available
if command -v ccache &>/dev/null; then
export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
fi
# --- libc header visibility on NixOS ---------------------------------
# Two different compilers need glibc headers and neither finds them by
# default; CPATH must stay unset because it breaks C++ #include_next.
# CPLUS_INCLUDE_PATH must ALSO stay unset: we no longer set it (libstdc++
# comes via --gcc-install-dir, glibc via -idirafter; see 1b/1d), but an
# inherited value (e.g. direnv re-entry, or a nested `nix develop`) would
# otherwise leak gcc's include/c++/<ver> into compiler-rt's -nostdinc++
# TUs and resurrect the gcc-15 "redefinition of 'array'" build failure.
unset CPATH CPLUS_INCLUDE_PATH
# 1. The freshly-built, *non*-nix-wrapped clang (build/.../bin/clang)
# compiles compiler-rt's builtins AND the Swift stdlib/runtime C
# sources. It ignores NIX_CFLAGS_COMPILE but honours C_INCLUDE_PATH
# (C-only, so it can't disturb C++ include_next).
export C_INCLUDE_PATH="${pkgs.glibc.dev}/include''${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}"
# 1b. That same bare clang also compiles the stdlib/runtime *C++* sources
# (CommandLine.cpp, Demangler.cpp, …) and so needs gcc's libstdc++
# headers (algorithm, cstdint) + glibc (stdlib.h/math.h via
# #include_next). We DELIBERATELY do NOT use CPLUS_INCLUDE_PATH for
# this any more. CPLUS_INCLUDE_PATH dirs are injected like -isystem
# and are NOT suppressed by -nostdinc++, so putting gcc's
# include/c++/<ver> there leaks it into compiler-rt's sanitizer TUs
# (which build with -nostdinc++). With gcc 15 that is fatal: the
# sanitizers' `#include <math.h>` then resolves to libstdc++'s math.h
# wrapper -> <cmath> -> bits/stl_pair.h, which forward-declares
# std::array, colliding with sanitizer_redefine_builtins.h's poisoned
# `array` ("redefinition of 'array' as different kind of symbol").
# Instead libstdc++ is delivered via --gcc-install-dir and glibc via
# -idirafter, BOTH in CCC_OVERRIDE_OPTIONS below (see 1d). That keeps
# the C++ TUs working (libstdc++ found; its #include_next <math.h>/
# <stdlib.h> reach glibc, which -idirafter places dead last) AND keeps
# compiler-rt clean (--gcc-install-dir's libstdc++ dirs ARE suppressed
# by -nostdinc++, so the sanitizers' <math.h> hits glibc's C header).
# Verified 2026-06-16 against gcc 15.2.0: nsan/asan compile under
# -nostdinc++, and a normal libstdc++ C++ TU compiles+links+runs on
# both the bare and the nix-wrapped clang.
# 1c. The bare clang also *links* the stdlib/runtime shared libraries
# (e.g. libswiftRemoteMirror.so) with -fuse-ld=gold, and unwrapped
# it has no -L for gcc/glibc, so it can't find -lstdc++/-lgcc_s/
# -lgcc nor the crt startup objects (crti.o/Scrt1.o/crtbeginS.o).
# clang searches LIBRARY_PATH for both -l libs AND crt files, so
# point it at: gcc-lib (libstdc++ + libgcc_s), gcc's target dir
# (libgcc.a + crtbegin/crtend), and glibc (crt1/crti/crtn + libc).
# These links are plain ninja commands (not `cmake -E env`-wrapped),
# so the shell's LIBRARY_PATH reaches them.
export LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.stdenv.cc.cc}/lib/gcc/${pkgs.stdenv.hostPlatform.config}/${pkgs.stdenv.cc.cc.version}:${pkgs.glibc}/lib''${LIBRARY_PATH:+:$LIBRARY_PATH}"
# 1d. LIBRARY_PATH covers -l libs, but clang locates the crt startup
# objects (crt1/crti/crtn/crtbegin/crtend) via its GCC-toolchain
# detection and the sysroot, NOT LIBRARY_PATH -- and the bare clang
# detects no toolchain on NixOS (sysroot is "/", which is empty).
# CCC_OVERRIDE_OPTIONS injects driver flags into EVERY clang call:
# --gcc-install-dir=<gcc>/lib/gcc/<triple>/<ver> -> gcc crtbegin/
# crtend + libgcc + libstdc++ headers/libs, correctly ordered AND
# (unlike CPLUS_INCLUDE_PATH) suppressed by -nostdinc++, so it
# supplies libstdc++ to the C++ TUs without leaking into compiler-rt
# (see 1b);
# -B<glibc>/lib -> glibc's
# crt1/crti/crtn/Scrt1.o;
# -idirafter <glibc>/include -> glibc's C headers
# placed dead last, so libstdc++'s #include_next <math.h>/<stdlib.h>
# and compiler-rt's <math.h> both resolve to glibc (this replaces
# the old CPLUS_INCLUDE_PATH glibc entry, which sat too early).
# ('+' appends the arg.) Verified this does not regress the
# nix-wrapped clang (it already has a consistent gcc/glibc; the extra
# -idirafter glibc is lowest-priority and never shadows its own copies).
export CCC_OVERRIDE_OPTIONS="+-B${pkgs.glibc}/lib +--gcc-install-dir=${pkgs.stdenv.cc.cc}/lib/gcc/${pkgs.stdenv.hostPlatform.config}/${pkgs.stdenv.cc.cc.version} +-idirafter +${pkgs.glibc.dev}/include"
# 2. The nix-wrapped clang (llbuild, swift-driver, …) must NOT have
# glibc forced onto the FRONT of its include path. The cc-wrapper
# already places glibc AFTER gcc's libstdc++ headers, which is
# exactly where libstdc++'s '#include_next <math.h>' needs it.
# Any front-loaded glibc (a glibc.dev buildInput's '-isystem', or an
# '-idirafter' that clang dedups against the front copy) inverts that
# order and makes <cmath>/<cstdlib> fail to find math.h/stdlib.h.
# Verified 2026-06-10: with no front glibc, llbuild's Atomic.cpp
# (the real TU, '-I llbuild/include' so <cmath> is actually pulled)
# compiles cleanly. So we add NOTHING to NIX_CFLAGS_COMPILE here.
# On NixOS, 32-bit system headers are not at standard paths; skip i386
# builtins entirely (not needed for Swift development on x86_64).
# BUILTINS_CMAKE_ARGS is forwarded directly into the builtins ExternalProject.
#
# Glibc-sysroot for the stdlib overlays: the just-built (unwrapped) swiftc
# compiles the overlays (Glibc, CxxStdlib, …) with `-sdk /`; its ClangImporter
# detects the target libc via the clang toolchain's SYSROOT-based system
# include paths (NOT -Xcc -idirafter / CPATH), so on NixOS (empty /usr/include)
# it reports "libc not found" and SwiftGlibc/the overlays fail. Fix: point the
# Linux SDK path at ${swiftSysroot} so swiftc gets `-sdk ${swiftSysroot}` ->
# ClangImporter SysRoot=swiftSysroot -> usr/include (glibc) -> libc found
# (verified: replaying Glibc.o with -sdk ${swiftSysroot} builds it).
# IMPORTANT: EXTRA_CMAKE_OPTIONS only reaches the LLVM/builtins cmake, NOT the
# swift compiler's cmake (verified), so the BUILTINS fix below works, but the
# SDK-path -D must be passed on the build-script COMMAND LINE instead (which
# does reach swift cmake, and sticks because SwiftConfigureSDK sets the SDK
# path with `CACHE ... ` without FORCE). We export the path for that command:
# utils/build-script ... --extra-cmake-options=-DSWIFT_SDK_LINUX_ARCH_x86_64_PATH=$SWIFT_GLIBC_SYSROOT
export EXTRA_CMAKE_OPTIONS="-DBUILTINS_CMAKE_ARGS=-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON"
export SWIFT_GLIBC_SYSROOT="${swiftSysroot}"
# The C++ interop overlay (CxxStdlib) defaults to `-Xcc --gcc-toolchain=/usr`
# (SwiftConfigureSDK.cmake), empty on NixOS -> "libstdc++ not found" ->
# "underlying module 'CxxStdlib' not found". Point --gcc-toolchain at the
# nix gcc (has libstdc++). Passed on the build-script CLI via
# SWIFT_SDK_LINUX_CXX_OVERLAY_SWIFT_COMPILE_FLAGS (verified: replaying
# CxxStdlib with -Xcc --gcc-toolchain=$SWIFT_GCC_TOOLCHAIN builds it).
export SWIFT_GCC_TOOLCHAIN="${pkgs.stdenv.cc.cc}"
'';
foundationHook = ''
# --- Foundation / corelibs augmented sysroot -------------------------
# Foundation's macros (compiled by the just-built 6.5-dev compiler) import
# swift-syntax, whose HOST modules were built by the BOOTSTRAP swift 5.10.1
# and so cannot be loaded -> the compiler REBUILDS swift-syntax from its
# .swiftinterface in-process. That rebuild needs a glibc `-sdk` to find
# SwiftGlibc (NixOS /usr/include is empty; the importer ignores
# C_INCLUDE_PATH/SDKROOT -- only -sdk/-isysroot work). But a *plain* glibc
# -sdk also redirects swiftc's runtime lookup into the sysroot, so the
# corelibs LINKS can't find swiftrt.o / the swift .so's. Fix: an AUGMENTED
# sysroot = glibc (headers + libs + the ld store-farm, all from swiftSysroot)
# PLUS a symlink to the just-built swift runtime (usr/lib/swift ->
# build/.../lib/swift), so the SAME -sdk satisfies BOTH the macro compile
# (SwiftGlibc) and the corelibs links (swiftrt.o + libswift*.so). Plus
# libstdc++/libgcc_s so the link's C++ runtime resolves. Built here in the
# shellHook (not as a nix derivation) because it must reference the build
# tree. dobuild.sh pairs it with `-L $SWIFT_GCC_LIB -Xlinker -rpath-link
# -Xlinker $SWIFT_GCC_LIB` so ld also resolves libswiftCore's INDIRECT
# libstdc++.so.6 NEEDED, which --sysroot otherwise hides.
export SWIFT_GCC_LIB="${pkgs.stdenv.cc.cc.lib}/lib"
corelibsSdk="$SWIFT_BUILD_ROOT/corelibs-sdk"
swiftRuntime="$SWIFT_BUILD_ROOT/Ninja-RelWithDebInfoAssert+swift-DebugAssert/swift-linux-x86_64/lib/swift"
rm -rf "$corelibsSdk"; mkdir -p "$corelibsSdk/usr/lib"
ln -sfn "${swiftSysroot}/usr/include" "$corelibsSdk/usr/include"
ln -sfn "${swiftSysroot}/nix" "$corelibsSdk/nix"
for f in ${swiftSysroot}/usr/lib/*; do ln -sfn "$f" "$corelibsSdk/usr/lib/$(basename "$f")"; done
for f in ${pkgs.stdenv.cc.cc.lib}/lib/libstdc++.so* ${pkgs.stdenv.cc.cc.lib}/lib/libgcc_s.so*; do ln -sfn "$f" "$corelibsSdk/usr/lib/$(basename "$f")"; done
ln -sfn "$swiftRuntime" "$corelibsSdk/usr/lib/swift"
export SWIFT_CORELIBS_SDK="$corelibsSdk"
# The core swift runtime dir (libswiftCore/Synchronization/Concurrency/…).
# Corelibs EXECUTABLE links (plutil, FoundationNetworking tools) need it on
# -L + -rpath-link so ld resolves libFoundation.so's INDIRECT NEEDED
# libswiftSynchronization.so etc. (the -sdk sysroot's usr/lib/swift/linux is
# NOT in ld's default indirect-dependency search).
export SWIFT_RUNTIME_LIB="$swiftRuntime/linux"
'';
testHook = ''
# === lit test suite: NixOS toolchain flags, delivered with NO edits to
# the swift/ or llvm-project/ source trees ===================================
# Running `check-swift` builds *executables* (and resilient dylibs) with the
# just-built BARE clang, which the swift lit.cfg puts first in PATH. That
# clang has no NixOS toolchain knowledge, and lit deliberately sanitizes the
# test environment (its hardcoded TestingConfig.py allowlist strips
# CCC_OVERRIDE_OPTIONS / NIX_* / CPATH...), so the test builds get none of the
# dev-shell's toolchain wiring. Result on a pristine checkout: executables
# link against crt-less / wrong-interpreter defaults and SIGSEGV at startup
# (/lib64/ld-linux is nix-ld here, mismatched with the nix glibc in RUNPATH),
# and any test that imports StdlibUnittest fails to even compile SwiftGlibc
# ("libc not found", NixOS /usr/include is empty).
#
# The fix uses ONLY swiftc's supported SWIFT_DRIVER_TEST_OPTIONS knob, which
# swift's lit.cfg reads from this environment and appends to the build/driver
# command lines (but NOT to the swift-frontend line, so -verify/typecheck
# tests are untouched). Everything the sanitized env would otherwise drop is
# re-supplied here as explicit compiler flags:
# -Xcc --sysroot=<glibc sysroot> : the ClangImporter finds the target libc
# so it can (re)build the SwiftGlibc clang module (StdlibUnittest tests).
# -Xclang-linker -B<glibc>/lib : glibc crt (crt1/crti/crtn/Scrt1.o).
# -Xclang-linker --gcc-install-dir=<gcc> : gcc crtbegin/crtend + libgcc.
# -Xclang-linker -Wl,--dynamic-linker,<nix glibc ld.so> : the correct program
# interpreter, so executables don't fall back to /lib64 (nix-ld) and crash.
# -Xclang-linker -Wno-unused-command-line-argument : the above are link-only;
# silence the warning on the rare driver test that only compiles.
# -L <gcc lib> -Xlinker -rpath-link -Xlinker <gcc lib> : resolve libswiftCore's
# INDIRECT libstdc++.so.6 NEEDED at link.
# This mirrors the CCC_OVERRIDE_OPTIONS toolchain completion the main build
# uses, but scoped to the test harness via a public env var instead of patching
# lit. Verified: Parse 252/252 + the executable Parse tests pass; Interpreter
# 257/260 (the 3 failures are an unrelated LTO/lld issue and 2 cdecl C-interop
# tests: llvm_link_time_opt.swift, cdecl_official_run.swift,
# cdecl_implementation_run.swift).
# NOTE the leading space: swift's lit.cfg does `swift_driver_test_options +=
# os.environ['SWIFT_DRIVER_TEST_OPTIONS']` with no separator, so without it the
# first token glues onto the preceding -Xfrontend '...' option and swiftc sees
# a mangled argument.
export SWIFT_DRIVER_TEST_OPTIONS=" -Xcc --sysroot=${swiftSysroot} -Xclang-linker -B${pkgs.glibc}/lib -Xclang-linker --gcc-install-dir=${pkgs.stdenv.cc.cc}/lib/gcc/${pkgs.stdenv.hostPlatform.config}/${pkgs.stdenv.cc.cc.version} -Xclang-linker -Wl,--dynamic-linker,${pkgs.glibc}/lib/ld-linux-x86-64.so.2 -Xclang-linker -Wno-unused-command-line-argument -L ${pkgs.stdenv.cc.cc.lib}/lib -Xlinker -rpath-link -Xlinker ${pkgs.stdenv.cc.cc.lib}/lib"
'';
# `nix run .#smoke-test`: validate a freshly-built toolchain with three
# tiny end-to-end programs (plain Swift, Foundation, C++ interop). Run it
# from the workspace root, ideally from inside the dev shell so the NixOS
# toolchain env (SWIFT_CORELIBS_SDK, SWIFT_GCC_TOOLCHAIN, …) is present.
# The Foundation and C++ tests SKIP without it; the plain test always runs.
smokeTest = pkgs.writeShellScript "swift-smoke-test" ''
set -u
BUILD="$PWD/build/Ninja-RelWithDebInfoAssert+swift-DebugAssert"
B="$BUILD/swift-linux-x86_64"
SWIFTC="$B/bin/swiftc"
if [ ! -x "$SWIFTC" ]; then
echo "smoke-test: no built compiler at"
echo " $SWIFTC"
echo "Build one first, from the workspace root:"
echo " nix develop .#full --command bash dobuild.sh foundation"
exit 1
fi
if [ -z "''${SWIFT_GLIBC_SYSROOT:-}" ]; then
echo "note: not in the dev shell, link/run may fail. Prefer:"
echo " nix develop .#full --command nix run .#smoke-test"
echo
fi
export LD_LIBRARY_PATH="$B/lib/swift/linux''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
fail=0
# --- Test 1: plain Swift ---------------------------------------------
echo 'print((1...5).map{$0*$0})' > "$tmp/t1.swift"
if out="$("$SWIFTC" "$tmp/t1.swift" -o "$tmp/t1" 2>"$tmp/t1.err" && "$tmp/t1")" \
&& [ "$out" = "[1, 4, 9, 16, 25]" ]; then
echo "PASS plain Swift -> $out"
else
echo "FAIL plain Swift -> '$out'"; sed 's/^/ /' "$tmp/t1.err"; fail=1
fi
# --- Test 2: Foundation (exact corelibs flags from README §3) --------
if [ -n "''${SWIFT_CORELIBS_SDK:-}" ] && [ -n "''${SWIFT_GCC_LIB:-}" ]; then
SDK="$tmp/foundation-sdk"
DESTDIR="$SDK" ${pkgs.ninja}/bin/ninja -C "$BUILD/libdispatch-linux-x86_64" install >/dev/null 2>&1
DESTDIR="$SDK" ${pkgs.ninja}/bin/ninja -C "$BUILD/foundation-linux-x86_64" install >/dev/null 2>&1
SDKLIB="$SDK/usr/lib/swift"
echo 'import Foundation; print(UUID().uuidString.count)' > "$tmp/t2.swift"
if out="$("$SWIFTC" "$tmp/t2.swift" -o "$tmp/t2" \
-sdk "$SWIFT_CORELIBS_SDK" -L "$SWIFT_GCC_LIB" -Xlinker -rpath-link -Xlinker "$SWIFT_GCC_LIB" \
-L "$B/lib/swift/linux" -Xlinker -rpath-link -Xlinker "$B/lib/swift/linux" \
-I "$SDKLIB/linux" -I "$SDKLIB" -L "$SDKLIB/linux" \
-Xlinker -rpath -Xlinker "$B/lib/swift/linux" -Xlinker -rpath -Xlinker "$SDKLIB/linux" \
2>"$tmp/t2.err" && "$tmp/t2")" \
&& [ "$out" = "36" ]; then
echo "PASS Foundation -> UUID length $out"
else
echo "FAIL Foundation -> '$out'"; sed 's/^/ /' "$tmp/t2.err"; fail=1
fi
else
echo "SKIP Foundation (SWIFT_CORELIBS_SDK unset; run inside nix develop .#full)"
fi
# --- Test 3: C++ interop (exact -Xcc flags from README §3) -----------
if [ -n "''${SWIFT_GCC_TOOLCHAIN:-}" ] && [ -n "''${SWIFT_GLIBC_SYSROOT:-}" ]; then
mkdir -p "$tmp/cxxmod"
printf '#pragma once\ninline int cxx_answer() { return 42; }\n' > "$tmp/cxxmod/shim.h"
printf 'module CxxHello { header "shim.h" requires cplusplus }\n' > "$tmp/cxxmod/module.modulemap"
printf 'import CxxHello\nprint(cxx_answer())\n' > "$tmp/t3.swift"
if out="$("$SWIFTC" -cxx-interoperability-mode=default \
-Xcc --gcc-toolchain="$SWIFT_GCC_TOOLCHAIN" -Xcc --sysroot="$SWIFT_GLIBC_SYSROOT" \
-I "$tmp/cxxmod" "$tmp/t3.swift" -o "$tmp/t3" 2>"$tmp/t3.err" && "$tmp/t3")" \
&& [ "$out" = "42" ]; then
echo "PASS C++ interop -> cxx_answer() = $out"
else
echo "FAIL C++ interop -> '$out'"; sed 's/^/ /' "$tmp/t3.err"; fail=1
fi
else
echo "SKIP C++ interop (SWIFT_GCC_TOOLCHAIN unset; run inside nix develop .#full)"
fi
echo
if [ "$fail" = 0 ]; then echo "smoke-test: all run tests passed"; exit 0; fi
echo "smoke-test: FAILURES above"; exit 1
'';
in {
devShells = rec {
# The complete toolchain shell: compiler + stdlib + C++ interop +
# libdispatch + Foundation. Pair with `./dobuild.sh foundation`.
full = pkgs.mkShell {
name = "swift-dev-full";
packages = commonPackages;
shellHook = baseHook + foundationHook + testHook + ''
echo "Swift 6.5 dev shell (full: compiler + stdlib + C++ interop + Foundation). source root: $SWIFT_SOURCE_ROOT"
'';
};
# Compiler + standard library only: the faster loop for swift/lib and
# stdlib work. Drops the Foundation augmented-sysroot construction.
# Pair with `./dobuild.sh compiler`.
compiler = pkgs.mkShell {
name = "swift-dev-compiler";
packages = commonPackages;
shellHook = baseHook + testHook + ''
echo "Swift 6.5 dev shell (compiler + stdlib only). source root: $SWIFT_SOURCE_ROOT"
'';
};
# Backwards compatibility: `nix develop` keeps meaning the full shell.
default = full;
# Stubs for components not yet built/tested on NixOS.
sourcekit = pkgs.mkShell {
name = "swift-dev-sourcekit";
shellHook = ''
echo "SourceKit-LSP: not yet built/tested on NixOS. See HACKING.md section 5."
'';
};
swiftpm = pkgs.mkShell {
name = "swift-dev-swiftpm";
shellHook = ''
echo "SwiftPM: not yet built/tested on NixOS. See HACKING.md section 5."
'';
};
};
apps.smoke-test = {
type = "app";
program = "${smokeTest}";
meta.description = "Smoke-test a swift-nixos build (plain Swift, Foundation, C++ interop)";
};
});
}