1{ lib }:
2
3let
4 inherit (lib)
5 any
6 filterAttrs
7 foldl
8 hasInfix
9 isFunction
10 isList
11 isString
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 removeFunctions = a: filterAttrs (_: v: !isFunction v) a;
46 in a: b: removeFunctions a == removeFunctions b;
47
48 /**
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`.
51
52 :::{.warning}
53 This attribute is considered experimental and is subject to change.
54 :::
55 */
56 flakeExposed = import ./flake-systems.nix { };
57
58 # Elaborate a `localSystem` or `crossSystem` so that it contains everything
59 # necessary.
60 #
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'; }
66 else args';
67
68 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
69 rust = args.rust or args.rustc or {};
70
71 final = {
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";
83 # Derived meta-data
84 useLLVM = final.isFreeBSD;
85
86 libc =
87 /**/ if final.isDarwin then "libSystem"
88 else if final.isMinGW then "msvcrt"
89 else if final.isWasi then "wasilibc"
90 else if final.isRedox then "relibc"
91 else if final.isMusl then "musl"
92 else if final.isUClibc then "uclibc"
93 else if final.isAndroid then "bionic"
94 else if final.isLinux /* default */ then "glibc"
95 else if final.isFreeBSD then "fblibc"
96 else if final.isNetBSD then "nblibc"
97 else if final.isAvr then "avrlibc"
98 else if final.isGhcjs then null
99 else if final.isNone then "newlib"
100 # TODO(@Ericson2314) think more about other operating systems
101 else "native/impure";
102 # Choose what linker we wish to use by default. Someday we might also
103 # choose the C compiler, runtime library, C++ standard library, etc. in
104 # this way, nice and orthogonally, and deprecate `useLLVM`. But due to
105 # the monolithic GCC build we cannot actually make those choices
106 # independently, so we are just doing `linker` and keeping `useLLVM` for
107 # now.
108 linker =
109 /**/ if final.useLLVM or false then "lld"
110 else if final.isDarwin then "cctools"
111 # "bfd" and "gold" both come from GNU binutils. The existence of Gold
112 # is why we use the more obscure "bfd" and not "binutils" for this
113 # choice.
114 else "bfd";
115 # The standard lib directory name that non-nixpkgs binaries distributed
116 # for this platform normally assume.
117 libDir = if final.isLinux then
118 if final.isx86_64 || final.isMips64 || final.isPower64
119 then "lib64"
120 else "lib"
121 else null;
122 extensions = optionalAttrs final.hasSharedLibraries {
123 sharedLibrary =
124 if final.isDarwin then ".dylib"
125 else if final.isWindows then ".dll"
126 else ".so";
127 } // {
128 staticLibrary =
129 /**/ if final.isWindows then ".lib"
130 else ".a";
131 library =
132 /**/ if final.isStatic then final.extensions.staticLibrary
133 else final.extensions.sharedLibrary;
134 executable =
135 /**/ if final.isWindows then ".exe"
136 else "";
137 };
138 # Misc boolean options
139 useAndroidPrebuilt = false;
140 useiOSPrebuilt = false;
141
142 # Output from uname
143 uname = {
144 # uname -s
145 system = {
146 linux = "Linux";
147 windows = "Windows";
148 darwin = "Darwin";
149 netbsd = "NetBSD";
150 freebsd = "FreeBSD";
151 openbsd = "OpenBSD";
152 wasi = "Wasi";
153 redox = "Redox";
154 genode = "Genode";
155 }.${final.parsed.kernel.name} or null;
156
157 # uname -m
158 processor =
159 if final.isPower64
160 then "ppc64${optionalString final.isLittleEndian "le"}"
161 else if final.isPower
162 then "ppc${optionalString final.isLittleEndian "le"}"
163 else if final.isMips64
164 then "mips64" # endianness is *not* included on mips64
165 else final.parsed.cpu.name;
166
167 # uname -r
168 release = null;
169 };
170
171 # It is important that hasSharedLibraries==false when the platform has no
172 # dynamic library loader. Various tools (including the gcc build system)
173 # have knowledge of which platforms are incapable of dynamic linking, and
174 # will still build on/for those platforms with --enable-shared, but simply
175 # omit any `.so` build products such as libgcc_s.so. When that happens,
176 # it causes hard-to-troubleshoot build failures.
177 hasSharedLibraries = with final;
178 (isAndroid || isGnu || isMusl # Linux (allows multiple libcs)
179 || isDarwin || isSunOS || isOpenBSD || isFreeBSD || isNetBSD # BSDs
180 || isCygwin || isMinGW # Windows
181 ) && !isStatic;
182
183 # The difference between `isStatic` and `hasSharedLibraries` is mainly the
184 # addition of the `staticMarker` (see make-derivation.nix). Some
185 # platforms, like embedded machines without a libc (e.g. arm-none-eabi)
186 # don't support dynamic linking, but don't get the `staticMarker`.
187 # `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
188 # has the `staticMarker`.
189 isStatic = final.isWasm || final.isRedox;
190
191 # Just a guess, based on `system`
192 inherit
193 ({
194 linux-kernel = args.linux-kernel or {};
195 gcc = args.gcc or {};
196 } // platforms.select final)
197 linux-kernel gcc;
198
199 # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
200 rustc = args.rustc or {};
201
202 linuxArch =
203 if final.isAarch32 then "arm"
204 else if final.isAarch64 then "arm64"
205 else if final.isx86_32 then "i386"
206 else if final.isx86_64 then "x86_64"
207 # linux kernel does not distinguish microblaze/microblazeel
208 else if final.isMicroBlaze then "microblaze"
209 else if final.isMips32 then "mips"
210 else if final.isMips64 then "mips" # linux kernel does not distinguish mips32/mips64
211 else if final.isPower then "powerpc"
212 else if final.isRiscV then "riscv"
213 else if final.isS390 then "s390"
214 else if final.isLoongArch64 then "loongarch"
215 else final.parsed.cpu.name;
216
217 # https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106
218 ubootArch =
219 if final.isx86_32 then "x86" # not i386
220 else if final.isMips64 then "mips64" # uboot *does* distinguish between mips32/mips64
221 else final.linuxArch; # other cases appear to agree with linuxArch
222
223 qemuArch =
224 if final.isAarch32 then "arm"
225 else if final.isS390 && !final.isS390x then null
226 else if final.isx86_64 then "x86_64"
227 else if final.isx86 then "i386"
228 else if final.isMips64n32 then "mipsn32${optionalString final.isLittleEndian "el"}"
229 else if final.isMips64 then "mips64${optionalString final.isLittleEndian "el"}"
230 else final.uname.processor;
231
232 # Name used by UEFI for architectures.
233 efiArch =
234 if final.isx86_32 then "ia32"
235 else if final.isx86_64 then "x64"
236 else if final.isAarch32 then "arm"
237 else if final.isAarch64 then "aa64"
238 else final.parsed.cpu.name;
239
240 darwinArch = {
241 armv7a = "armv7";
242 aarch64 = "arm64";
243 }.${final.parsed.cpu.name} or final.parsed.cpu.name;
244
245 darwinPlatform =
246 if final.isMacOS then "macos"
247 else if final.isiOS then "ios"
248 else null;
249 # The canonical name for this attribute is darwinSdkVersion, but some
250 # platforms define the old name "sdkVer".
251 darwinSdkVersion = final.sdkVer or (if final.isAarch64 then "11.0" else "10.12");
252 darwinMinVersion = final.darwinSdkVersion;
253 darwinMinVersionVariable =
254 if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET"
255 else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET"
256 else null;
257 } // (
258 let
259 selectEmulator = pkgs:
260 let
261 qemu-user = pkgs.qemu.override {
262 smartcardSupport = false;
263 spiceSupport = false;
264 openGLSupport = false;
265 virglSupport = false;
266 vncSupport = false;
267 gtkSupport = false;
268 sdlSupport = false;
269 alsaSupport = false;
270 pulseSupport = false;
271 pipewireSupport = false;
272 jackSupport = false;
273 smbdSupport = false;
274 seccompSupport = false;
275 tpmSupport = false;
276 capstoneSupport = false;
277 enableDocs = false;
278 hostCpuTargets = [ "${final.qemuArch}-linux-user" ];
279 };
280 wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
281 in
282 if pkgs.stdenv.hostPlatform.canExecute final
283 then "${pkgs.runtimeShell} -c '\"$@\"' --"
284 else if final.isWindows
285 then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}"
286 else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null
287 then "${qemu-user}/bin/qemu-${final.qemuArch}"
288 else if final.isWasi
289 then "${pkgs.wasmtime}/bin/wasmtime"
290 else if final.isMmix
291 then "${pkgs.mmixware}/bin/mmix"
292 else null;
293 in {
294 emulatorAvailable = pkgs: (selectEmulator pkgs) != null;
295
296 emulator = pkgs:
297 if (final.emulatorAvailable pkgs)
298 then selectEmulator pkgs
299 else throw "Don't know how to run ${final.config} executables.";
300
301 }) // mapAttrs (n: v: v final.parsed) inspect.predicates
302 // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
303 // args // {
304 rust = rust // {
305 # Once args.rustc.platform.target-family is deprecated and
306 # removed, there will no longer be any need to modify any
307 # values from args.rust.platform, so we can drop all the
308 # "args ? rust" etc. checks, and merge args.rust.platform in
309 # /after/.
310 platform = rust.platform or {} // {
311 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
312 arch =
313 /**/ if rust ? platform then rust.platform.arch
314 else if final.isAarch32 then "arm"
315 else if final.isMips64 then "mips64" # never add "el" suffix
316 else if final.isPower64 then "powerpc64" # never add "le" suffix
317 else final.parsed.cpu.name;
318
319 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
320 os =
321 /**/ if rust ? platform then rust.platform.os or "none"
322 else if final.isDarwin then "macos"
323 else final.parsed.kernel.name;
324
325 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
326 target-family =
327 /**/ if args ? rust.platform.target-family then args.rust.platform.target-family
328 else if args ? rustc.platform.target-family
329 then
330 (
331 # Since https://github.com/rust-lang/rust/pull/84072
332 # `target-family` is a list instead of single value.
333 let
334 f = args.rustc.platform.target-family;
335 in
336 if isList f then f else [ f ]
337 )
338 else optional final.isUnix "unix"
339 ++ optional final.isWindows "windows";
340
341 # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
342 vendor = let
343 inherit (final.parsed) vendor;
344 in rust.platform.vendor or {
345 "w64" = "pc";
346 }.${vendor.name} or vendor.name;
347 };
348
349 # The name of the rust target, even if it is custom. Adjustments are
350 # because rust has slightly different naming conventions than we do.
351 rustcTarget = let
352 inherit (final.parsed) cpu kernel abi;
353 cpu_ = rust.platform.arch or {
354 "armv7a" = "armv7";
355 "armv7l" = "armv7";
356 "armv6l" = "arm";
357 "armv5tel" = "armv5te";
358 "riscv64" = "riscv64gc";
359 }.${cpu.name} or cpu.name;
360 vendor_ = final.rust.platform.vendor;
361 # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
362 in args.rust.rustcTarget or args.rustc.config
363 or "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}";
364
365 # The name of the rust target if it is standard, or the json file
366 # containing the custom target spec.
367 rustcTargetSpec = rust.rustcTargetSpec or (
368 /**/ if rust ? platform
369 then builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform)
370 else final.rust.rustcTarget);
371
372 # The name of the rust target if it is standard, or the
373 # basename of the file containing the custom target spec,
374 # without the .json extension.
375 #
376 # This is the name used by Cargo for target subdirectories.
377 cargoShortTarget =
378 removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
379
380 # When used as part of an environment variable name, triples are
381 # uppercased and have all hyphens replaced by underscores:
382 #
383 # https://github.com/rust-lang/cargo/pull/9169
384 # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
385 cargoEnvVarTarget =
386 replaceStrings ["-"] ["_"]
387 (toUpper final.rust.cargoShortTarget);
388
389 # True if the target is no_std
390 # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
391 isNoStdTarget =
392 any (t: hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"];
393 };
394 };
395 in assert final.useAndroidPrebuilt -> final.isAndroid;
396 assert foldl
397 (pass: { assertion, message }:
398 if assertion final
399 then pass
400 else throw message)
401 true
402 (final.parsed.abi.assertions or []);
403 final;
404
405in
406
407# Everything in this attrset is the public interface of the file.
408{
409 inherit
410 architectures
411 doubles
412 elaborate
413 equals
414 examples
415 flakeExposed
416 inspect
417 parse
418 platforms
419 ;
420}