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