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