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