diff --git a/checks/cell-aware-cross-inputs/Cargo.lock b/checks/cell-aware-cross-inputs/Cargo.lock new file mode 100644 index 0000000..7b4b86e --- /dev/null +++ b/checks/cell-aware-cross-inputs/Cargo.lock @@ -0,0 +1,149 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cc" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cell-aware-cross-inputs" +version = "0.1.0" +dependencies = [ + "openssl", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/checks/cell-aware-cross-inputs/Cargo.toml b/checks/cell-aware-cross-inputs/Cargo.toml new file mode 100644 index 0000000..d1c7e79 --- /dev/null +++ b/checks/cell-aware-cross-inputs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cell-aware-cross-inputs" +version = "0.1.0" +edition = "2021" + +[dependencies] +openssl = "0.10" + +[workspace] diff --git a/checks/cell-aware-cross-inputs/default.nix b/checks/cell-aware-cross-inputs/default.nix new file mode 100644 index 0000000..df3b1fd --- /dev/null +++ b/checks/cell-aware-cross-inputs/default.nix @@ -0,0 +1,73 @@ +{ pkgs, flakeboxLib }: +let + inherit (pkgs) lib; + + crossPkgs = pkgs.pkgsCross.aarch64-multiplatform; + expectedPkgConfig = crossPkgs.pkgsBuildHost.pkg-config; + expectedOpenSsl = crossPkgs.pkgsHostTarget.openssl; + targetOpenSsl = lib.getLib expectedOpenSsl; + toolchains = lib.getAttrs [ + "default" + "aarch64-linux" + ] (flakeboxLib.mkStdToolchains { }); + + outputs = + (flakeboxLib.craneMultiBuild { + inherit toolchains; + profiles = [ "release" ]; + argsFor = + { + packages, + toolchainName, + ... + }: + lib.optionalAttrs (toolchainName == "aarch64-linux") ( + assert toString packages.nativeBuildInputs.pkg-config == toString expectedPkgConfig; + assert toString packages.buildInputs.openssl == toString expectedOpenSsl; + { + buildInputs = [ packages.buildInputs.openssl ]; + nativeBuildInputs = [ packages.nativeBuildInputs.pkg-config ]; + } + ); + }) + (craneLib: { + package = craneLib.buildPackage { + pname = "cell-aware-cross-inputs"; + version = "0.1.0"; + src = craneLib.cleanCargoSource ./.; + strictDeps = true; + }; + }); + + crossPackage = outputs.aarch64-linux.release.package; +in +pkgs.runCommand "cell-aware-cross-inputs-check" + { + nativeBuildInputs = [ + pkgs.binutils + pkgs.file + ]; + } + '' + binary=${crossPackage}/bin/cell-aware-cross-inputs + ${expectedPkgConfig}/bin/aarch64-unknown-linux-gnu-pkg-config --version > pkg-config-version.txt + file "$binary" > binary-file.txt + readelf -h "$binary" > binary-elf-header.txt + readelf -d "$binary" > binary-dynamic.txt + + grep -Fq "ELF 64-bit LSB pie executable, ARM aarch64" binary-file.txt + grep -Eq "Machine:[[:space:]]+AArch64" binary-elf-header.txt + grep -Eq "Shared library: \\[libssl\\.so" binary-dynamic.txt + grep -F "(RUNPATH)" binary-dynamic.txt > binary-runpath.txt + grep -Fq "${targetOpenSsl}/lib" binary-runpath.txt + + target_libssl=$(readlink -f ${targetOpenSsl}/lib/libssl.so) + file "$target_libssl" > dependency-file.txt + readelf -h "$target_libssl" > dependency-elf-header.txt + + grep -Fq "ELF 64-bit LSB shared object, ARM aarch64" dependency-file.txt + grep -Eq "Machine:[[:space:]]+AArch64" dependency-elf-header.txt + + mkdir "$out" + cp ./*.txt "$out/" + '' diff --git a/checks/cell-aware-cross-inputs/src/main.rs b/checks/cell-aware-cross-inputs/src/main.rs new file mode 100644 index 0000000..fa0f5a4 --- /dev/null +++ b/checks/cell-aware-cross-inputs/src/main.rs @@ -0,0 +1,5 @@ +use openssl::ssl::{SslConnector, SslMethod}; + +fn main() { + let _connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); +} diff --git a/checks/crane-multi-build-eval-fixture.nix b/checks/crane-multi-build-eval-fixture.nix new file mode 100644 index 0000000..4f8ee20 --- /dev/null +++ b/checks/crane-multi-build-eval-fixture.nix @@ -0,0 +1,84 @@ +# `builtins.tryEval` discards error text, and pure Nix evaluation cannot expose +# how often it forced a function. This minimal Crane-shaped adapter exists only +# so the check can capture those two `craneMultiBuild` contracts from a nested +# evaluator. All package-role and merge behavior uses the real Crane library in +# `crane-multi-build-tests.nix`; do not reproduce it here. +{ + craneMultiBuildPath, + nixpkgsLibPath, + scenario, +}: +let + lib = import nixpkgsLibPath; + + mkCraneLib = args: { + inherit args; + overrideArgs = newArgs: mkCraneLib (args // newArgs); + mapWithProfiles = outputsFn: profiles: lib.genAttrs profiles (_: outputsFn (mkCraneLib args)); + }; + + baseToolchain = { + craneLib = mkCraneLib { }; + }; + unsupportedToolchain = + cellKind: + baseToolchain + // { + dependencyContext = { + packages.depsBuildBuild = { }; + unavailablePackages.buildInputs = "this ${cellKind} cell has no truthful target Nixpkgs package universe"; + }; + }; + + craneMultiBuild = import craneMultiBuildPath { + craneMkLib = _: throw "unused craneMkLib"; + mkStdToolchains = _: throw "unused mkStdToolchains"; + inherit lib; + pkgs = { }; + }; + + cell = + if scenario == "custom" then + "custom" + else if scenario == "wasm" then + "wasm32-unknown" + else + "aarch64-android"; + cellToolchain = + if scenario == "custom" then + baseToolchain + else if scenario == "wasm" then + unsupportedToolchain "wasm32-unknown-unknown" + else + unsupportedToolchain "Android"; + + outputs = + (craneMultiBuild { + profiles = [ + "dev" + "release" + ]; + toolchains = { + default = baseToolchain; + ${if scenario == "scope" then "cross" else cell} = + if scenario == "scope" then baseToolchain else cellToolchain; + }; + argsFor = + { toolchainName, packages, ... }: + if scenario == "scope" then + builtins.trace "argsFor invocation: ${toolchainName}" { + marker = toolchainName; + } + else + { + marker = packages.buildInputs.openssl; + }; + }) + (craneLib: { + probe = craneLib.args.marker; + }); +in +if scenario == "scope" then + builtins.deepSeq outputs true +else + builtins.seq outputs.${cell}.dev.probe true diff --git a/checks/crane-multi-build-tests.nix b/checks/crane-multi-build-tests.nix new file mode 100644 index 0000000..b721310 --- /dev/null +++ b/checks/crane-multi-build-tests.nix @@ -0,0 +1,291 @@ +{ pkgs, flakeboxLib }: +let + inherit (pkgs) lib; + + assertEq = + name: actual: expected: + lib.assertMsg ( + actual == expected + ) "${name}: expected ${builtins.toJSON expected}, got ${builtins.toJSON actual}"; + + stdToolchains = flakeboxLib.mkStdToolchains { }; + isLinux = pkgs.stdenv.isLinux; + hasAndroid = stdToolchains ? aarch64-android; + crossPkgs = pkgs.pkgsCross.aarch64-multiplatform; + + roleToolchains = { + inherit (stdToolchains) default; + } + // lib.optionalAttrs isLinux { + inherit (stdToolchains) aarch64-linux; + }; + roleOutputs = + (flakeboxLib.craneMultiBuild { + profiles = [ + "dev" + "release" + ]; + toolchains = roleToolchains; + argsFor = + { + capabilities, + toolchainName, + packages, + }: + { + resolvedForCell = toolchainName; + resolvedTargetCapability = capabilities.targetBuildInputs; + resolvedBuildInput = packages.buildInputs.openssl; + resolvedNativeBuildInput = packages.nativeBuildInputs.pkg-config; + resolvedDepsBuildBuild = packages.depsBuildBuild.hello; + }; + }) + (craneLib: { + probe = { + inherit (craneLib.args) + resolvedBuildInput + resolvedDepsBuildBuild + resolvedForCell + resolvedNativeBuildInput + resolvedTargetCapability + ; + profile = craneLib.cargoProfile; + }; + }); + + customToolchains.default = builtins.removeAttrs stdToolchains.default [ "dependencyContext" ]; + customWithoutRoleAccess = + (flakeboxLib.craneMultiBuild { + toolchains = customToolchains; + profiles = [ "dev" ]; + argsFor = { toolchainName, ... }: { + resolvedForCell = toolchainName; + }; + }) + (craneLib: { + probe = craneLib.args.resolvedForCell; + }); + customWithRoleAccess = builtins.tryEval ( + let + outputs = + (flakeboxLib.craneMultiBuild { + toolchains = customToolchains; + profiles = [ "dev" ]; + argsFor = { packages, ... }: { + resolvedBuildInput = packages.buildInputs.openssl; + }; + }) + (craneLib: { + probe = craneLib.args.resolvedBuildInput; + }); + in + builtins.seq outputs.probe true + ); + + unsupportedToolchainNames = [ + "wasm32-unknown" + ] + ++ lib.optionals hasAndroid [ + "aarch64-android" + ]; + unsupportedToolchains = lib.getAttrs ( + [ + "default" + ] + ++ unsupportedToolchainNames + ) stdToolchains; + capabilityOutputs = + (flakeboxLib.craneMultiBuild { + toolchains = unsupportedToolchains; + profiles = [ "dev" ]; + argsFor = + { + capabilities, + packages, + ... + }: + { + resolvedTargetCapability = capabilities.targetBuildInputs; + resolvedOptionalBuildInput = + if capabilities.targetBuildInputs then packages.buildInputs.openssl else null; + }; + }) + (craneLib: { + probe = { + inherit (craneLib.args) resolvedOptionalBuildInput resolvedTargetCapability; + }; + }); + + unsupportedRoleAccess = + toolchainName: + let + toolchain = stdToolchains.${toolchainName}; + context = toolchain.dependencyContext; + outputs = + (flakeboxLib.craneMultiBuild { + toolchains = { + inherit (stdToolchains) default; + ${toolchainName} = toolchain; + }; + profiles = [ "dev" ]; + argsFor = { packages, ... }: { + resolvedBuildInput = packages.buildInputs.openssl; + }; + }) + (craneLib: { + probe = craneLib.args.resolvedBuildInput; + }); + in + assert context ? packages; + assert !(context.packages ? buildInputs); + builtins.tryEval (builtins.seq outputs.${toolchainName}.dev.probe true); + + precedenceToolchains.default = stdToolchains.default // { + craneLib = stdToolchains.default.craneLib.overrideArgs { + precedenceProbe = "built-in"; + }; + }; + precedenceOutputs = + (flakeboxLib.craneMultiBuild { + toolchains = precedenceToolchains; + profiles = [ "dev" ]; + argsFor = _: { + precedenceProbe = "argsFor"; + }; + }) + (craneLib: { + precedenceProbe = { + afterArgsFor = craneLib.args.precedenceProbe; + directCall = + (craneLib.mkCargoDerivation { + pname = "crane-multi-build-precedence-probe"; + version = "0"; + src = pkgs.emptyDirectory; + cargoArtifacts = null; + buildPhaseCargoCommand = "true"; + precedenceProbe = "direct-call"; + }).precedenceProbe; + }; + }); + + assertions = [ + (assertEq "native buildInputs role" (toString roleOutputs.probe.resolvedBuildInput) ( + toString pkgs.pkgsHostTarget.openssl + )) + (assertEq "native nativeBuildInputs role" (toString roleOutputs.probe.resolvedNativeBuildInput) ( + toString pkgs.pkgsBuildHost.pkg-config + )) + (assertEq "native depsBuildBuild role" (toString roleOutputs.probe.resolvedDepsBuildBuild) ( + toString pkgs.pkgsBuildBuild.hello + )) + (assertEq "native target capability" roleOutputs.probe.resolvedTargetCapability true) + (assertEq "argsFor native cell" roleOutputs.probe.resolvedForCell "default") + (assertEq "argsFor value reused for dev profile" roleOutputs.dev.probe.resolvedForCell "default") + (assertEq "argsFor value reused for release profile" roleOutputs.release.probe.resolvedForCell + "default" + ) + (assertEq "dev profile remains independent" roleOutputs.dev.probe.profile "dev") + (assertEq "release profile remains independent" roleOutputs.release.probe.profile "release") + (assertEq "custom toolchain without role access" customWithoutRoleAccess.probe "default") + (assertEq "custom toolchain role access fails lazily" customWithRoleAccess.success false) + (assertEq "wasm32 target capability" + capabilityOutputs.wasm32-unknown.dev.probe.resolvedTargetCapability + false + ) + (assertEq "wasm32 capability filters target input" + capabilityOutputs.wasm32-unknown.dev.probe.resolvedOptionalBuildInput + null + ) + (assertEq "wasm32-unknown-unknown target input fails" + (unsupportedRoleAccess "wasm32-unknown").success + false + ) + (assertEq "built-in to argsFor precedence" precedenceOutputs.precedenceProbe.afterArgsFor "argsFor") + (assertEq "argsFor to direct-call precedence" precedenceOutputs.precedenceProbe.directCall + "direct-call" + ) + ] + ++ lib.optionals isLinux [ + (assertEq "AArch64 buildInputs role" + (toString roleOutputs.aarch64-linux.dev.probe.resolvedBuildInput) + (toString crossPkgs.pkgsHostTarget.openssl) + ) + (assertEq "AArch64 nativeBuildInputs role" + (toString roleOutputs.aarch64-linux.dev.probe.resolvedNativeBuildInput) + (toString crossPkgs.pkgsBuildHost.pkg-config) + ) + (assertEq "AArch64 depsBuildBuild role" + (toString roleOutputs.aarch64-linux.dev.probe.resolvedDepsBuildBuild) + (toString crossPkgs.pkgsBuildBuild.hello) + ) + (assertEq "AArch64 target capability" roleOutputs.aarch64-linux.dev.probe.resolvedTargetCapability + true + ) + (assertEq "argsFor cross cell" roleOutputs.aarch64-linux.dev.probe.resolvedForCell "aarch64-linux") + ] + ++ lib.optionals hasAndroid [ + (assertEq "Android target capability" + capabilityOutputs.aarch64-android.dev.probe.resolvedTargetCapability + false + ) + (assertEq "Android capability filters target input" + capabilityOutputs.aarch64-android.dev.probe.resolvedOptionalBuildInput + null + ) + (assertEq "Android target input fails" (unsupportedRoleAccess "aarch64-android").success false) + ]; +in +assert lib.all (assertion: assertion) assertions; +pkgs.runCommand "crane-multi-build-tests" + { + nativeBuildInputs = [ (lib.getBin pkgs.nix) ]; + } + '' + fixture=${./crane-multi-build-eval-fixture.nix} + crane_multi_build=${../lib/craneMultiBuild.nix} + nixpkgs_lib=${pkgs.path}/lib + + # Give the nested evaluator writable state without connecting to the host + # daemon. It only evaluates the self-contained expression above. + export HOME="$TMPDIR/home" + export NIX_STATE_DIR="$TMPDIR/nix-state" + export NIX_LOG_DIR="$TMPDIR/nix-log" + export NIX_CONF_DIR="$TMPDIR/nix-conf" + mkdir -p "$HOME" "$NIX_CONF_DIR" + + eval_scenario() { + local scenario=$1 + nix-instantiate --eval --strict --expr \ + "import $fixture { + craneMultiBuildPath = $crane_multi_build; + nixpkgsLibPath = $nixpkgs_lib; + scenario = \"$scenario\"; + }" + } + + if ! eval_scenario scope >/dev/null 2>scope-trace; then + cat scope-trace >&2 + exit 1 + fi + + test "$(grep -Fc 'argsFor invocation: default' scope-trace)" -eq 1 + test "$(grep -Fc 'argsFor invocation: cross' scope-trace)" -eq 1 + + check_diagnostic() { + local scenario=$1 + local cell=$2 + + if eval_scenario "$scenario" >"$scenario-out" 2>"$scenario-err"; then + echo "expected $scenario evaluation to fail" >&2 + exit 1 + fi + + grep -Fq "toolchain/cell \`$cell\` cannot provide \`packages.buildInputs\`" "$scenario-err" + } + + check_diagnostic custom custom + check_diagnostic wasm wasm32-unknown + check_diagnostic android aarch64-android + + touch "$out" + '' diff --git a/checks/default.nix b/checks/default.nix index a4bd129..d91d6dd 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -21,11 +21,13 @@ onlyDrvs ( customStdenv = callPackage ./custom-stdenv { }; nextest = callPackage ./nextest { }; mergeArgsTests = callPackage ./mergeArgs-tests.nix { }; + craneMultiBuildTests = callPackage ./crane-multi-build-tests.nix { }; cargoCrapModule = callPackage ./cargo-crap-module.nix { inherit mkLib; }; gitHooks = callPackage ./git-hooks.nix { inherit mkLib; }; } // lib.optionalAttrs (pkgs.stdenv.buildPlatform.system == "x86_64-linux") { crossLinkerCompression = callPackage ./cross-linker-compression.nix { }; + cellAwareCrossInputs = callPackage ./cell-aware-cross-inputs { }; } ) ) diff --git a/docs/flakeboxLib.md b/docs/flakeboxLib.md index fafaa9d..0830888 100644 --- a/docs/flakeboxLib.md +++ b/docs/flakeboxLib.md @@ -92,6 +92,76 @@ supported toolchains. A nested set with following keys: See [Tutorial: Flakebox in a New Project](./building-new-project.md) for more information. +### Cell-aware common build arguments + +`craneMultiBuild` accepts an optional `argsFor` resolver for build arguments +that depend on the selected toolchain/target cell: + +```nix +(flakeboxLib.craneMultiBuild { + argsFor = + { + toolchainName, + packages, + capabilities, + }: + pkgs.lib.optionalAttrs capabilities.targetBuildInputs { + buildInputs = [ packages.buildInputs.openssl ]; + nativeBuildInputs = [ packages.nativeBuildInputs.pkg-config ]; + }; +}) (craneLib: { + app = craneLib.buildPackage { + inherit src; + }; +}) +``` + +Flakebox evaluates `argsFor` once for each selected cell, before it maps that +cell over Cargo profiles. The resolver receives: + +* `toolchainName`: the cell's key in `toolchains`. +* `packages.buildInputs`: the package scope for target libraries. +* `packages.nativeBuildInputs`: the package scope for build-host tools that + configure or emit artifacts for the target. +* `packages.depsBuildBuild`: the package scope for build-host tools that emit + build-host artifacts. +* `capabilities.targetBuildInputs`: whether the cell has a truthful Nixpkgs + target package universe. + +For ordinary Nixpkgs cross cells these roles map to `pkgsHostTarget`, +`pkgsBuildHost`, and `pkgsBuildBuild`, respectively. Native cells expose the +equivalent roles from the native package set. + +Android, `wasm32-unknown-unknown`, and other SDK or Rust-only cells do not +pretend that native or WASI packages are target packages. Check +`capabilities.targetBuildInputs` before selecting target dependencies. +Accessing an unavailable role fails with the toolchain/cell name and the +missing role. Custom toolchains may omit `dependencyContext` as long as their +`argsFor` result does not access `packages`; role access then fails lazily. +Custom toolchains that support role-scoped packages can provide: + +```nix +dependencyContext = { + packages = { + depsBuildBuild = crossPkgs.pkgsBuildBuild; + nativeBuildInputs = crossPkgs.pkgsBuildHost; + buildInputs = crossPkgs.pkgsHostTarget; + }; +}; +``` + +The merge order is: the cell's built-in target/common arguments, then the +attrset returned by `argsFor`, then arguments passed to an individual Crane +build call. All layers use the existing `overrideArgs`/`mergeArgs` behavior: +`buildInputs`, `nativeBuildInputs`, and `packages` concatenate from left to +right; `env` attrsets merge with later values winning; and every other +attribute, including `depsBuildBuild`, uses the later value. Omitting `argsFor` +preserves the existing build path. + +This API selects explicit artifacts for Flakebox's existing manual Cargo-cross +model. It does not turn the root Rust derivation into a fully spliced Nixpkgs +cross derivation. + ## `flakeboxLib.cargoCrap` `flakeboxLib.cargoCrap` exposes flakebox's packaged `cargo-crap` derivation for diff --git a/lib/craneMultiBuild.nix b/lib/craneMultiBuild.nix index 4bcc7e7..97223b5 100644 --- a/lib/craneMultiBuild.nix +++ b/lib/craneMultiBuild.nix @@ -1,8 +1,5 @@ { - mkFenixToolchain, craneMkLib, - mapWithToolchains, - mkTarget, mkStdToolchains, lib, pkgs, @@ -18,11 +15,57 @@ in "release" ], craneLib ? craneLib', + argsFor ? null, }: outputsFn: let + dependencyRoles = [ + "buildInputs" + "nativeBuildInputs" + "depsBuildBuild" + ]; + + craneLibFor = + toolchainName: toolchain: + let + dependencyContext = toolchain.dependencyContext or { }; + missingRole = + role: + let + reason = + dependencyContext.unavailablePackages.${role} + or "the toolchain does not define a dependency context for this role"; + in + throw "craneMultiBuild argsFor: toolchain/cell `${toolchainName}` cannot provide `packages.${role}`: ${reason}"; + packages = lib.genAttrs dependencyRoles ( + role: + if dependencyContext ? packages && builtins.hasAttr role dependencyContext.packages then + dependencyContext.packages.${role} + else + missingRole role + ); + capabilities.targetBuildInputs = + dependencyContext ? packages && dependencyContext.packages ? buildInputs; + baseCraneLib = toolchain.craneLib.overrideArgs { inherit toolchainName; }; + in + if argsFor == null then + baseCraneLib + else + baseCraneLib.overrideArgs (argsFor { + inherit + capabilities + packages + toolchainName + ; + }); + + # Resolve cell arguments here, before any profile mapping. Besides keeping + # `argsFor` profile-independent, this lets every profile reuse the same + # cell-local Crane library. + cellCraneLibs = lib.mapAttrs craneLibFor toolchains; + defaultCraneLib = cellCraneLibs.default; profilesFn = craneLib: craneLib.mapWithProfiles outputsFn profiles; in -(mapWithToolchains outputsFn { default = toolchains.default; }).default -// (mapWithToolchains profilesFn { default = toolchains.default; }).default -// (mapWithToolchains profilesFn toolchains) +outputsFn defaultCraneLib +// profilesFn defaultCraneLib +// lib.mapAttrs (_: profilesFn) cellCraneLibs diff --git a/lib/mkStdToolchains.nix b/lib/mkStdToolchains.nix index 7ffa6c3..a1c2447 100644 --- a/lib/mkStdToolchains.nix +++ b/lib/mkStdToolchains.nix @@ -20,9 +20,39 @@ }@args: let stdTargets = mkStdTargets { }; + + dependencyContext = packageSet: { + packages = { + nativeBuildInputs = packageSet.pkgsBuildHost; + buildInputs = packageSet.pkgsHostTarget; + depsBuildBuild = packageSet.pkgsBuildBuild; + }; + }; + + nativeDependencyContext = dependencyContext pkgs; + crossDependencyContext = dependencyContext; + dependencyContextFor = + packageSet: + if packageSet.stdenv.hostPlatform.config == pkgs.stdenv.buildPlatform.config then + nativeDependencyContext + else + crossDependencyContext packageSet; + unsupportedTargetDependencyContext = cellKind: { + packages.depsBuildBuild = pkgs.pkgsBuildBuild; + unavailablePackages = { + nativeBuildInputs = "this ${cellKind} cell has no truthful Nixpkgs package scope for build-host tools targeting the Cargo target"; + buildInputs = "this ${cellKind} cell has no truthful target Nixpkgs package universe"; + }; + }; + mkFenixToolchainWithContext = + dependencyContext: toolchainArgs: + mkFenixToolchain toolchainArgs + // { + inherit dependencyContext; + }; in { - default = mkFenixToolchain ( + default = mkFenixToolchainWithContext nativeDependencyContext ( args // { targets = { @@ -31,7 +61,7 @@ in } ); - nightly = mkFenixToolchain ( + nightly = mkFenixToolchainWithContext nativeDependencyContext ( args // { targets = { @@ -43,16 +73,18 @@ in } // lib.optionalAttrs pkgs.stdenv.isLinux { - aarch64-linux = mkFenixToolchain ( - args - // { - defaultTarget = "aarch64-unknown-linux-gnu"; - targets = { - default = stdTargets.aarch64-linux; - }; - } - ); - x86_64-linux = mkFenixToolchain ( + aarch64-linux = + mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.aarch64-multiplatform) + ( + args + // { + defaultTarget = "aarch64-unknown-linux-gnu"; + targets = { + default = stdTargets.aarch64-linux; + }; + } + ); + x86_64-linux = mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.gnu64) ( args // { defaultTarget = "x86_64-unknown-linux-gnu"; @@ -61,7 +93,7 @@ in }; } ); - i686-linux = mkFenixToolchain ( + i686-linux = mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.gnu32) ( args // { defaultTarget = "i686-unknown-linux-gnu"; @@ -70,7 +102,7 @@ in }; } ); - riscv64-linux = mkFenixToolchain ( + riscv64-linux = mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.riscv64) ( args // { defaultTarget = "riscv64gc-unknown-linux-gnu"; @@ -82,38 +114,40 @@ in } // { - wasm32-unknown = mkFenixToolchain ( - args - // { - defaultTarget = "wasm32-unknown-unknown"; - targets = { - wasm32-unknown = stdTargets.wasm32-unknown; - }; - } - ); + wasm32-unknown = + mkFenixToolchainWithContext (unsupportedTargetDependencyContext "wasm32-unknown-unknown") + ( + args + // { + defaultTarget = "wasm32-unknown-unknown"; + targets = { + wasm32-unknown = stdTargets.wasm32-unknown; + }; + } + ); } // lib.optionalAttrs ((args ? androidSdk) || (builtins.hasAttr system android-nixpkgs.sdk)) { - aarch64-android = mkFenixToolchain { + aarch64-android = mkFenixToolchainWithContext (unsupportedTargetDependencyContext "Android") { defaultTarget = "aarch64-linux-android"; targets = { aarch64-android = stdTargets.aarch64-android; }; }; - x86_64-android = mkFenixToolchain { + x86_64-android = mkFenixToolchainWithContext (unsupportedTargetDependencyContext "Android") { defaultTarget = "x86_64-linux-android"; targets = { x86_64-android = stdTargets.x86_64-android; }; }; - i686-android = mkFenixToolchain { + i686-android = mkFenixToolchainWithContext (unsupportedTargetDependencyContext "Android") { defaultTarget = "i686-linux-android"; targets = { i686-android = stdTargets.i686-android; }; }; - armv7-android = mkFenixToolchain { + armv7-android = mkFenixToolchainWithContext (unsupportedTargetDependencyContext "Android") { defaultTarget = "armv7-linux-androideabi"; targets = { armv7-android = stdTargets.armv7-android; @@ -122,7 +156,7 @@ in } // lib.optionalAttrs (pkgs.stdenv.buildPlatform.config == "aarch64-apple-darwin") { - aarch64-darwin = mkFenixToolchain ( + aarch64-darwin = mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.aarch64-darwin) ( args // { defaultTarget = "aarch64-apple-darwin"; @@ -134,7 +168,7 @@ in } // lib.optionalAttrs (pkgs.stdenv.buildPlatform.config == "x86_64-apple-darwin") { - x86_64-darwin = mkFenixToolchain ( + x86_64-darwin = mkFenixToolchainWithContext (dependencyContextFor pkgs.pkgsCross.x86_64-darwin) ( args // { defaultTarget = "x86_64-apple-darwin";