From ca4beaaf1cf89deb630987361fa824c785c894f3 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 20 Jul 2025 12:34:42 +0200 Subject: [PATCH 1/3] lib: Add splice structure utilities --- lib/customisation.nix | 121 ++++++++++++++++++++++++++++++++++++++++++ lib/default.nix | 3 ++ lib/tests/misc.nix | 78 +++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) diff --git a/lib/customisation.nix b/lib/customisation.nix index 16f248cd1b20..a20b352dfdf4 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -863,4 +863,125 @@ rec { transformDrv ; }; + + /** + Removes a prefix from the attribute names of a set of splices. + + # Inputs + + `prefix` + : The prefix to remove from splice attribute names + + `splices` + : A set of splices with prefixed names + + # Type + + ``` + renameSplicesFrom :: String -> AttrSet -> AttrSet + ``` + + # Examples + + :::{.example} + ## `lib.customisation.renameSplicesFrom` usage example + + ```nix + renameSplicesFrom "pkgs" { pkgsBuildBuild = ...; pkgsBuildHost = ...; ... } + => { buildBuild = ...; buildHost = ...; ... } + ``` + ::: + */ + renameSplicesFrom = prefix: x: { + buildBuild = x."${prefix}BuildBuild"; + buildHost = x."${prefix}BuildHost"; + buildTarget = x."${prefix}BuildTarget"; + hostHost = x."${prefix}HostHost"; + hostTarget = x."${prefix}HostTarget"; + targetTarget = x."${prefix}TargetTarget"; + }; + + /** + Adds a prefix to the attribute names of a set of splices. + + # Inputs + + `prefix` + : The prefix to add to splice attribute names + + `splices` + : A set of splices to be prefixed + + # Type + + ``` + renameSplicesTo :: String -> AttrSet -> AttrSet + ``` + + # Examples + + :::{.example} + ## `lib.customisation.renameSplicesTo` usage example + + ```nix + renameSplicesTo "self" { buildBuild = ...; buildHost = ...; ... } + => { selfBuildBuild = ...; selfBuildHost = ...; ... } + ``` + ::: + */ + renameSplicesTo = prefix: x: { + "${prefix}BuildBuild" = x.buildBuild; + "${prefix}BuildHost" = x.buildHost; + "${prefix}BuildTarget" = x.buildTarget; + "${prefix}HostHost" = x.hostHost; + "${prefix}HostTarget" = x.hostTarget; + "${prefix}TargetTarget" = x.targetTarget; + }; + + /** + Takes a function and applies it pointwise to each splice. + + # Inputs + + `f` + : Function to apply to each splice value + + `splices` + : A set of splices to transform + + # Type + + ``` + mapSplices :: (a -> b) -> AttrSet -> AttrSet + ``` + + # Examples + + :::{.example} + ## `lib.customisation.mapSplices` usage example + + ```nix + mapSplices (x: x * 10) { buildBuild = 1; buildHost = 2; ... } + => { buildBuild = 10; buildHost = 20; ... } + ``` + ::: + */ + mapSplices = + f: + { + buildBuild, + buildHost, + buildTarget, + hostHost, + hostTarget, + targetTarget, + }: + { + buildBuild = f buildBuild; + buildHost = f buildHost; + buildTarget = f buildTarget; + hostHost = f hostHost; + hostTarget = f hostTarget; + targetTarget = f targetTarget; + }; } diff --git a/lib/default.nix b/lib/default.nix index 009140f23047..9b9f8bcba1e8 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -398,6 +398,9 @@ let makeScopeWithSplicing makeScopeWithSplicing' extendMkDerivation + renameSplicesFrom + renameSplicesTo + mapSplices ; inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; inherit (self.generators) mkLuaInline; diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index bb5603de75bd..4f321d2658e3 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -4432,4 +4432,82 @@ runTests { expected = "/non-existent/this/does/not/exist/for/real/please-dont-mess-with-your-local-fs/default.nix"; }; + # Tests for splicing utilities + + testRenameSplicesFrom = { + expr = lib.renameSplicesFrom "pkgs" { + pkgsBuildBuild = "dummy-build-build"; + pkgsBuildHost = "dummy-build-host"; + pkgsBuildTarget = "dummy-build-target"; + pkgsHostHost = "dummy-host-host"; + pkgsHostTarget = "dummy-host-target"; + pkgsTargetTarget = "dummy-target-target"; + }; + expected = { + buildBuild = "dummy-build-build"; + buildHost = "dummy-build-host"; + buildTarget = "dummy-build-target"; + hostHost = "dummy-host-host"; + hostTarget = "dummy-host-target"; + targetTarget = "dummy-target-target"; + }; + }; + + testRenameSplicesTo = { + expr = lib.renameSplicesTo "self" { + buildBuild = "dummy-build-build"; + buildHost = "dummy-build-host"; + buildTarget = "dummy-build-target"; + hostHost = "dummy-host-host"; + hostTarget = "dummy-host-target"; + targetTarget = "dummy-target-target"; + }; + expected = { + selfBuildBuild = "dummy-build-build"; + selfBuildHost = "dummy-build-host"; + selfBuildTarget = "dummy-build-target"; + selfHostHost = "dummy-host-host"; + selfHostTarget = "dummy-host-target"; + selfTargetTarget = "dummy-target-target"; + }; + }; + + testMapSplices = { + expr = lib.mapSplices (x: x * 10) { + buildBuild = 1; + buildHost = 2; + buildTarget = 3; + hostHost = 4; + hostTarget = 5; + targetTarget = 6; + }; + expected = { + buildBuild = 10; + buildHost = 20; + buildTarget = 30; + hostHost = 40; + hostTarget = 50; + targetTarget = 60; + }; + }; + + testMapSplicesString = { + expr = lib.mapSplices (x: "prefix-${x}") { + buildBuild = "bb"; + buildHost = "bh"; + buildTarget = "bt"; + hostHost = "hh"; + hostTarget = "ht"; + targetTarget = "tt"; + }; + expected = { + buildBuild = "prefix-bb"; + buildHost = "prefix-bh"; + buildTarget = "prefix-bt"; + hostHost = "prefix-hh"; + hostTarget = "prefix-ht"; + targetTarget = "prefix-tt"; + }; + }; + } From 7ba464154f85e44e4db3efb50008a62f58cea290 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 20 Jul 2025 12:39:20 +0200 Subject: [PATCH 2/3] lib: Introduce Cross Index concept A Cross Index, short for Cross Platform Pair Index, is the essential shape of a splice, without the invoking the more mind bending concept of adding variations of for these 6 pairings to an existing thing so that it can be switched out for something else. So the purpose of a Cross Index is to contain the result of `f`\ (which may be reified in code, or just an abstract concept): - f "build" "build" - f "build" "host" - ... Splicing on the other hand refers not just to these six variants, but to the idea of tacking them onto one of the variants. (hostTarget, I believe) Cross Indexes are a necessity for making cross compilation work, but splicing is more than necessary. --- lib/customisation.nix | 62 ++++++++++++++++++++++++++----------------- lib/default.nix | 6 ++--- lib/tests/misc.nix | 18 ++++++------- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/lib/customisation.nix b/lib/customisation.nix index a20b352dfdf4..247891a1944e 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -865,34 +865,37 @@ rec { }; /** - Removes a prefix from the attribute names of a set of splices. + Removes a prefix from the attribute names of a cross index. + + A cross index (short for "Cross Platform Pair Index") is a 6-field structure + organizing values by cross-compilation platform relationships. # Inputs `prefix` - : The prefix to remove from splice attribute names + : The prefix to remove from cross index attribute names - `splices` - : A set of splices with prefixed names + `crossIndex` + : A cross index with prefixed names # Type ``` - renameSplicesFrom :: String -> AttrSet -> AttrSet + renameCrossIndexFrom :: String -> AttrSet -> AttrSet ``` # Examples :::{.example} - ## `lib.customisation.renameSplicesFrom` usage example + ## `lib.customisation.renameCrossIndexFrom` usage example ```nix - renameSplicesFrom "pkgs" { pkgsBuildBuild = ...; pkgsBuildHost = ...; ... } + renameCrossIndexFrom "pkgs" { pkgsBuildBuild = ...; pkgsBuildHost = ...; ... } => { buildBuild = ...; buildHost = ...; ... } ``` ::: */ - renameSplicesFrom = prefix: x: { + renameCrossIndexFrom = prefix: x: { buildBuild = x."${prefix}BuildBuild"; buildHost = x."${prefix}BuildHost"; buildTarget = x."${prefix}BuildTarget"; @@ -902,34 +905,37 @@ rec { }; /** - Adds a prefix to the attribute names of a set of splices. + Adds a prefix to the attribute names of a cross index. + + A cross index (short for "Cross Platform Pair Index") is a 6-field structure + organizing values by cross-compilation platform relationships. # Inputs `prefix` - : The prefix to add to splice attribute names + : The prefix to add to cross index attribute names - `splices` - : A set of splices to be prefixed + `crossIndex` + : A cross index to be prefixed # Type ``` - renameSplicesTo :: String -> AttrSet -> AttrSet + renameCrossIndexTo :: String -> AttrSet -> AttrSet ``` # Examples :::{.example} - ## `lib.customisation.renameSplicesTo` usage example + ## `lib.customisation.renameCrossIndexTo` usage example ```nix - renameSplicesTo "self" { buildBuild = ...; buildHost = ...; ... } + renameCrossIndexTo "self" { buildBuild = ...; buildHost = ...; ... } => { selfBuildBuild = ...; selfBuildHost = ...; ... } ``` ::: */ - renameSplicesTo = prefix: x: { + renameCrossIndexTo = prefix: x: { "${prefix}BuildBuild" = x.buildBuild; "${prefix}BuildHost" = x.buildHost; "${prefix}BuildTarget" = x.buildTarget; @@ -939,34 +945,42 @@ rec { }; /** - Takes a function and applies it pointwise to each splice. + Takes a function and applies it pointwise to each field of a cross index. + + A cross index (short for "Cross Platform Pair Index") is a 6-field structure + organizing values by cross-compilation platform relationships. # Inputs `f` - : Function to apply to each splice value + : Function to apply to each cross index value - `splices` - : A set of splices to transform + `crossIndex` + : A cross index to transform # Type ``` - mapSplices :: (a -> b) -> AttrSet -> AttrSet + mapCrossIndex :: (a -> b) -> AttrSet -> AttrSet ``` # Examples :::{.example} - ## `lib.customisation.mapSplices` usage example + ## `lib.customisation.mapCrossIndex` usage example ```nix - mapSplices (x: x * 10) { buildBuild = 1; buildHost = 2; ... } + mapCrossIndex (x: x * 10) { buildBuild = 1; buildHost = 2; ... } => { buildBuild = 10; buildHost = 20; ... } ``` + + ```nix + # Extract a package from package sets + mapCrossIndex (pkgs: pkgs.hello) crossIndexedPackageSets + ``` ::: */ - mapSplices = + mapCrossIndex = f: { buildBuild, diff --git a/lib/default.nix b/lib/default.nix index 9b9f8bcba1e8..52f363037b6a 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -398,9 +398,9 @@ let makeScopeWithSplicing makeScopeWithSplicing' extendMkDerivation - renameSplicesFrom - renameSplicesTo - mapSplices + renameCrossIndexFrom + renameCrossIndexTo + mapCrossIndex ; inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; inherit (self.generators) mkLuaInline; diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 4f321d2658e3..19d3f8f1aa96 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -4432,10 +4432,10 @@ runTests { expected = "/non-existent/this/does/not/exist/for/real/please-dont-mess-with-your-local-fs/default.nix"; }; - # Tests for splicing utilities + # Tests for cross index utilities - testRenameSplicesFrom = { - expr = lib.renameSplicesFrom "pkgs" { + testRenameCrossIndexFrom = { + expr = lib.renameCrossIndexFrom "pkgs" { pkgsBuildBuild = "dummy-build-build"; pkgsBuildHost = "dummy-build-host"; pkgsBuildTarget = "dummy-build-target"; @@ -4453,8 +4453,8 @@ runTests { }; }; - testRenameSplicesTo = { - expr = lib.renameSplicesTo "self" { + testRenameCrossIndexTo = { + expr = lib.renameCrossIndexTo "self" { buildBuild = "dummy-build-build"; buildHost = "dummy-build-host"; buildTarget = "dummy-build-target"; @@ -4472,8 +4472,8 @@ runTests { }; }; - testMapSplices = { - expr = lib.mapSplices (x: x * 10) { + testMapCrossIndex = { + expr = lib.mapCrossIndex (x: x * 10) { buildBuild = 1; buildHost = 2; buildTarget = 3; @@ -4491,8 +4491,8 @@ runTests { }; }; - testMapSplicesString = { - expr = lib.mapSplices (x: "prefix-${x}") { + testMapCrossIndexString = { + expr = lib.mapCrossIndex (x: "prefix-${x}") { buildBuild = "bb"; buildHost = "bh"; buildTarget = "bt"; From f8566d22c152baea486dfd69ca5d38ef1dd581a7 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 6 Sep 2025 16:59:20 +0200 Subject: [PATCH 3/3] pkgs/splice.nix: Refactor, use cross index helpers --- pkgs/top-level/splice.nix | 65 ++++++++++----------------------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/pkgs/top-level/splice.nix b/pkgs/top-level/splice.nix index 6d3ad1174107..253cb2a6cc40 100644 --- a/pkgs/top-level/splice.nix +++ b/pkgs/top-level/splice.nix @@ -17,48 +17,30 @@ lib: pkgs: actuallySplice: let + inherit (lib.customisation) mapCrossIndex renameCrossIndexFrom; spliceReal = - { - pkgsBuildBuild, - pkgsBuildHost, - pkgsBuildTarget, - pkgsHostHost, - pkgsHostTarget, - pkgsTargetTarget, - }: + inputs: let mash = # Other pkgs sets - pkgsBuildBuild - // pkgsBuildTarget - // pkgsHostHost - // pkgsTargetTarget + inputs.buildBuild + // inputs.buildTarget + // inputs.hostHost + // inputs.targetTarget # The same pkgs sets one probably intends - // pkgsBuildHost - // pkgsHostTarget; + // inputs.buildHost + // inputs.hostTarget; merge = name: { inherit name; value = let defaultValue = mash.${name}; # `or {}` is for the non-derivation attsert splicing case, where `{}` is the identity. - valueBuildBuild = pkgsBuildBuild.${name} or { }; - valueBuildHost = pkgsBuildHost.${name} or { }; - valueBuildTarget = pkgsBuildTarget.${name} or { }; - valueHostHost = pkgsHostHost.${name} or { }; - valueHostTarget = pkgsHostTarget.${name} or { }; - valueTargetTarget = pkgsTargetTarget.${name} or { }; + value' = mapCrossIndex (x: x.${name} or { }) inputs; + augmentedValue = defaultValue // { - __spliced = - (lib.optionalAttrs (pkgsBuildBuild ? ${name}) { buildBuild = valueBuildBuild; }) - // (lib.optionalAttrs (pkgsBuildHost ? ${name}) { buildHost = valueBuildHost; }) - // (lib.optionalAttrs (pkgsBuildTarget ? ${name}) { buildTarget = valueBuildTarget; }) - // (lib.optionalAttrs (pkgsHostHost ? ${name}) { hostHost = valueHostHost; }) - // (lib.optionalAttrs (pkgsHostTarget ? ${name}) { hostTarget = valueHostTarget; }) - // (lib.optionalAttrs (pkgsTargetTarget ? ${name}) { - targetTarget = valueTargetTarget; - }); + __spliced = lib.filterAttrs (k: v: inputs.${k} ? ${name}) value'; }; # Get the set of outputs of a derivation. If one derivation fails to # evaluate we don't want to diverge the entire splice, so we fall back @@ -76,27 +58,12 @@ let # on to splice them together. if lib.isDerivation defaultValue then augmentedValue - // spliceReal { - pkgsBuildBuild = tryGetOutputs valueBuildBuild; - pkgsBuildHost = tryGetOutputs valueBuildHost; - pkgsBuildTarget = tryGetOutputs valueBuildTarget; - pkgsHostHost = tryGetOutputs valueHostHost; - pkgsHostTarget = getOutputs valueHostTarget; - pkgsTargetTarget = tryGetOutputs valueTargetTarget; - # Just recur on plain attrsets - } + // spliceReal (mapCrossIndex tryGetOutputs value' // { hostTarget = getOutputs value'.hostTarget; }) else if lib.isAttrs defaultValue then - spliceReal { - pkgsBuildBuild = valueBuildBuild; - pkgsBuildHost = valueBuildHost; - pkgsBuildTarget = valueBuildTarget; - pkgsHostHost = valueHostHost; - pkgsHostTarget = valueHostTarget; - pkgsTargetTarget = valueTargetTarget; - # Don't be fancy about non-derivations. But we could have used used - # `__functor__` for functions instead. - } + spliceReal value' else + # Don't be fancy about non-derivations. But we could have used used + # `__functor__` for functions instead. defaultValue; }; in @@ -111,7 +78,7 @@ let pkgsHostTarget, pkgsTargetTarget, }@args: - if actuallySplice then spliceReal args else pkgsHostTarget; + if actuallySplice then spliceReal (renameCrossIndexFrom "pkgs" args) else pkgsHostTarget; splicedPackages = splicePackages {