21 inherit (lib.strings) toJSON;
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; };
31 Elaborated systems contain functions, which means that they don't satisfy
32 `==` for a lack of reflexivity.
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.
39 Hence a custom equality test.
41 Note that this does not canonicalize the systems, so you'll want to make sure
42 both arguments have been `elaborate`-d.
45 let removeFunctions = a: filterAttrs (_: v: !isFunction v) a;
46 in a: b: removeFunctions a == removeFunctions b;
49 List of all Nix system doubles the nixpkgs flake will expose the package set
50 for. All systems listed here must be supported by nixpkgs as `localSystem`.
53 This attribute is considered experimental and is subject to change.
56 flakeExposed = import ./flake-systems.nix { };
58 # Elaborate a `localSystem` or `crossSystem` so that it contains everything
61 # `parsed` is inferred from args, both because there are two options with one
62 # clearly preferred, and to prevent cycles. A simpler fixed point where the RHS
63 # always just used `final.*` would fail on both counts.
64 elaborate = args': let
65 args = if isString args' then { system = args'; }
68 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
69 rust = args.rust or args.rustc or {};
72 # Prefer to parse `config` as it is strictly more informative.
73 parsed = parse.mkSystemFromString (if args ? config then args.config else args.system);
74 # Either of these can be losslessly-extracted from `parsed` iff parsing succeeds.
75 system = parse.doubleFromSystem final.parsed;
76 config = parse.tripleFromSystem final.parsed;
77 # Determine whether we can execute binaries built for the provided platform.
78 canExecute = platform:
79 final.isAndroid == platform.isAndroid &&
80 parse.isCompatible final.parsed.cpu platform.parsed.cpu
81 && final.parsed.kernel == platform.parsed.kernel;
82 isCompatible = _: throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details";
84 useLLVM = final.isFreeBSD || final.isOpenBSD;
87 /**/ if final.isDarwin then "libSystem"
88 else if final.isMinGW then "msvcrt"
89 else if final.isWasi then "wasilibc"
90 else if final.isWasm && !final.isWasi then null
91 else if final.isRedox then "relibc"
92 else if final.isMusl then "musl"
93 else if final.isUClibc then "uclibc"
94 else if final.isAndroid then "bionic"
95 else if final.isLinux /* default */ then "glibc"
96 else if final.isFreeBSD then "fblibc"
97 else if final.isOpenBSD then "oblibc"
98 else if final.isNetBSD then "nblibc"
99 else if final.isAvr then "avrlibc"
100 else if final.isGhcjs then null
101 else if final.isNone then "newlib"
102 # TODO(@Ericson2314) think more about other operating systems
103 else "native/impure";
104 # Choose what linker we wish to use by default. Someday we might also
105 # choose the C compiler, runtime library, C++ standard library, etc. in
106 # this way, nice and orthogonally, and deprecate `useLLVM`. But due to
107 # the monolithic GCC build we cannot actually make those choices
108 # independently, so we are just doing `linker` and keeping `useLLVM` for
111 /**/ if final.useLLVM or false then "lld"
112 else if final.isDarwin then "cctools"
113 # "bfd" and "gold" both come from GNU binutils. The existence of Gold
114 # is why we use the more obscure "bfd" and not "binutils" for this
117 # The standard lib directory name that non-nixpkgs binaries distributed
118 # for this platform normally assume.
119 libDir = if final.isLinux then
120 if final.isx86_64 || final.isMips64 || final.isPower64
124 extensions = optionalAttrs final.hasSharedLibraries {
126 if final.isDarwin then ".dylib"
127 else if final.isWindows then ".dll"
131 /**/ if final.isWindows then ".lib"
134 /**/ if final.isStatic then final.extensions.staticLibrary
135 else final.extensions.sharedLibrary;
137 /**/ if final.isWindows then ".exe"
140 # Misc boolean options
141 useAndroidPrebuilt = false;
142 useiOSPrebuilt = false;
157 }.${final.parsed.kernel.name} or null;
162 then "ppc64${optionalString final.isLittleEndian "le"}"
163 else if final.isPower
164 then "ppc${optionalString final.isLittleEndian "le"}"
165 else if final.isMips64
166 then "mips64" # endianness is *not* included on mips64
167 else final.parsed.cpu.name;
173 # It is important that hasSharedLibraries==false when the platform has no
174 # dynamic library loader. Various tools (including the gcc build system)
175 # have knowledge of which platforms are incapable of dynamic linking, and
176 # will still build on/for those platforms with --enable-shared, but simply
177 # omit any `.so` build products such as libgcc_s.so. When that happens,
178 # it causes hard-to-troubleshoot build failures.
179 hasSharedLibraries = with final;
180 (isAndroid || isGnu || isMusl # Linux (allows multiple libcs)
181 || isDarwin || isSunOS || isOpenBSD || isFreeBSD || isNetBSD # BSDs
182 || isCygwin || isMinGW || isWindows # Windows
186 # The difference between `isStatic` and `hasSharedLibraries` is mainly the
187 # addition of the `staticMarker` (see make-derivation.nix). Some
188 # platforms, like embedded machines without a libc (e.g. arm-none-eabi)
189 # don't support dynamic linking, but don't get the `staticMarker`.
190 # `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
191 # has the `staticMarker`.
192 isStatic = final.isWasi || final.isRedox;
194 # Just a guess, based on `system`
197 linux-kernel = args.linux-kernel or {};
198 gcc = args.gcc or {};
199 } // platforms.select final)
202 # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
203 rustc = args.rustc or {};
206 if final.isAarch32 then "arm"
207 else if final.isAarch64 then "arm64"
208 else if final.isx86_32 then "i386"
209 else if final.isx86_64 then "x86_64"
210 # linux kernel does not distinguish microblaze/microblazeel
211 else if final.isMicroBlaze then "microblaze"
212 else if final.isMips32 then "mips"
213 else if final.isMips64 then "mips" # linux kernel does not distinguish mips32/mips64
214 else if final.isPower then "powerpc"
215 else if final.isRiscV then "riscv"
216 else if final.isS390 then "s390"
217 else if final.isLoongArch64 then "loongarch"
218 else final.parsed.cpu.name;
220 # https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106
222 if final.isx86_32 then "x86" # not i386
223 else if final.isMips64 then "mips64" # uboot *does* distinguish between mips32/mips64
224 else final.linuxArch; # other cases appear to agree with linuxArch
227 if final.isAarch32 then "arm"
228 else if final.isS390 && !final.isS390x then null
229 else if final.isx86_64 then "x86_64"
230 else if final.isx86 then "i386"
231 else if final.isMips64n32 then "mipsn32${optionalString final.isLittleEndian "el"}"
232 else if final.isMips64 then "mips64${optionalString final.isLittleEndian "el"}"
233 else final.uname.processor;
235 # Name used by UEFI for architectures.
237 if final.isx86_32 then "ia32"
238 else if final.isx86_64 then "x64"
239 else if final.isAarch32 then "arm"
240 else if final.isAarch64 then "aa64"
241 else final.parsed.cpu.name;
246 }.${final.parsed.cpu.name} or final.parsed.cpu.name;
249 if final.isMacOS then "macos"
250 else if final.isiOS then "ios"
252 # The canonical name for this attribute is darwinSdkVersion, but some
253 # platforms define the old name "sdkVer".
254 darwinSdkVersion = final.sdkVer or (if final.isAarch64 then "11.0" else "10.12");
255 darwinMinVersion = final.darwinSdkVersion;
256 darwinMinVersionVariable =
257 if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET"
258 else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET"
261 # Remove before 25.05
263 if (args ? sdkVer && !args ? androidSdkVersion) then
264 throw "For android `sdkVer` has been renamed to `androidSdkVersion`"
265 else if (args ? androidSdkVersion) then
266 args.androidSdkVersion
270 if (args ? ndkVer && !args ? androidNdkVersion) then
271 throw "For android `ndkVer` has been renamed to `androidNdkVersion`"
272 else if (args ? androidSdkVersion) then
273 args.androidNdkVersion
278 selectEmulator = pkgs:
280 wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
282 # Note: we guarantee that the return value is either `null` or a path
283 # to an emulator program. That is, if an emulator requires additional
284 # arguments, a wrapper should be used.
285 if pkgs.stdenv.hostPlatform.canExecute final
286 then "${pkgs.execline}/bin/exec"
287 else if final.isWindows
288 then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
289 else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null
290 then "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}"
292 then "${pkgs.wasmtime}/bin/wasmtime"
294 then "${pkgs.mmixware}/bin/mmix"
297 emulatorAvailable = pkgs: (selectEmulator pkgs) != null;
299 # whether final.emulator pkgs.pkgsStatic works
300 staticEmulatorAvailable = pkgs: final.emulatorAvailable pkgs
301 && (final.isLinux || final.isWasi || final.isMmix);
304 if (final.emulatorAvailable pkgs)
305 then selectEmulator pkgs
306 else throw "Don't know how to run ${final.config} executables.";
308 }) // mapAttrs (n: v: v final.parsed) inspect.predicates
309 // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
312 # Once args.rustc.platform.target-family is deprecated and
313 # removed, there will no longer be any need to modify any
314 # values from args.rust.platform, so we can drop all the
315 # "args ? rust" etc. checks, and merge args.rust.platform in
317 platform = rust.platform or {} // {
318 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
320 /**/ if rust ? platform then rust.platform.arch
321 else if final.isAarch32 then "arm"
322 else if final.isMips64 then "mips64" # never add "el" suffix
323 else if final.isPower64 then "powerpc64" # never add "le" suffix
324 else final.parsed.cpu.name;
326 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
328 /**/ if rust ? platform then rust.platform.os or "none"
329 else if final.isDarwin then "macos"
330 else if final.isWasm && !final.isWasi then "unknown" # Needed for {wasm32,wasm64}-unknown-unknown.
331 else final.parsed.kernel.name;
333 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
335 /**/ if args ? rust.platform.target-family then args.rust.platform.target-family
336 else if args ? rustc.platform.target-family
339 # Since https://github.com/rust-lang/rust/pull/84072
340 # `target-family` is a list instead of single value.
342 f = args.rustc.platform.target-family;
344 if isList f then f else [ f ]
346 else optional final.isUnix "unix"
347 ++ optional final.isWindows "windows"
348 ++ optional final.isWasm "wasm";
350 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
352 inherit (final.parsed) vendor;
353 in rust.platform.vendor or {
355 }.${vendor.name} or vendor.name;
358 # The name of the rust target, even if it is custom. Adjustments are
359 # because rust has slightly different naming conventions than we do.
361 inherit (final.parsed) cpu kernel abi;
362 cpu_ = rust.platform.arch or {
366 "armv5tel" = "armv5te";
367 "riscv32" = "riscv32gc";
368 "riscv64" = "riscv64gc";
369 }.${cpu.name} or cpu.name;
370 vendor_ = final.rust.platform.vendor;
371 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
373 args.rust.rustcTarget or
374 args.rustc.config or (
375 # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`.
376 # We cannot know which subversion does the user want, and
377 # currently use WASI 0.1 as default for compatibility. Custom
378 # users can set `rust.rustcTarget` to override it.
380 then "${cpu_}-wasip1"
381 else "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}"
384 # The name of the rust target if it is standard, or the json file
385 # containing the custom target spec.
386 rustcTargetSpec = rust.rustcTargetSpec or (
387 /**/ if rust ? platform
388 then builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform)
389 else final.rust.rustcTarget);
391 # The name of the rust target if it is standard, or the
392 # basename of the file containing the custom target spec,
393 # without the .json extension.
395 # This is the name used by Cargo for target subdirectories.
397 removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
399 # When used as part of an environment variable name, triples are
400 # uppercased and have all hyphens replaced by underscores:
402 # https://github.com/rust-lang/cargo/pull/9169
403 # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
405 replaceStrings ["-"] ["_"]
406 (toUpper final.rust.cargoShortTarget);
408 # True if the target is no_std
409 # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
411 any (t: hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"];
414 in assert final.useAndroidPrebuilt -> final.isAndroid;
416 (pass: { assertion, message }:
421 (final.parsed.abi.assertions or []);
426 # Everything in this attrset is the public interface of the file.