From 550a1b8eb5775a7df5c0cde123d24905d99fc423 Mon Sep 17 00:00:00 2001 From: dramforever Date: Wed, 25 Feb 2026 10:57:39 +0800 Subject: [PATCH 1/3] lib.systems: Refactor rust.platform generation In preparation for a future commit, refactor rust.platform somewhat. No functional changes intended. Most of the diff here is whitespace changes. --- lib/systems/default.nix | 130 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/lib/systems/default.nix b/lib/systems/default.nix index 534af380738d..972f78c17375 100644 --- a/lib/systems/default.nix +++ b/lib/systems/default.nix @@ -417,62 +417,65 @@ let // args // { rust = rust // { - # Once args.rustc.platform.target-family is deprecated and - # removed, there will no longer be any need to modify any - # values from args.rust.platform, so we can drop all the - # "args ? rust" etc. checks, and merge args.rust.platform in - # /after/. - platform = rust.platform or { } // { - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch - arch = - if rust ? platform then - rust.platform.arch - else if final.isAarch32 then - "arm" - else if final.isMips64 then - "mips64" # never add "el" suffix - else if final.isPower64 then - "powerpc64" # never add "le" suffix - else - final.parsed.cpu.name; + platform = + rust.platform or { } - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os - os = - if rust ? platform then - rust.platform.os or "none" - else if final.isDarwin then - "macos" - else if final.isWasm && !final.isWasi then - "unknown" # Needed for {wasm32,wasm64}-unknown-unknown. - else - final.parsed.kernel.name; + # Once args.rustc.platform.target-family is deprecated and + # removed, there will no longer be any need to modify any + # values from args.rust.platform, so we can drop all the + # "args ? rust" etc. checks, and merge args.rust.platform in + # /after/. + // { + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch + arch = + if rust ? platform then + rust.platform.arch + else if final.isAarch32 then + "arm" + else if final.isMips64 then + "mips64" # never add "el" suffix + else if final.isPower64 then + "powerpc64" # never add "le" suffix + else + final.parsed.cpu.name; - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family - target-family = - if args ? rust.platform.target-family then - args.rust.platform.target-family - else if args ? rustc.platform.target-family then - ( - # Since https://github.com/rust-lang/rust/pull/84072 - # `target-family` is a list instead of single value. - let - f = args.rustc.platform.target-family; - in - if isList f then f else [ f ] - ) - else - optional final.isUnix "unix" ++ optional final.isWindows "windows" ++ optional final.isWasm "wasm"; + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os + os = + if rust ? platform then + rust.platform.os or "none" + else if final.isDarwin then + "macos" + else if final.isWasm && !final.isWasi then + "unknown" # Needed for {wasm32,wasm64}-unknown-unknown. + else + final.parsed.kernel.name; - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor - vendor = - let - inherit (final.parsed) vendor; - in - rust.platform.vendor or { - "w64" = "pc"; - } - .${vendor.name} or vendor.name; - }; + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family + target-family = + if args ? rust.platform.target-family then + args.rust.platform.target-family + else if args ? rustc.platform.target-family then + ( + # Since https://github.com/rust-lang/rust/pull/84072 + # `target-family` is a list instead of single value. + let + f = args.rustc.platform.target-family; + in + if isList f then f else [ f ] + ) + else + optional final.isUnix "unix" ++ optional final.isWindows "windows" ++ optional final.isWasm "wasm"; + + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor + vendor = + let + inherit (final.parsed) vendor; + in + rust.platform.vendor or { + "w64" = "pc"; + } + .${vendor.name} or vendor.name; + }; # The name of the rust target, even if it is custom. Adjustments are # because rust has slightly different naming conventions than we do. @@ -497,18 +500,19 @@ let "gnu" else abi.name; + + inferred = + # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. + # We cannot know which subversion does the user want, and + # currently use WASI 0.1 as default for compatibility. Custom + # users can set `rust.rustcTarget` to override it. + if final.isWasi then + "${cpu_}-wasip1" + else + "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi_}"}"; in # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. - args.rust.rustcTarget or args.rustc.config or ( - # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. - # We cannot know which subversion does the user want, and - # currently use WASI 0.1 as default for compatibility. Custom - # users can set `rust.rustcTarget` to override it. - if final.isWasi then - "${cpu_}-wasip1" - else - "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi_}"}" - ); + args.rust.rustcTarget or args.rustc.config or inferred; # The name of the rust target if it is standard, or the json file # containing the custom target spec. From 60ac986e4973ad43734fff355496de5dc3a8d508 Mon Sep 17 00:00:00 2001 From: dramforever Date: Wed, 25 Feb 2026 11:09:26 +0800 Subject: [PATCH 2/3] lib.systems: rust: Make rustcTargetSpec the primary entrypoint Make rustcTargetSpec the primary entrypoint for setting a custom target, and wire up all the other stuff so they are hopefully as working and as broken as before. In particular, to specify a custom target, the user now just specifies rust.rustcTargetSpec. rust.platform and rust.cargoShortTarget are populated from rust.rustcTargetSpec now. In addition, rust.rustcTarget defaults to rust.cargoShortTarget. (rust.rustcTarget and rust.cargoShortTarget really should always be the same, but I think we can deal with that later). This allows the user to more easily control the basename of rust.rustcTargetSpec by passing e.g. "${./rust}/mips64el_mips3-unknown-linux-gnuabi64.json", which allows cc-rs and in turn std to work. --- lib/systems/default.nix | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/systems/default.nix b/lib/systems/default.nix index 972f78c17375..b4f6c13feafa 100644 --- a/lib/systems/default.nix +++ b/lib/systems/default.nix @@ -418,7 +418,12 @@ let // { rust = rust // { platform = - rust.platform or { } + rust.platform or ( + if lib.hasSuffix ".json" (rust.rustcTargetSpec or "") then + lib.importJSON rust.rustcTargetSpec + else + { } + ) # Once args.rustc.platform.target-family is deprecated and # removed, there will no longer be any need to modify any @@ -477,9 +482,10 @@ let .${vendor.name} or vendor.name; }; - # The name of the rust target, even if it is custom. Adjustments are - # because rust has slightly different naming conventions than we do. - rustcTarget = + # The name of the rust target if it is standard, or the json file + # containing the custom target spec. Adjustments are because rust has + # slightly different naming conventions than we do. + rustcTargetSpec = let inherit (final.parsed) cpu kernel abi; cpu_ = @@ -502,28 +508,28 @@ let abi.name; inferred = - # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. - # We cannot know which subversion does the user want, and - # currently use WASI 0.1 as default for compatibility. Custom - # users can set `rust.rustcTarget` to override it. if final.isWasi then + # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. + # We cannot know which subversion does the user want, and + # currently use WASI 0.1 as default for compatibility. Custom + # users can set `rust.rustcTargetSpec` to override it. "${cpu_}-wasip1" else "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi_}"}"; in # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. - args.rust.rustcTarget or args.rustc.config or inferred; - - # The name of the rust target if it is standard, or the json file - # containing the custom target spec. - rustcTargetSpec = - rust.rustcTargetSpec or ( + args.rust.rustcTargetSpec or args.rustc.config or ( if rust ? platform then - builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform) + # TODO: This breaks cc-rs and thus std support, so maybe remove support? + builtins.toFile (rust.rustcTarget or inferred + ".json") (toJSON rust.platform) else - final.rust.rustcTarget + args.rust.rustcTarget or inferred ); + # Do not use rustcTarget. Use rustcTargetSpec or cargoShortTarget. + # TODO: Remove all in-tree usages, and deprecate + rustcTarget = rust.rustcTarget or final.rust.cargoShortTarget; + # The name of the rust target if it is standard, or the # basename of the file containing the custom target spec, # without the .json extension. From 8920a833278a89713bf36b88a3aaf4095d513e96 Mon Sep 17 00:00:00 2001 From: dramforever Date: Sat, 27 Sep 2025 22:16:22 +0800 Subject: [PATCH 3/3] doc/rust: Use rustcTargetSpec everywhere Cargo uses the basename of a JSON target specification in various places to refer to targets, cc-rs parses the basename to grab target information. There may be other examples where the basename is relevant. Instead of fighting these existing conventions, let's just recommend users provide a rustcTargetSpec, either as a standard name or as a custom JSON file with the right basename --- doc/languages-frameworks/rust.section.md | 33 ++++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/doc/languages-frameworks/rust.section.md b/doc/languages-frameworks/rust.section.md index 66f821ea21a3..04b43b5c272f 100644 --- a/doc/languages-frameworks/rust.section.md +++ b/doc/languages-frameworks/rust.section.md @@ -254,7 +254,7 @@ By default, it takes the `stdenv.hostPlatform.config` and replaces components where they are known to differ. But there are ways to customize the argument: - To choose a different target by name, define - `stdenv.hostPlatform.rust.rustcTarget` as that name (a string), and that + `stdenv.hostPlatform.rust.rustcTargetSpec` as that name (a string), and that name will be used instead. For example: @@ -262,7 +262,7 @@ where they are known to differ. But there are ways to customize the argument: ```nix import { crossSystem = (import ).systems.examples.armhf-embedded // { - rust.rustcTarget = "thumbv7em-none-eabi"; + rust.rustcTargetSpec = "thumbv7em-none-eabi"; }; } ``` @@ -274,22 +274,24 @@ where they are known to differ. But there are ways to customize the argument: ``` - To pass a completely custom target, define - `stdenv.hostPlatform.rust.rustcTarget` with its name, and - `stdenv.hostPlatform.rust.platform` with the value. The value will be - serialized to JSON in a file called - `${stdenv.hostPlatform.rust.rustcTarget}.json`, and the path of that file - will be used instead. + `stdenv.hostPlatform.rust.rustcTargetSpec` with the path to the custom + target specification JSON file. + + Note that some tools like Cargo and some crates like `cc` make use of the + file name of the target JSON. Therefore, do not use + `./path/to/target-spec.json` directly, because it will be renamed by Nix. + Instead, place it a directory and use `"${./path/to/dir}/target-spec.json"`. + The directory should contain only this one file, to avoid unrelated changes + causing unnecessary rebuilds. For example: ```nix import { - crossSystem = (import ).systems.examples.armhf-embedded // { - rust.rustcTarget = "thumb-crazy"; - rust.platform = { - foo = ""; - bar = ""; - }; + crossSystem = { + config = "mips64el-unknown-linux-gnuabi64"; + # gcc = ...; # Config for C compiler omitted + rust.rustcTargetSpec = "${./rust}/mips64el_mips3-unknown-linux-gnuabi64.json"; }; } ``` @@ -297,12 +299,9 @@ where they are known to differ. But there are ways to customize the argument: will result in: ```shell - --target /nix/store/asdfasdfsadf-thumb-crazy.json # contains {"foo":"","bar":""} + --target /nix/store/...-rust/mips64el_mips3-unknown-linux-gnuabi64.json ``` -Note that currently custom targets aren't compiled with `std`, so `cargo test` -will fail. This can be ignored by adding `doCheck = false;` to your derivation. - ### Running package tests {#running-package-tests} When using `buildRustPackage`, the `checkPhase` is enabled by default and runs