This commit is contained in:
Robert Helgesson 2020-01-16 00:29:57 +01:00
commit 4b04050953
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
9 changed files with 251 additions and 8 deletions

View file

@ -7,6 +7,7 @@ let
cfg = config.home;
dag = config.lib.dag;
dagOf = (import ./lib/types.nix { inherit dag lib; }).dagOf;
languageSubModule = types.submodule {
options = {
@ -234,17 +235,51 @@ in
};
home.activation = mkOption {
internal = true;
type = dagOf types.str;
default = {};
type = types.attrs;
example = literalExample ''
{
myActivationAction = config.lib.dag.entryAfter ["writeBoundary"] '''
$DRY_RUN_CMD ln -s $VERBOSE_ARG \
''${builtins.toPath ./link-me-directly} $HOME
''';
}
'';
description = ''
Activation scripts for the home environment.
The activation scripts blocks to run when activating a Home
Manager generation. Any entry here should be idempotent,
meaning running twice or more times produces the same result
as running it once.
</para><para>
Any script should respect the <varname>DRY_RUN</varname>
variable, if it is set then no actual action should be taken.
If the script block produces any observable side effect, such
as writing or deleting files, then it
<emphasis>must</emphasis> be placed after the special
<literal>writeBoundary</literal> script block. Prior to the
write boundary one can place script blocks that verifies, but
does not modify, the state of the system and exits if an
unexpected state is found. For example, the
<literal>checkLinkTargets</literal> script block checks for
collisions between non-managed files and files defined in
<varname><link linkend="opt-home.file">home.file</link></varname>.
</para><para>
A script block should respect the <varname>DRY_RUN</varname>
variable, if it is set then the actions taken by the script
should be logged to standard out and not actually performed.
The variable <varname>DRY_RUN_CMD</varname> is set to
<code>echo</code> if dry run is enabled. Thus, many cases you
can use the idiom <code>$DRY_RUN_CMD rm -rf /</code>.
<command>echo</command> if dry run is enabled.
</para><para>
A script block should also respect the
<varname>VERBOSE</varname> variable, and if set print
information on standard out that may be useful for debugging
any issue that may arise. The variable
<varname>VERBOSE_ARG</varname> is set to
<option>--verbose</option> if verbose output is enabled.
'';
};

96
modules/lib/types-dag.nix Normal file
View file

@ -0,0 +1,96 @@
{ dag, lib }:
with lib;
let
isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);
dagContentType = elemType: types.submodule {
options = {
data = mkOption { type = elemType; };
after = mkOption { type = with types; uniq (listOf str); };
before = mkOption { type = with types; uniq (listOf str); };
};
};
in
{
# A directed acyclic graph of some inner type.
dagOf = elemType:
let
convertAllToDags =
let
maybeConvert = n: v:
if isDagEntry v
then v
else dag.entryAnywhere v;
in
map (def: def // { value = mapAttrs maybeConvert def.value; });
attrEquivalent = types.attrsOf (dagContentType elemType);
in
mkOptionType rec {
name = "dagOf";
description = "DAG of ${elemType.description}s";
check = isAttrs;
merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = 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;
convertAllToDags = defs:
let
convertAttrValue = n: v:
if isDagEntry v then v
else dag.entryAnywhere v;
convertListValue = namePrefix: vs:
let
pad = paddedIndexStr vs;
makeEntry = i: v:
nameValuePair "${namePrefix}.${pad i}" (dag.entryAnywhere v);
in
listToAttrs (imap1 makeEntry vs);
convertValue = i: value:
if isList value
then convertListValue "unnamed-${paddedIndexStr defs i}" value
else mapAttrs convertAttrValue value;
in
imap1 (i: def: def // { value = convertValue i def.value; }) defs;
attrEquivalent = types.attrsOf (dagContentType elemType);
in
mkOptionType rec {
name = "dagOf";
description = "DAG of ${elemType.description}s";
check = x: isAttrs x || isList x;
merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
};
}

View file

@ -1,9 +1,18 @@
{ lib }:
{ lib, dag ? import ./dag.nix { inherit lib; } }:
with lib;
let
hmLib = import ./default.nix { inherit lib; };
typesDag = import ./types-dag.nix { inherit dag lib; };
in
{
inherit (typesDag) dagOf listOrDagOf;
selectorFunction = mkOptionType {
name = "selectorFunction";
description =

View file

@ -34,6 +34,7 @@ import nmt {
// import ./modules/services/sxhkd
// import ./modules/systemd
)
// import ./lib/types
// import ./modules/files
// import ./modules/home-environment
// import ./modules/misc/fontconfig

View file

@ -0,0 +1,3 @@
before:before
between:between
after:after

View file

@ -0,0 +1,39 @@
{ config, lib, pkgs, ... }:
with lib;
let
dag = config.lib.dag;
hmTypes = import ../../../modules/lib/types.nix { inherit dag lib; };
result =
let
sorted = dag.topoSort config.tested.dag;
data = map (e: "${e.name}:${e.data}") sorted.result;
in
concatStringsSep "\n" data + "\n";
in
{
options.tested.dag = mkOption {
type = with types; hmTypes.dagOf str;
};
config = {
tested = mkMerge [
{ dag.after = "after"; }
{ dag.before = dag.entryBefore ["after"] "before"; }
{ dag.between = dag.entryBetween ["after"] ["before"] "between"; }
];
home.file."result.txt".text = result;
nmt.script = ''
assertFileContent \
home-files/result.txt \
${./dag-merge-result.txt}
'';
};
}

View file

@ -0,0 +1,4 @@
{
lib-types-dag-merge = ./dag-merge.nix;
lib-types-list-or-dag-merge = ./list-or-dag-merge.nix;
}

View file

@ -0,0 +1,15 @@
before:before
between:between
after:after
unnamed-1.1:k
unnamed-1.2:l
unnamed-2.01:a
unnamed-2.02:b
unnamed-2.03:c
unnamed-2.04:d
unnamed-2.05:e
unnamed-2.06:f
unnamed-2.07:g
unnamed-2.08:h
unnamed-2.09:i
unnamed-2.10:j

View file

@ -0,0 +1,41 @@
{ config, lib, pkgs, ... }:
with lib;
let
dag = config.lib.dag;
hmTypes = import ../../../modules/lib/types.nix { inherit dag lib; };
result =
let
sorted = dag.topoSort config.tested.dag;
data = map (e: "${e.name}:${e.data}") sorted.result;
in
concatStringsSep "\n" data + "\n";
in
{
options.tested.dag = mkOption {
type = with types; hmTypes.listOrDagOf str;
};
config = {
tested = mkMerge [
{ dag = [ "k" "l" ]; }
{ dag = [ "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" ]; }
{ dag.after = "after"; }
{ dag.before = dag.entryBefore ["after"] "before"; }
{ dag.between = dag.entryBetween ["after"] ["before"] "between"; }
];
home.file."result.txt".text = result;
nmt.script = ''
assertFileContent \
home-files/result.txt \
${./list-or-dag-merge-result.txt}
'';
};
}