nixpkgs-suyu/lib/customisation.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

412 lines
15 KiB
Nix
Raw Normal View History

Convert libs to a fixed-point This does break the API of being able to import any lib file and get its libs, however I'm not sure people did this. I made this while exploring being able to swap out docFn with a stub in #2305, to avoid functor performance problems. I don't know if that is going to move forward (or if it is a problem or not,) but after doing all this work figured I'd put it up anyway :) Two notable advantages to this approach: 1. when a lib inherits another lib's functions, it doesn't automatically get put in to the scope of lib 2. when a lib implements a new obscure functions, it doesn't automatically get put in to the scope of lib Using the test script (later in this commit) I got the following diff on the API: + diff master fixed-lib 11764a11765,11766 > .types.defaultFunctor > .types.defaultTypeMerge 11774a11777,11778 > .types.isOptionType > .types.isType 11781a11786 > .types.mkOptionType 11788a11794 > .types.setType 11795a11802 > .types.types This means that this commit _adds_ to the API, however I can't find a way to fix these last remaining discrepancies. At least none are _removed_. Test script (run with nix-repl in the PATH): #!/bin/sh set -eux repl() { suff=${1:-} echo "(import ./lib)$suff" \ | nix-repl 2>&1 } attrs_to_check() { repl "${1:-}" \ | tr ';' $'\n' \ | grep "\.\.\." \ | cut -d' ' -f2 \ | sed -e "s/^/${1:-}./" \ | sort } summ() { repl "${1:-}" \ | tr ' ' $'\n' \ | sort \ | uniq } deep_summ() { suff="${1:-}" depth="${2:-4}" depth=$((depth - 1)) summ "$suff" for attr in $(attrs_to_check "$suff" | grep -v "types.types"); do if [ $depth -eq 0 ]; then summ "$attr" | sed -e "s/^/$attr./" else deep_summ "$attr" "$depth" | sed -e "s/^/$attr./" fi done } ( cd nixpkgs #git add . #git commit -m "Auto-commit, sorry" || true git checkout fixed-lib deep_summ > ../fixed-lib git checkout master deep_summ > ../master ) if diff master fixed-lib; then echo "SHALLOW MATCH!" fi ( cd nixpkgs git checkout fixed-lib repl .types )
2017-07-29 02:05:35 +02:00
{ lib }:
let
inherit (builtins)
intersectAttrs;
inherit (lib)
functionArgs isFunction mirrorFunctionArgs isAttrs setFunctionArgs
optionalAttrs attrNames filter elemAt concatStringsSep sortOn take length
filterAttrs optionalString flip pathIsDirectory head pipe isDerivation listToAttrs
mapAttrs seq flatten deepSeq warnIf isInOldestRelease extends
;
inherit (lib.strings) levenshtein levenshteinAtMost;
in
rec {
/* `overrideDerivation drv f` takes a derivation (i.e., the result
of a call to the builtin function `derivation`) and returns a new
2017-04-19 21:41:28 +02:00
derivation in which the attributes of the original are overridden
according to the function `f`. The function `f` is called with
the original derivation attributes.
`overrideDerivation` allows certain "ad-hoc" customisation
scenarios (e.g. in ~/.config/nixpkgs/config.nix). For instance,
if you want to "patch" the derivation returned by a package
function in Nixpkgs to build another version than what the
function itself provides.
For another application, see build-support/vm, where this
function is used to build arbitrary derivations inside a QEMU
2015-03-19 16:48:54 +01:00
virtual machine.
Note that in order to preserve evaluation errors, the new derivation's
outPath depends on the old one's, which means that this function cannot
be used in circular situations when the old derivation also depends on the
new one.
You should in general prefer `drv.overrideAttrs` over this function;
see the nixpkgs manual for more information on overriding.
Example:
mySed = overrideDerivation pkgs.gnused (oldAttrs: {
name = "sed-4.2.2-pre";
src = fetchurl {
url = ftp://alpha.gnu.org/gnu/sed/sed-4.2.2-pre.tar.bz2;
hash = "sha256-MxBJRcM2rYzQYwJ5XKxhXTQByvSg5jZc5cSHEZoB2IY=";
};
patches = [];
});
Type:
overrideDerivation :: Derivation -> ( Derivation -> AttrSet ) -> Derivation
2015-03-19 16:48:54 +01:00
*/
overrideDerivation = drv: f:
let
newDrv = derivation (drv.drvAttrs // (f drv));
in flip (extendDerivation (seq drv.drvPath true)) newDrv (
{ meta = drv.meta or {};
passthru = if drv ? passthru then drv.passthru else {};
}
//
(drv.passthru or {})
//
optionalAttrs (drv ? __spliced) {
__spliced = {} // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
});
/* `makeOverridable` takes a function from attribute set to attribute set and
2019-09-02 13:39:40 +02:00
injects `override` attribute which can be used to override arguments of
the function.
Please refer to documentation on [`<pkg>.overrideDerivation`](#sec-pkg-overrideDerivation) to learn about `overrideDerivation` and caveats
related to its use.
Example:
nix-repl> x = {a, b}: { result = a + b; }
nix-repl> y = lib.makeOverridable x { a = 1; b = 2; }
nix-repl> y
{ override = «lambda»; overrideDerivation = «lambda»; result = 3; }
nix-repl> y.override { a = 10; }
{ override = «lambda»; overrideDerivation = «lambda»; result = 12; }
Type:
makeOverridable :: (AttrSet -> a) -> AttrSet -> a
*/
makeOverridable = f:
let
# Creates a functor with the same arguments as f
mirrorArgs = mirrorFunctionArgs f;
in
mirrorArgs (origArgs:
let
result = f origArgs;
# Changes the original arguments with (potentially a function that returns) a set of new attributes
overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs);
# Re-call the function but with different arguments
overrideArgs = mirrorArgs (newArgs: makeOverridable f (overrideWith newArgs));
# Change the result of the function call by applying g to it
overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs;
in
if isAttrs result then
result // {
override = overrideArgs;
overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv);
${if result ? overrideAttrs then "overrideAttrs" else null} = fdrv:
overrideResult (x: x.overrideAttrs fdrv);
}
else if isFunction result then
# Transform the result into a functor while propagating its arguments
setFunctionArgs result (functionArgs result) // {
override = overrideArgs;
}
else result);
/* Call the package function in the file `fn` with the required
arguments automatically. The function is called with the
arguments `args`, but any missing arguments are obtained from
`autoArgs`. This function is intended to be partially
parameterised, e.g.,
```nix
callPackage = callPackageWith pkgs;
pkgs = {
libfoo = callPackage ./foo.nix { };
libbar = callPackage ./bar.nix { };
};
```
If the `libbar` function expects an argument named `libfoo`, it is
automatically passed as an argument. Overrides or missing
arguments can be supplied in `args`, e.g.
```nix
libbar = callPackage ./bar.nix {
libfoo = null;
enableX11 = true;
};
```
<!-- TODO: Apply "Example:" tag to the examples above -->
Type:
callPackageWith :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a
*/
callPackageWith = autoArgs: fn: args:
let
f = if isFunction fn then fn else import fn;
fargs = functionArgs f;
# All arguments that will be passed to the function
# This includes automatic ones and ones passed explicitly
allArgs = intersectAttrs fargs autoArgs // args;
# a list of argument names that the function requires, but
# wouldn't be passed to it
missingArgs =
# Filter out arguments that have a default value
(filterAttrs (name: value: ! value)
# Filter out arguments that would be passed
(removeAttrs fargs (attrNames allArgs)));
# Get a list of suggested argument names for a given missing one
getSuggestions = arg: pipe (autoArgs // args) [
attrNames
# Only use ones that are at most 2 edits away. While mork would work,
# levenshteinAtMost is only fast for 2 or less.
(filter (levenshteinAtMost 2 arg))
# Put strings with shorter distance first
(sortOn (levenshtein arg))
# Only take the first couple results
(take 3)
# Quote all entries
(map (x: "\"" + x + "\""))
];
prettySuggestions = suggestions:
if suggestions == [] then ""
else if length suggestions == 1 then ", did you mean ${elemAt suggestions 0}?"
else ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";
errorForArg = arg:
let
loc = builtins.unsafeGetAttrPos arg fargs;
# loc' can be removed once lib/minver.nix is >2.3.4, since that includes
# https://github.com/NixOS/nix/pull/3468 which makes loc be non-null
loc' = if loc != null then loc.file + ":" + toString loc.line
else if ! isFunction fn then
toString fn + optionalString (pathIsDirectory fn) "/default.nix"
else "<unknown location>";
in "Function called without required argument \"${arg}\" at "
+ "${loc'}${prettySuggestions (getSuggestions arg)}";
# Only show the error for the first missing argument
error = errorForArg (head (attrNames missingArgs));
in if missingArgs == {}
then makeOverridable f allArgs
# This needs to be an abort so it can't be caught with `builtins.tryEval`,
# which is used by nix-env and ofborg to filter out packages that don't evaluate.
# This way we're forced to fix such errors in Nixpkgs,
# which is especially relevant with allowAliases = false
else abort "lib.customisation.callPackageWith: ${error}";
/* Like callPackage, but for a function that returns an attribute
set of derivations. The override function is added to the
individual attributes.
Type:
callPackagesWith :: AttrSet -> ((AttrSet -> AttrSet) | Path) -> AttrSet -> AttrSet
*/
callPackagesWith = autoArgs: fn: args:
let
f = if isFunction fn then fn else import fn;
auto = intersectAttrs (functionArgs f) autoArgs;
origArgs = auto // args;
pkgs = f origArgs;
2019-02-03 16:30:15 +01:00
mkAttrOverridable = name: _: makeOverridable (newArgs: (f newArgs).${name}) origArgs;
in
if isDerivation pkgs then throw
("function `callPackages` was called on a *single* derivation "
+ ''"${pkgs.name or "<unknown-name>"}";''
+ " did you mean to use `callPackage` instead?")
else mapAttrs mkAttrOverridable pkgs;
/* Add attributes to each output of a derivation without changing
the derivation itself and check a given condition when evaluating.
Type:
extendDerivation :: Bool -> Any -> Derivation -> Derivation
*/
extendDerivation = condition: passthru: drv:
let
outputs = drv.outputs or [ "out" ];
commonAttrs = drv // (listToAttrs outputsList) //
({ all = map (x: x.value) outputsList; }) // passthru;
outputToAttrListElement = outputName:
{ name = outputName;
value = commonAttrs // {
inherit (drv.${outputName}) type outputName;
outputSpecified = true;
drvPath = assert condition; drv.${outputName}.drvPath;
outPath = assert condition; drv.${outputName}.outPath;
} //
# TODO: give the derivation control over the outputs.
# `overrideAttrs` may not be the only attribute that needs
# updating when switching outputs.
optionalAttrs (passthru?overrideAttrs) {
# TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing.
overrideAttrs = f: (passthru.overrideAttrs f).${outputName};
};
};
outputsList = map outputToAttrListElement outputs;
in commonAttrs // {
drvPath = assert condition; drv.drvPath;
outPath = assert condition; drv.outPath;
};
/* Strip a derivation of all non-essential attributes, returning
only those needed by hydra-eval-jobs. Also strictly evaluate the
result to ensure that there are no thunks kept alive to prevent
garbage collection.
Type:
hydraJob :: (Derivation | Null) -> (Derivation | Null)
*/
hydraJob = drv:
let
outputs = drv.outputs or ["out"];
commonAttrs =
{ inherit (drv) name system meta; inherit outputs; }
// optionalAttrs (drv._hydraAggregate or false) {
_hydraAggregate = true;
constituents = map hydraJob (flatten drv.constituents);
}
// (listToAttrs outputsList);
makeOutput = outputName:
let output = drv.${outputName}; in
{ name = outputName;
value = commonAttrs // {
outPath = output.outPath;
drvPath = output.drvPath;
type = "derivation";
inherit outputName;
};
};
outputsList = map makeOutput outputs;
drv' = (head outputsList).value;
in if drv == null then null else
deepSeq drv' drv';
2015-09-27 16:45:23 +02:00
/* Make a set of packages with a common scope. All packages called
with the provided `callPackage` will be evaluated with the same
2015-09-27 16:45:23 +02:00
arguments. Any package in the set may depend on any other. The
`overrideScope'` function allows subsequent modification of the package
2015-09-27 16:45:23 +02:00
set in a consistent way, i.e. all packages in the set will be
called with the overridden packages. The package sets may be
hierarchical: the packages in the set are called with the scope
provided by `newScope` and the set provides a `newScope` attribute
which can form the parent scope for later package sets.
Type:
makeScope :: (AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a) -> (AttrSet -> AttrSet) -> AttrSet
*/
2015-09-27 16:45:23 +02:00
makeScope = newScope: f:
let self = f self // {
newScope = scope: newScope (self // scope);
callPackage = self.newScope {};
overrideScope = g: makeScope newScope (extends g f);
# Remove after 24.11 is released.
overrideScope' = g: warnIf (isInOldestRelease 2311)
"`overrideScope'` (from `lib.makeScope`) has been renamed to `overrideScope`."
(makeScope newScope (extends g f));
packages = f;
2015-09-27 16:45:23 +02:00
};
in self;
/* backward compatibility with old uncurried form; deprecated */
makeScopeWithSplicing =
splicePackages: newScope: otherSplices: keep: extra: f:
makeScopeWithSplicing'
{ inherit splicePackages newScope; }
{ inherit otherSplices keep extra f; };
/* Like makeScope, but aims to support cross compilation. It's still ugly, but
hopefully it helps a little bit.
Type:
makeScopeWithSplicing' ::
{ splicePackages :: Splice -> AttrSet
, newScope :: AttrSet -> ((AttrSet -> a) | Path) -> AttrSet -> a
}
-> { otherSplices :: Splice, keep :: AttrSet -> AttrSet, extra :: AttrSet -> AttrSet }
-> AttrSet
Splice ::
{ pkgsBuildBuild :: AttrSet
, pkgsBuildHost :: AttrSet
, pkgsBuildTarget :: AttrSet
, pkgsHostHost :: AttrSet
, pkgsHostTarget :: AttrSet
, pkgsTargetTarget :: AttrSet
}
*/
makeScopeWithSplicing' =
{ splicePackages
, newScope
}:
{ otherSplices
# Attrs from `self` which won't be spliced.
# Avoid using keep, it's only used for a python hook workaround, added in PR #104201.
# ex: `keep = (self: { inherit (self) aAttr; })`
, keep ? (_self: {})
# Additional attrs to add to the sets `callPackage`.
# When the package is from a subset (but not a subset within a package IS #211340)
# within `spliced0` it will be spliced.
# When using an package outside the set but it's available from `pkgs`, use the package from `pkgs.__splicedPackages`.
# If the package is not available within the set or in `pkgs`, such as a package in a let binding, it will not be spliced
# ex:
# ```
# nix-repl> darwin.apple_sdk.frameworks.CoreFoundation
# «derivation ...CoreFoundation-11.0.0.drv»
# nix-repl> darwin.CoreFoundation
# error: attribute 'CoreFoundation' missing
# nix-repl> darwin.callPackage ({ CoreFoundation }: CoreFoundation) { }
# «derivation ...CoreFoundation-11.0.0.drv»
# ```
, extra ? (_spliced0: {})
, f
}:
let
spliced0 = splicePackages {
pkgsBuildBuild = otherSplices.selfBuildBuild;
pkgsBuildHost = otherSplices.selfBuildHost;
pkgsBuildTarget = otherSplices.selfBuildTarget;
pkgsHostHost = otherSplices.selfHostHost;
pkgsHostTarget = self; # Not `otherSplices.selfHostTarget`;
pkgsTargetTarget = otherSplices.selfTargetTarget;
};
spliced = extra spliced0 // spliced0 // keep self;
self = f self // {
newScope = scope: newScope (spliced // scope);
callPackage = newScope spliced; # == self.newScope {};
# N.B. the other stages of the package set spliced in are *not*
# overridden.
overrideScope = g: (makeScopeWithSplicing'
{ inherit splicePackages newScope; }
{ inherit otherSplices keep extra;
f = extends g f;
});
packages = f;
};
in self;
}