1
0
mirror of https://github.com/nix-community/home-manager synced 2024-06-29 01:48:31 +02:00
home-manager/modules/lib/types-dag.nix
Naïm Favier 8db712a6a2
types: fix dagOf behaviour with mkIf
This makes definitions like

    home.activation.foo = mkIf false "bar"

work, where previously they would complain about
`home.activation.foobar.data` being used but not defined.

The crucial part is that we don't call `convertAllToDags` in
`dagOf.merge`, because we need to process `mkIf`/`mkMerge` properties
first. So we let `attrEquivalent.merge` do its job normally, but give
it a type `dagEntryOf` that does the conversion.

Ideally this shouldn't require so much boilerplate; I'd like to
implement something like

    types.changeInto dagContentType elemType dagEntryAnywhere

in Nixpkgs.
2022-03-30 23:29:09 +02:00

107 lines
4.0 KiB
Nix

{ dag, lib }:
let
inherit (lib)
concatStringsSep defaultFunctor fixedWidthNumber imap1 isAttrs isList length
listToAttrs mapAttrs mkIf mkOption mkOptionType nameValuePair stringLength
types warn;
isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);
dagEntryOf = elemType:
let
submoduleType = types.submodule ({ name, ... }: {
options = {
data = mkOption { type = elemType; };
after = mkOption { type = with types; uniq (listOf str); };
before = mkOption { type = with types; uniq (listOf str); };
};
config = mkIf (elemType.name == "submodule") {
data._module.args.dagName = name;
};
});
maybeConvert = v: if isDagEntry v then v else dag.entryAnywhere v;
in mkOptionType {
name = "dagEntryOf";
description = "DAG entry of ${elemType.description}";
# leave the checking to the submodule type
merge = loc: defs:
submoduleType.merge loc
(map (def: def // { value = maybeConvert def.value; }) defs);
};
in rec {
# A directed acyclic graph of some inner type.
#
# Note, if the element type is a submodule then the `name` argument
# will always be set to the string "data" since it picks up the
# internal structure of the DAG values. To give access to the
# "actual" attribute name a new submodule argument is provided with
# the name `dagName`.
dagOf = elemType:
let attrEquivalent = types.attrsOf (dagEntryOf elemType);
in mkOptionType rec {
name = "dagOf";
description = "DAG of ${elemType.description}s";
inherit (attrEquivalent) check merge emptyValue;
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<name>" ]);
getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
nestedTypes.elemType = elemType;
};
# A directed acyclic graph of some inner type OR a list of that
# inner type. This is a temporary hack for use by the
# `programs.ssh.matchBlocks` and is only guaranteed to be vaguely
# correct!
#
# In particular, adding a dependency on one of the "unnamed-N-M"
# entries generated by a list value is almost guaranteed to destroy
# the list's order.
#
# This function will be removed in version 20.09.
listOrDagOf = elemType:
let
paddedIndexStr = list: i:
let padWidth = stringLength (toString (length list));
in fixedWidthNumber padWidth i;
convertAll = loc: defs:
let
convertListValue = namePrefix: def:
let
vs = def.value;
pad = paddedIndexStr vs;
makeEntry = i: v: nameValuePair "${namePrefix}.${pad i}" v;
warning = ''
In file ${def.file}
a list is being assigned to the option '${
concatStringsSep "." loc
}'.
This will soon be an error due to the list form being deprecated.
Please use the attribute set form instead with DAG functions to
express the desired order of entries.
'';
in warn warning (listToAttrs (imap1 makeEntry vs));
convertValue = i: def:
if isList def.value then
convertListValue "unnamed-${paddedIndexStr defs i}" def
else
def.value;
in imap1 (i: def: def // { value = convertValue i def; }) defs;
dagType = dagOf elemType;
in mkOptionType rec {
name = "listOrDagOf";
description = "list or DAG of ${elemType.description}s";
check = x: isList x || dagType.check x;
merge = loc: defs: dagType.merge loc (convertAll loc defs);
getSubOptions = dagType.getSubOptions;
getSubModules = dagType.getSubModules;
substSubModules = m: listOrDagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
};
}