diff options
| author | tv <tv@krebsco.de> | 2026-11-14 00:27:45 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-14 01:46:01 +0100 |
| commit | a4540a584d762b000b21cb7e61987c414eb72a1d (patch) | |
| tree | 077bb7dfc4d104e5e133ad96c4485ff99bcd14c5 /pkgs/populate/lib | |
| parent | 8f08426ae3a2e5f0ecc5d010bc6469b41e3d7678 (diff) | |
populate: layer populate below krops
populate is a program of its own, with a Nix library, in pkgs/populate,
which krops uses, and which doesn't use krops:
- default.nix builds the program, with nix-build there or callPackage,
as pkgs.populate-unwrapped.
- lib is populate's Nix library: the types of sources and the POSIX
types they use, evalSource, flakeFollows, hasGeneratedFlake, and
derivesFlake, taking nixpkgs' lib. krops' lib takes them from there,
so it provides them as before, and so does lib/types.
pkgs.populate, the function from populate's arguments to a script
running the program, is krops' own, in pkgs/krops/populate.nix, with
checks.nix next to it. It takes the program as populate-unwrapped,
which callPackage passes from the overlay; the flake, whose nixpkgs
lacks it, passes its own.
Diffstat (limited to 'pkgs/populate/lib')
| -rw-r--r-- | pkgs/populate/lib/default.nix | 78 | ||||
| -rw-r--r-- | pkgs/populate/lib/types/populate.nix | 323 | ||||
| -rw-r--r-- | pkgs/populate/lib/types/posix.nix | 54 |
3 files changed, 455 insertions, 0 deletions
diff --git a/pkgs/populate/lib/default.nix b/pkgs/populate/lib/default.nix new file mode 100644 index 0000000..e9155c5 --- /dev/null +++ b/pkgs/populate/lib/default.nix @@ -0,0 +1,78 @@ +# populate's Nix library: the types of sources, which describe how to write +# them in Nix, and evalSource, which turns sources written so into the +# sources populate reads as JSON. lib is nixpkgs' lib. +{ lib ? import <nixpkgs/lib> }: + +let + # nixpkgs' lib, with what the types use besides. + lib' = lib // { + test = re: x: lib.isString x && lib.match re x != null; + types = lib.types // types; + }; + + types = + import ./types/posix.nix { lib = lib'; } // + import ./types/populate.nix { lib = lib'; }; + +in rec { + + inherit types; + + # The sources, a list of attribute sets of sources by name, or one, with + # shorthands expanded and defaults filled in, and without settings that + # are null, as populate reads them, e.g. with builtins.toJSON. + evalSource = let + eval = source: lib.evalModules { + modules = lib.singleton { + _file = toString ./.; + imports = map (source: { inherit source; }) (lib.toList source); + options.source = lib.mkOption { + default = {}; + type = lib.types.attrsOf types.source; + }; + }; + }; + sanitize = x: lib.getAttr (lib.typeOf x) { + bool = x; + list = map sanitize x; + set = lib.mapAttrs + (lib.const sanitize) + (lib.filterAttrs + (name: value: name != "_module" && value != null) x); + string = x; + }; + in + source: sanitize (eval source).config.source; + + # Inputs a source draws from other sources, as list of attribute sets like + # { path = [ "stockholm" "nixpkgs" ]; source = "nixpkgs"; }. + flakeFollows = source: let + flatten = prefix: inputs: lib.concatLists (lib.mapAttrsToList + (name: input: + lib.optional (input.follows or null != null) { + path = prefix ++ [ name ]; + source = input.follows; + } ++ + flatten (prefix ++ [ name ]) (input.inputs or {})) + inputs); + in + flatten [] (source.${source.type}.inputs or {}); + + # Tells whether a source's inputs get drawn from other sources by a + # generated flake in .populate/flakes, instead of by rewriting the lock + # file of the flake in the source. Symlink sources point at other + # sources, whose lock files stay as they are. + hasGeneratedFlake = source: + flakeFollows source != [] && + (source.type == "symlink" || !(source.${source.type}.rewriteLock or true)); + + # Tells whether a flake or flake-input source gets delivered as a copy, + # which gets patched, gets its lock file rewritten, or is materialized. + derivesFlake = source: let + source' = source.${source.type}; + in + lib.elem source.type [ "flake" "flake-input" ] && + (source'.materialize or false || source'.patches or [] != [] || + (source'.rewriteLock or true && flakeFollows source != [])); + +} diff --git a/pkgs/populate/lib/types/populate.nix b/pkgs/populate/lib/types/populate.nix new file mode 100644 index 0000000..56b0292 --- /dev/null +++ b/pkgs/populate/lib/types/populate.nix @@ -0,0 +1,323 @@ +{ lib }: rec { + + source = lib.types.submodule ({ config, ... }: { + options = { + type = let + known-types = lib.attrNames source-types; + type-candidates = lib.filter (k: config.${k} != null) known-types; + in lib.mkOption { + default = if lib.length type-candidates == 1 + then lib.head type-candidates + else throw "cannot determine type"; + type = lib.types.enum known-types; + }; + derivation = lib.mkOption { + apply = x: + if lib.types.str.check x + then { text = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.either lib.types.str source-types.derivation); + }; + file = lib.mkOption { + apply = x: + if lib.types.absolute-pathname.check x || lib.types.package.check x + then { path = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.oneOf [ + lib.types.absolute-pathname + lib.types.package + source-types.file + ]); + }; + flake = lib.mkOption { + apply = x: + if lib.types.str.check x + then { url = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.either lib.types.str source-types.flake); + }; + flake-input = lib.mkOption { + default = null; + type = lib.types.nullOr source-types.flake-input; + }; + git = lib.mkOption { + default = null; + type = lib.types.nullOr source-types.git; + }; + pass = lib.mkOption { + default = null; + type = lib.types.nullOr source-types.pass; + }; + passage = lib.mkOption { + apply = x: + if lib.types.pathname.check x + then { dir = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.oneOf [ + lib.types.pathname + source-types.passage + ]); + }; + pipe = lib.mkOption { + apply = x: + if lib.types.absolute-pathname.check x + then { command = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.either lib.types.absolute-pathname source-types.pipe); + }; + symlink = lib.mkOption { + apply = x: + if lib.types.pathname.check x + then { target = x; } + else x; + default = null; + type = lib.types.nullOr (lib.types.either lib.types.pathname source-types.symlink); + }; + }; + }); + + # Inputs of the flake in a source that get drawn from other sources, in the + # style of flake.nix, e.g. { nixpkgs.follows = "nixpkgs"; }. + flake-inputs = lib.types.attrsOf (lib.types.submodule { + options = { + follows = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; # TODO lib.types.label + }; + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + }; + }); + + filter = lib.types.submodule { + options = { + type = lib.mkOption { + type = lib.types.enum ["include" "exclude"]; + default = "exclude"; + }; + pattern = lib.mkOption { + type = lib.types.str; + }; + }; + }; + + source-types = { + derivation = lib.types.submodule { + options = { + text = lib.mkOption { + type = lib.types.str; + }; + }; + }; + file = lib.types.submodule { + options = { + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + path = lib.mkOption { + type = lib.types.absolute-pathname; + }; + rewriteLock = lib.mkOption { + default = true; + type = lib.types.bool; + }; + useChecksum = lib.mkOption { + default = false; + type = lib.types.bool; + }; + exclude = lib.mkOption { + apply = x: + if x != [] then + lib.warn + "file.*.exclude is deprecated in favor of file.*.filters" + x + else + x; + description = '' + DEPRECATED, use `filters`. + ''; + type = lib.types.listOf lib.types.str; + default = []; + example = [".git"]; + }; + filters = lib.mkOption { + type = lib.types.listOf filter; + default = []; + example = [ + { + type = "include"; + pattern = "*.nix"; + } + { + type = "include"; + pattern = "*/"; + } + { + type = "exclude"; + pattern = "*"; + } + ]; + }; + deleteExcluded = lib.mkOption { + default = true; + type = lib.types.bool; + }; + insecureLinks = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.bool; + }; + }; + }; + flake = lib.types.submodule { + options = { + fetchOnTarget = lib.mkOption { + default = false; + type = lib.types.bool; + }; + url = lib.mkOption { + type = lib.types.str; # TODO lib.types.flake-ref + }; + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + materialize = lib.mkOption { + default = false; + type = lib.types.bool; + }; + patches = lib.mkOption { + apply = map toString; + default = []; + type = lib.types.listOf lib.types.path; + }; + rewriteLock = lib.mkOption { + default = true; + type = lib.types.bool; + }; + }; + }; + flake-input = lib.types.submodule { + options = { + fetchOnTarget = lib.mkOption { + default = false; + type = lib.types.bool; + }; + url = lib.mkOption { + type = lib.types.str; # TODO lib.types.flake-ref + }; + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + materialize = lib.mkOption { + default = false; + type = lib.types.bool; + }; + patches = lib.mkOption { + apply = map toString; + default = []; + type = lib.types.listOf lib.types.path; + }; + rewriteLock = lib.mkOption { + default = true; + type = lib.types.bool; + }; + name = lib.mkOption { + type = lib.types.str; + }; + requireInputs = lib.mkOption { + default = true; + type = lib.types.bool; + }; + }; + }; + git = lib.types.submodule { + options = { + clean = { + exclude = lib.mkOption { + default = []; + type = lib.types.listOf lib.types.str; + }; + }; + fetchAlways = lib.mkOption { + type = lib.types.bool; + default = false; + }; + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + ref = lib.mkOption { + type = lib.types.str; # TODO lib.types.git.ref + }; + reclone = lib.mkOption { + default = false; + type = lib.types.bool; + }; + rewriteLock = lib.mkOption { + default = true; + type = lib.types.bool; + }; + url = lib.mkOption { + type = lib.types.str; # TODO lib.types.git.url + }; + shallow = lib.mkOption { + default = false; + type = lib.types.bool; + }; + }; + }; + pass = lib.types.submodule { + options = { + dir = lib.mkOption { + type = lib.types.absolute-pathname; + }; + name = lib.mkOption { + type = lib.types.pathname; # TODO relative-pathname + }; + }; + }; + passage = lib.types.submodule { + options = { + age = lib.mkOption { + default = "age"; + type = lib.types.pathname; + }; + dir = lib.mkOption { + type = lib.types.pathname; + }; + identities_file = lib.mkOption { + default = toString ~/.passage/identities; + type = lib.types.pathname; + }; + }; + }; + pipe = lib.types.submodule { + options = { + command = lib.mkOption { + type = lib.types.absolute-pathname; + }; + }; + }; + symlink = lib.types.submodule { + options = { + inputs = lib.mkOption { + default = {}; + type = flake-inputs; + }; + target = lib.mkOption { + type = lib.types.pathname; # TODO relative-pathname + }; + }; + }; + }; + +} diff --git a/pkgs/populate/lib/types/posix.nix b/pkgs/populate/lib/types/posix.nix new file mode 100644 index 0000000..e8f464e --- /dev/null +++ b/pkgs/populate/lib/types/posix.nix @@ -0,0 +1,54 @@ +{ lib }: rec { + + # RFC952, B. Lexical grammar, <hname> + hostname = lib.mkOptionType { + name = "hostname"; + check = x: lib.isString x && lib.all label.check (lib.splitString "." x); + merge = lib.mergeOneOption; + }; + + # RFC952, B. Lexical grammar, <name> + # RFC1123, 2.1 Host Names and Numbers + label = lib.mkOptionType { + name = "label"; + # TODO case-insensitive labels + check = lib.test "[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?"; + merge = lib.mergeOneOption; + }; + + # POSIX.1‐2013, 3.278 Portable Filename Character Set + filename = lib.mkOptionType { + name = "POSIX filename"; + check = lib.test "([0-9A-Za-z._])[0-9A-Za-z._-]*"; + merge = lib.mergeOneOption; + }; + + # POSIX.1‐2013, 3.2 Absolute Pathname + absolute-pathname = lib.mkOptionType { + name = "POSIX absolute pathname"; + check = x: lib.isString x && lib.substring 0 1 x == "/" && pathname.check x; + merge = lib.mergeOneOption; + }; + + # POSIX.1‐2013, 3.267 Pathname + pathname = lib.mkOptionType { + name = "POSIX pathname"; + check = x: + let + # The filter is used to normalize paths, i.e. to remove duplicated and + # trailing slashes. It also removes leading slashes, thus we have to + # check for "/" explicitly below. + xs = lib.filter (s: lib.stringLength s > 0) (lib.splitString "/" x); + in + lib.isString x && (x == "/" || (lib.length xs > 0 && lib.all filename.check xs)); + merge = lib.mergeOneOption; + }; + + # POSIX.1-2013, 3.431 User Name + username = lib.mkOptionType { + name = "POSIX username"; + check = filename.check; + merge = lib.mergeOneOption; + }; + +} |
