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