1{ lib }:
2
3let
4 inherit (lib)
5 any
6 filterAttrs
7 foldl
8 hasInfix
9 isAttrs
10 isFunction
11 isList
12 mapAttrs
13 optional
14 optionalAttrs
15 optionalString
16 removeSuffix
17 replaceStrings
18 toUpper
19 ;
20
21 inherit (lib.strings) toJSON;
22
23 doubles = import ./doubles.nix { inherit lib; };
24 parse = import ./parse.nix { inherit lib; };
25 inspect = import ./inspect.nix { inherit lib; };
26 platforms = import ./platforms.nix { inherit lib; };
27 examples = import ./examples.nix { inherit lib; };
28 architectures = import ./architectures.nix { inherit lib; };
29
30 /**
31 Elaborated systems contain functions, which means that they don't satisfy
32 `==` for a lack of reflexivity.
33
34 They might *appear* to satisfy `==` reflexivity when the same exact value is
35 compared to itself, because object identity is used as an "optimization";
36 compare the value with a reconstruction of itself, e.g. with `f == a: f a`,
37 or perhaps calling `elaborate` twice, and one will see reflexivity fail as described.
38
39 Hence a custom equality test.
40
41 Note that this does not canonicalize the systems, so you'll want to make sure
42 both arguments have been `elaborate`-d.
43 */
44 equals =
45 let
46 removeFunctions = a: filterAttrs (_: v: !isFunction v) a;
47 in
48 a: b: removeFunctions a == removeFunctions b;
49
50 /**
51 List of all Nix system doubles the nixpkgs flake will expose the package set
52 for. All systems listed here must be supported by nixpkgs as `localSystem`.
53
54 :::{.warning}
55 This attribute is considered experimental and is subject to change.
56 :::
57 */
58 flakeExposed = import ./flake-systems.nix { };
59
60 # Turn localSystem or crossSystem, which could be system-string or attrset, into
61 # attrset.
62 systemToAttrs =
63 systemOrArgs: if isAttrs systemOrArgs then systemOrArgs else { system = systemOrArgs; };
64
65 # Elaborate a `localSystem` or `crossSystem` so that it contains everything
66 # necessary.
67 #
68 # `parsed` is inferred from args, both because there are two options with one
69 # clearly preferred, and to prevent cycles. A simpler fixed point where the RHS
70 # always just used `final.*` would fail on both counts.
71 elaborate =
72 systemOrArgs:
73 let
74 allArgs = systemToAttrs systemOrArgs;
75
76 # Those two will always be derived from "config", if given, so they should NOT
77 # be overridden further down with "// args".
78 args = builtins.removeAttrs allArgs [
79 "parsed"
80 "system"
81 ];
82
83 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
84 rust = args.rust or args.rustc or { };
85
86 final =
87 {
88 # Prefer to parse `config` as it is strictly more informative.
89 parsed = parse.mkSystemFromString (args.config or allArgs.system);
90 # This can be losslessly-extracted from `parsed` iff parsing succeeds.
91 system = parse.doubleFromSystem final.parsed;
92 # TODO: This currently can't be losslessly-extracted from `parsed`, for example
93 # because of -mingw32.
94 config = parse.tripleFromSystem final.parsed;
95 # Determine whether we can execute binaries built for the provided platform.
96 canExecute =
97 platform:
98 final.isAndroid == platform.isAndroid
99 && parse.isCompatible final.parsed.cpu platform.parsed.cpu
100 && final.parsed.kernel == platform.parsed.kernel;
101 isCompatible =
102 _:
103 throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details";
104 # Derived meta-data
105 useLLVM = final.isFreeBSD || final.isOpenBSD;
106
107 libc =
108 if final.isDarwin then
109 "libSystem"
110 else if final.isMinGW then
111 "msvcrt"
112 else if final.isWasi then
113 "wasilibc"
114 else if final.isWasm && !final.isWasi then
115 null
116 else if final.isRedox then
117 "relibc"
118 else if final.isMusl then
119 "musl"
120 else if final.isUClibc then
121 "uclibc"
122 else if final.isAndroid then
123 "bionic"
124 else if
125 final.isLinux # default
126 then
127 "glibc"
128 else if final.isFreeBSD then
129 "fblibc"
130 else if final.isOpenBSD then
131 "oblibc"
132 else if final.isNetBSD then
133 "nblibc"
134 else if final.isAvr then
135 "avrlibc"
136 else if final.isGhcjs then
137 null
138 else if final.isNone then
139 "newlib"
140 # TODO(@Ericson2314) think more about other operating systems
141 else
142 "native/impure";
143 # Choose what linker we wish to use by default. Someday we might also
144 # choose the C compiler, runtime library, C++ standard library, etc. in
145 # this way, nice and orthogonally, and deprecate `useLLVM`. But due to
146 # the monolithic GCC build we cannot actually make those choices
147 # independently, so we are just doing `linker` and keeping `useLLVM` for
148 # now.
149 linker =
150 if final.useLLVM or false then
151 "lld"
152 else if final.isDarwin then
153 "cctools"
154 # "bfd" and "gold" both come from GNU binutils. The existence of Gold
155 # is why we use the more obscure "bfd" and not "binutils" for this
156 # choice.
157 else
158 "bfd";
159 # The standard lib directory name that non-nixpkgs binaries distributed
160 # for this platform normally assume.
161 libDir =
162 if final.isLinux then
163 if final.isx86_64 || final.isMips64 || final.isPower64 then "lib64" else "lib"
164 else
165 null;
166 extensions =
167 optionalAttrs final.hasSharedLibraries {
168 sharedLibrary =
169 if final.isDarwin then
170 ".dylib"
171 else if final.isWindows then
172 ".dll"
173 else
174 ".so";
175 }
176 // {
177 staticLibrary = if final.isWindows then ".lib" else ".a";
178 library = if final.isStatic then final.extensions.staticLibrary else final.extensions.sharedLibrary;
179 executable = if final.isWindows then ".exe" else "";
180 };
181 # Misc boolean options
182 useAndroidPrebuilt = false;
183 useiOSPrebuilt = false;
184
185 # Output from uname
186 uname = {
187 # uname -s
188 system =
189 {
190 linux = "Linux";
191 windows = "Windows";
192 darwin = "Darwin";
193 netbsd = "NetBSD";
194 freebsd = "FreeBSD";
195 openbsd = "OpenBSD";
196 wasi = "Wasi";
197 redox = "Redox";
198 genode = "Genode";
199 }
200 .${final.parsed.kernel.name} or null;
201
202 # uname -m
203 processor =
204 if final.isPower64 then
205 "ppc64${optionalString final.isLittleEndian "le"}"
206 else if final.isPower then
207 "ppc${optionalString final.isLittleEndian "le"}"
208 else if final.isMips64 then
209 "mips64" # endianness is *not* included on mips64
210 else if final.isDarwin then
211 final.darwinArch
212 else
213 final.parsed.cpu.name;
214
215 # uname -r
216 release = null;
217 };
218
219 # It is important that hasSharedLibraries==false when the platform has no
220 # dynamic library loader. Various tools (including the gcc build system)
221 # have knowledge of which platforms are incapable of dynamic linking, and
222 # will still build on/for those platforms with --enable-shared, but simply
223 # omit any `.so` build products such as libgcc_s.so. When that happens,
224 # it causes hard-to-troubleshoot build failures.
225 hasSharedLibraries =
226 with final;
227 (
228 isAndroid
229 || isGnu
230 || isMusl # Linux (allows multiple libcs)
231 || isDarwin
232 || isSunOS
233 || isOpenBSD
234 || isFreeBSD
235 || isNetBSD # BSDs
236 || isCygwin
237 || isMinGW
238 || isWindows # Windows
239 || isWasm # WASM
240 )
241 && !isStatic;
242
243 # The difference between `isStatic` and `hasSharedLibraries` is mainly the
244 # addition of the `staticMarker` (see make-derivation.nix). Some
245 # platforms, like embedded machines without a libc (e.g. arm-none-eabi)
246 # don't support dynamic linking, but don't get the `staticMarker`.
247 # `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
248 # has the `staticMarker`.
249 isStatic = final.isWasi || final.isRedox;
250
251 # Just a guess, based on `system`
252 inherit
253 (
254 {
255 linux-kernel = args.linux-kernel or { };
256 gcc = args.gcc or { };
257 }
258 // platforms.select final
259 )
260 linux-kernel
261 gcc
262 ;
263
264 # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
265 rustc = args.rustc or { };
266
267 linuxArch =
268 if final.isAarch32 then
269 "arm"
270 else if final.isAarch64 then
271 "arm64"
272 else if final.isx86_32 then
273 "i386"
274 else if final.isx86_64 then
275 "x86_64"
276 # linux kernel does not distinguish microblaze/microblazeel
277 else if final.isMicroBlaze then
278 "microblaze"
279 else if final.isMips32 then
280 "mips"
281 else if final.isMips64 then
282 "mips" # linux kernel does not distinguish mips32/mips64
283 else if final.isPower then
284 "powerpc"
285 else if final.isRiscV then
286 "riscv"
287 else if final.isS390 then
288 "s390"
289 else if final.isLoongArch64 then
290 "loongarch"
291 else
292 final.parsed.cpu.name;
293
294 # https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106
295 ubootArch =
296 if final.isx86_32 then
297 "x86" # not i386
298 else if final.isMips64 then
299 "mips64" # uboot *does* distinguish between mips32/mips64
300 else
301 final.linuxArch; # other cases appear to agree with linuxArch
302
303 qemuArch =
304 if final.isAarch32 then
305 "arm"
306 else if final.isAarch64 then
307 "aarch64"
308 else if final.isS390 && !final.isS390x then
309 null
310 else if final.isx86_64 then
311 "x86_64"
312 else if final.isx86 then
313 "i386"
314 else if final.isMips64n32 then
315 "mipsn32${optionalString final.isLittleEndian "el"}"
316 else if final.isMips64 then
317 "mips64${optionalString final.isLittleEndian "el"}"
318 else
319 final.uname.processor;
320
321 # Name used by UEFI for architectures.
322 efiArch =
323 if final.isx86_32 then
324 "ia32"
325 else if final.isx86_64 then
326 "x64"
327 else if final.isAarch32 then
328 "arm"
329 else if final.isAarch64 then
330 "aa64"
331 else
332 final.parsed.cpu.name;
333
334 darwinArch = parse.darwinArch final.parsed.cpu;
335
336 darwinPlatform =
337 if final.isMacOS then
338 "macos"
339 else if final.isiOS then
340 "ios"
341 else
342 null;
343 # The canonical name for this attribute is darwinSdkVersion, but some
344 # platforms define the old name "sdkVer".
345 darwinSdkVersion = final.sdkVer or "11.3";
346 darwinMinVersion = final.darwinSdkVersion;
347 darwinMinVersionVariable =
348 if final.isMacOS then
349 "MACOSX_DEPLOYMENT_TARGET"
350 else if final.isiOS then
351 "IPHONEOS_DEPLOYMENT_TARGET"
352 else
353 null;
354
355 # Handle Android SDK and NDK versions.
356 androidSdkVersion = args.androidSdkVersion or null;
357 androidNdkVersion = args.androidNdkVersion or null;
358 }
359 // (
360 let
361 selectEmulator =
362 pkgs:
363 let
364 wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
365 in
366 # Note: we guarantee that the return value is either `null` or a path
367 # to an emulator program. That is, if an emulator requires additional
368 # arguments, a wrapper should be used.
369 if pkgs.stdenv.hostPlatform.canExecute final then
370 lib.getExe (pkgs.writeShellScriptBin "exec" ''exec "$@"'')
371 else if final.isWindows then
372 "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
373 else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null then
374 "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}"
375 else if final.isWasi then
376 "${pkgs.wasmtime}/bin/wasmtime"
377 else if final.isMmix then
378 "${pkgs.mmixware}/bin/mmix"
379 else
380 null;
381 in
382 {
383 emulatorAvailable = pkgs: (selectEmulator pkgs) != null;
384
385 # whether final.emulator pkgs.pkgsStatic works
386 staticEmulatorAvailable =
387 pkgs: final.emulatorAvailable pkgs && (final.isLinux || final.isWasi || final.isMmix);
388
389 emulator =
390 pkgs:
391 if (final.emulatorAvailable pkgs) then
392 selectEmulator pkgs
393 else
394 throw "Don't know how to run ${final.config} executables.";
395
396 }
397 )
398 // mapAttrs (n: v: v final.parsed) inspect.predicates
399 // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
400 // args
401 // {
402 rust = rust // {
403 # Once args.rustc.platform.target-family is deprecated and
404 # removed, there will no longer be any need to modify any
405 # values from args.rust.platform, so we can drop all the
406 # "args ? rust" etc. checks, and merge args.rust.platform in
407 # /after/.
408 platform = rust.platform or { } // {
409 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
410 arch =
411 if rust ? platform then
412 rust.platform.arch
413 else if final.isAarch32 then
414 "arm"
415 else if final.isMips64 then
416 "mips64" # never add "el" suffix
417 else if final.isPower64 then
418 "powerpc64" # never add "le" suffix
419 else
420 final.parsed.cpu.name;
421
422 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
423 os =
424 if rust ? platform then
425 rust.platform.os or "none"
426 else if final.isDarwin then
427 "macos"
428 else if final.isWasm && !final.isWasi then
429 "unknown" # Needed for {wasm32,wasm64}-unknown-unknown.
430 else
431 final.parsed.kernel.name;
432
433 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
434 target-family =
435 if args ? rust.platform.target-family then
436 args.rust.platform.target-family
437 else if args ? rustc.platform.target-family then
438 (
439 # Since https://github.com/rust-lang/rust/pull/84072
440 # `target-family` is a list instead of single value.
441 let
442 f = args.rustc.platform.target-family;
443 in
444 if isList f then f else [ f ]
445 )
446 else
447 optional final.isUnix "unix" ++ optional final.isWindows "windows" ++ optional final.isWasm "wasm";
448
449 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
450 vendor =
451 let
452 inherit (final.parsed) vendor;
453 in
454 rust.platform.vendor or {
455 "w64" = "pc";
456 }
457 .${vendor.name} or vendor.name;
458 };
459
460 # The name of the rust target, even if it is custom. Adjustments are
461 # because rust has slightly different naming conventions than we do.
462 rustcTarget =
463 let
464 inherit (final.parsed) cpu kernel abi;
465 cpu_ =
466 rust.platform.arch or {
467 "armv7a" = "armv7";
468 "armv7l" = "armv7";
469 "armv6l" = "arm";
470 "armv5tel" = "armv5te";
471 "riscv32" = "riscv32gc";
472 "riscv64" = "riscv64gc";
473 }
474 .${cpu.name} or cpu.name;
475 vendor_ = final.rust.platform.vendor;
476 in
477 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
478 args.rust.rustcTarget or args.rustc.config or (
479 # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`.
480 # We cannot know which subversion does the user want, and
481 # currently use WASI 0.1 as default for compatibility. Custom
482 # users can set `rust.rustcTarget` to override it.
483 if final.isWasi then
484 "${cpu_}-wasip1"
485 else
486 "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}"
487 );
488
489 # The name of the rust target if it is standard, or the json file
490 # containing the custom target spec.
491 rustcTargetSpec =
492 rust.rustcTargetSpec or (
493 if rust ? platform then
494 builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform)
495 else
496 final.rust.rustcTarget
497 );
498
499 # The name of the rust target if it is standard, or the
500 # basename of the file containing the custom target spec,
501 # without the .json extension.
502 #
503 # This is the name used by Cargo for target subdirectories.
504 cargoShortTarget = removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
505
506 # When used as part of an environment variable name, triples are
507 # uppercased and have all hyphens replaced by underscores:
508 #
509 # https://github.com/rust-lang/cargo/pull/9169
510 # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
511 cargoEnvVarTarget = replaceStrings [ "-" ] [ "_" ] (toUpper final.rust.cargoShortTarget);
512
513 # True if the target is no_std
514 # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
515 isNoStdTarget = any (t: hasInfix t final.rust.rustcTarget) [
516 "-none"
517 "nvptx"
518 "switch"
519 "-uefi"
520 ];
521 };
522 }
523 // {
524 go = {
525 # See https://pkg.go.dev/internal/platform for a list of known platforms
526 GOARCH =
527 {
528 "aarch64" = "arm64";
529 "arm" = "arm";
530 "armv5tel" = "arm";
531 "armv6l" = "arm";
532 "armv7l" = "arm";
533 "i686" = "386";
534 "loongarch64" = "loong64";
535 "mips" = "mips";
536 "mips64el" = "mips64le";
537 "mipsel" = "mipsle";
538 "powerpc64" = "ppc64";
539 "powerpc64le" = "ppc64le";
540 "riscv64" = "riscv64";
541 "s390x" = "s390x";
542 "x86_64" = "amd64";
543 "wasm32" = "wasm";
544 }
545 .${final.parsed.cpu.name} or (throw "Unknown CPU variant ${final.parsed.cpu.name} by Go");
546 GOOS = if final.isWasi then "wasip1" else final.parsed.kernel.name;
547
548 # See https://go.dev/wiki/GoArm
549 GOARM = toString (lib.intersectLists [ (final.parsed.cpu.version or "") ] [ "5" "6" "7" ]);
550 };
551 };
552 in
553 assert final.useAndroidPrebuilt -> final.isAndroid;
554 assert foldl (pass: { assertion, message }: if assertion final then pass else throw message) true (
555 final.parsed.abi.assertions or [ ]
556 );
557 final;
558
559in
560
561# Everything in this attrset is the public interface of the file.
562{
563 inherit
564 architectures
565 doubles
566 elaborate
567 equals
568 examples
569 flakeExposed
570 inspect
571 parse
572 platforms
573 systemToAttrs
574 ;
575}