lib: Add splicing utilities (#426889)

This commit is contained in:
Robert Hensing
2025-10-30 21:22:54 +00:00
committed by GitHub
4 changed files with 232 additions and 49 deletions

View File

@@ -864,4 +864,139 @@ rec {
transformDrv transformDrv
; ;
}; };
/**
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 cross index attribute names
`crossIndex`
: A cross index with prefixed names
# Type
```
renameCrossIndexFrom :: String -> AttrSet -> AttrSet
```
# Examples
:::{.example}
## `lib.customisation.renameCrossIndexFrom` usage example
```nix
renameCrossIndexFrom "pkgs" { pkgsBuildBuild = ...; pkgsBuildHost = ...; ... }
=> { buildBuild = ...; buildHost = ...; ... }
```
:::
*/
renameCrossIndexFrom = 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 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 cross index attribute names
`crossIndex`
: A cross index to be prefixed
# Type
```
renameCrossIndexTo :: String -> AttrSet -> AttrSet
```
# Examples
:::{.example}
## `lib.customisation.renameCrossIndexTo` usage example
```nix
renameCrossIndexTo "self" { buildBuild = ...; buildHost = ...; ... }
=> { selfBuildBuild = ...; selfBuildHost = ...; ... }
```
:::
*/
renameCrossIndexTo = 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 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 cross index value
`crossIndex`
: A cross index to transform
# Type
```
mapCrossIndex :: (a -> b) -> AttrSet -> AttrSet
```
# Examples
:::{.example}
## `lib.customisation.mapCrossIndex` usage example
```nix
mapCrossIndex (x: x * 10) { buildBuild = 1; buildHost = 2; ... }
=> { buildBuild = 10; buildHost = 20; ... }
```
```nix
# Extract a package from package sets
mapCrossIndex (pkgs: pkgs.hello) crossIndexedPackageSets
```
:::
*/
mapCrossIndex =
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;
};
} }

View File

@@ -397,6 +397,9 @@ let
makeScopeWithSplicing makeScopeWithSplicing
makeScopeWithSplicing' makeScopeWithSplicing'
extendMkDerivation extendMkDerivation
renameCrossIndexFrom
renameCrossIndexTo
mapCrossIndex
; ;
inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate;
inherit (self.generators) mkLuaInline; inherit (self.generators) mkLuaInline;

View File

@@ -4741,4 +4741,82 @@ runTests {
expected = "/non-existent/this/does/not/exist/for/real/please-dont-mess-with-your-local-fs/default.nix"; expected = "/non-existent/this/does/not/exist/for/real/please-dont-mess-with-your-local-fs/default.nix";
}; };
# Tests for cross index utilities
testRenameCrossIndexFrom = {
expr = lib.renameCrossIndexFrom "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";
};
};
testRenameCrossIndexTo = {
expr = lib.renameCrossIndexTo "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";
};
};
testMapCrossIndex = {
expr = lib.mapCrossIndex (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;
};
};
testMapCrossIndexString = {
expr = lib.mapCrossIndex (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";
};
};
} }

View File

@@ -17,48 +17,30 @@
lib: pkgs: actuallySplice: lib: pkgs: actuallySplice:
let let
inherit (lib.customisation) mapCrossIndex renameCrossIndexFrom;
spliceReal = spliceReal =
{ inputs:
pkgsBuildBuild,
pkgsBuildHost,
pkgsBuildTarget,
pkgsHostHost,
pkgsHostTarget,
pkgsTargetTarget,
}:
let let
mash = mash =
# Other pkgs sets # Other pkgs sets
pkgsBuildBuild inputs.buildBuild
// pkgsBuildTarget // inputs.buildTarget
// pkgsHostHost // inputs.hostHost
// pkgsTargetTarget // inputs.targetTarget
# The same pkgs sets one probably intends # The same pkgs sets one probably intends
// pkgsBuildHost // inputs.buildHost
// pkgsHostTarget; // inputs.hostTarget;
merge = name: { merge = name: {
inherit name; inherit name;
value = value =
let let
defaultValue = mash.${name}; defaultValue = mash.${name};
# `or {}` is for the non-derivation attsert splicing case, where `{}` is the identity. # `or {}` is for the non-derivation attsert splicing case, where `{}` is the identity.
valueBuildBuild = pkgsBuildBuild.${name} or { }; value' = mapCrossIndex (x: x.${name} or { }) inputs;
valueBuildHost = pkgsBuildHost.${name} or { };
valueBuildTarget = pkgsBuildTarget.${name} or { };
valueHostHost = pkgsHostHost.${name} or { };
valueHostTarget = pkgsHostTarget.${name} or { };
valueTargetTarget = pkgsTargetTarget.${name} or { };
augmentedValue = defaultValue // { augmentedValue = defaultValue // {
__spliced = __spliced = lib.filterAttrs (k: v: inputs.${k} ? ${name}) value';
(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;
});
}; };
# Get the set of outputs of a derivation. If one derivation fails to # 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 # evaluate we don't want to diverge the entire splice, so we fall back
@@ -76,27 +58,12 @@ let
# on to splice them together. # on to splice them together.
if lib.isDerivation defaultValue then if lib.isDerivation defaultValue then
augmentedValue augmentedValue
// spliceReal { // spliceReal (mapCrossIndex tryGetOutputs value' // { hostTarget = getOutputs value'.hostTarget; })
pkgsBuildBuild = tryGetOutputs valueBuildBuild;
pkgsBuildHost = tryGetOutputs valueBuildHost;
pkgsBuildTarget = tryGetOutputs valueBuildTarget;
pkgsHostHost = tryGetOutputs valueHostHost;
pkgsHostTarget = getOutputs valueHostTarget;
pkgsTargetTarget = tryGetOutputs valueTargetTarget;
# Just recur on plain attrsets
}
else if lib.isAttrs defaultValue then else if lib.isAttrs defaultValue then
spliceReal { spliceReal value'
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.
}
else else
# Don't be fancy about non-derivations. But we could have used used
# `__functor__` for functions instead.
defaultValue; defaultValue;
}; };
in in
@@ -111,7 +78,7 @@ let
pkgsHostTarget, pkgsHostTarget,
pkgsTargetTarget, pkgsTargetTarget,
}@args: }@args:
if actuallySplice then spliceReal args else pkgsHostTarget; if actuallySplice then spliceReal (renameCrossIndexFrom "pkgs" args) else pkgsHostTarget;
splicedPackages = splicedPackages =
splicePackages { splicePackages {