Commit 49e500d
Changed files (1)
modules
modules/mk-settings.nix
@@ -1,5 +1,6 @@
# Settings surface shared by hosts and users. Aspects declare
-# `settings.{host,user}`; entities supply values and aspects read them back.
+# `settings.{host,user}` as an option set or as a function of the entity
+# instance; entities supply values and aspects read them back.
{
config,
lib,
@@ -27,12 +28,19 @@
if builtins.isAttrs s
then s
else null;
- hasHost = builtins.isAttrs (settings.host or null);
- hasUser = builtins.isAttrs (settings.user or null);
+ isDecl = v: builtins.isAttrs v || builtins.isFunction v;
+ hasHost = isDecl (settings.host or null);
+ hasUser = isDecl (settings.user or null);
mine = lib.optional (hasHost || hasUser) {
inherit prefix;
- host = lib.optionalAttrs hasHost settings.host;
- user = lib.optionalAttrs hasUser settings.user;
+ host =
+ if hasHost
+ then settings.host
+ else {};
+ user =
+ if hasUser
+ then settings.user
+ else {};
};
children = lib.concatMap (name: collect (prefix ++ [name]) node.${name}) (
builtins.filter (name:
@@ -50,24 +58,31 @@
collected = collect [] config.den.aspects;
- # Build nested option declarations, grouping shared path prefixes.
- mkOptions = nodes: let
+ # Build nested option declarations, grouping shared path prefixes. `ctx` is the
+ # entity instance, and forcing it here would recurse: the fixpoint is still
+ # being built (gen-schema ADR-0033), so only lazy reads such as defaults may touch it.
+ mkOptions = ctx: nodes: let
grouped = lib.groupBy (n: builtins.head n.path) nodes;
in
lib.mapAttrs (
name: group:
if lib.all (n: builtins.length n.path == 1) group
- then
+ then let
+ decls = (builtins.head group).decls;
+ in
lib.mkOption {
type = lib.types.submodule {
- options = (builtins.head group).decls;
+ options =
+ if builtins.isFunction decls
+ then decls ctx
+ else decls;
};
default = {};
description = "Settings for aspect `${name}'.";
}
else if !(lib.all (n: builtins.length n.path > 1) group)
then throw "aspect settings: aspect `${name}' is both a settings leaf and a container"
- else mkOptions (map (n: n // {path = lib.drop 1 n.path;}) group)
+ else mkOptions ctx (map (n: n // {path = lib.drop 1 n.path;}) group)
)
grouped;
@@ -81,11 +96,12 @@
decls = n.user;
}) (lib.filter (n: n.user != {}) collected);
- userSettingsType = lib.types.submodule {
- # The shared registry and hosts also carry raw user-system values
- freeformType = lib.types.attrsOf lib.types.anything;
- options.settings = mkOptions userNodes;
- };
+ userSettingsType = ctx:
+ lib.types.submodule {
+ # The shared registry and hosts also carry raw user-system values
+ freeformType = lib.types.attrsOf lib.types.anything;
+ options.settings = mkOptions ctx userNodes;
+ };
# Merge the shared `den.users.<username>` values as lowest-priority defs.
registryAware = inner:
@@ -108,18 +124,32 @@ in {
config = {
den.reservedKeys = ["settings"];
- den.schema.host.options.settings = lib.mkOption {
- type = lib.types.submodule {
- options = mkOptions hostNodes;
- };
- default = {};
- description = "Host-provided settings for den aspects.";
- };
+ # Through imports, not on the kind: the option type must be built inside the
+ # instance for function declarations to receive it.
+ den.schema.host.imports = [
+ ({host, ...}: {
+ options.settings = lib.mkOption {
+ type = lib.types.submodule {
+ options = mkOptions {inherit host;} hostNodes;
+ };
+ default = {};
+ description = "Host-provided settings for den aspects.";
+ };
+ })
+ ];
- den.schema.user.options.system = lib.mkOption {
- type = registryAware userSettingsType;
- default = {};
- description = "User-provided settings for den aspects.";
- };
+ den.schema.user.imports = [
+ ({
+ host,
+ user,
+ ...
+ }: {
+ options.system = lib.mkOption {
+ type = registryAware (userSettingsType {inherit host user;});
+ default = {};
+ description = "User-provided settings for den aspects.";
+ };
+ })
+ ];
};
}