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