This repository has no description
1{
2 pkgs,
3 lib,
4 options,
5 config,
6 ...
7} @ args: let
8 configPath = /run/spindle/user-config/config.json;
9 userConfig =
10 args.userConfig
11 or (
12 if builtins.pathExists configPath
13 then lib.importJSON configPath
14 else {}
15 );
16
17 registry = userConfig.registry or {};
18
19 # registry targets may be structured attrs or flake ref strings; strings are
20 # parsed by nix itself in getFlake. flakeRefToString rejects unforced attr
21 # values, hence the toJSON round-trip
22 toRefString = target:
23 if builtins.isAttrs target
24 then builtins.flakeRefToString (builtins.fromJSON (builtins.toJSON target))
25 else target;
26
27 # user registry entries shadow the system registry (which pins nixpkgs)
28 getFlake = ref: builtins.getFlake (toRefString (registry.${ref} or ref));
29
30 # "flakeref#attr" or a bare attr looked up in nixpkgs. nixpkgs refs use the
31 # already-evaluated pkgs directly instead of re-evaluating via getFlake,
32 # unless the user remapped nixpkgs in their registry
33 resolvePackage = ref: let
34 parts = lib.splitString "#" ref;
35 hasAttr = lib.length parts > 1;
36 flakeRef =
37 if hasAttr
38 then lib.head parts
39 else "nixpkgs";
40 pkgName =
41 if hasAttr
42 then lib.elemAt parts 1
43 else ref;
44 system = pkgs.stdenv.hostPlatform.system;
45 flake = getFlake flakeRef;
46 notFound = throw "Package ${pkgName} not found in ${flakeRef}";
47 # pkgName may be dotted (e.g. "python3Packages.requests"), so walk the
48 # attr path rather than doing a single dotted-string lookup
49 pkgPath = lib.splitString "." pkgName;
50 inPackages = ["packages" system] ++ pkgPath;
51 inLegacy = ["legacyPackages" system] ++ pkgPath;
52 in
53 if flakeRef == "nixpkgs" && !(registry ? nixpkgs)
54 then lib.attrByPath pkgPath notFound pkgs
55 else if lib.hasAttrByPath inLegacy flake
56 then lib.getAttrFromPath inLegacy flake
57 else if lib.hasAttrByPath inPackages flake
58 then lib.getAttrFromPath inPackages flake
59 else notFound;
60
61 # strings are resolved as package references only where the option type
62 # actually expects packages; everything else passes through untouched
63 resolveForType = type: v:
64 if type.name == "package" && builtins.isString v
65 then resolvePackage v
66 # path-typed options (e.g. services.udev.packages) accept derivations via
67 # coercion; "#" disambiguates flake refs from actual paths, which are
68 # always absolute
69 else if type.name == "path" && builtins.isString v && lib.hasInfix "#" v && !lib.hasPrefix "/" v
70 then resolvePackage v
71 else if type.name == "nullOr" && v != null
72 then resolveForType type.nestedTypes.elemType v
73 else if type.name == "listOf" && builtins.isList v
74 then map (resolveForType type.nestedTypes.elemType) v
75 else if (type.name == "attrsOf" || type.name == "lazyAttrsOf") && builtins.isAttrs v
76 then builtins.mapAttrs (_: resolveForType type.nestedTypes.elemType) v
77 else if type.name == "submodule" && builtins.isAttrs v
78 then resolveOptions (type.getSubOptions []) v
79 else v;
80
81 resolveOptions = opts: builtins.mapAttrs (name: resolveValue (opts.${name} or null));
82
83 resolveValue = opt: v:
84 if !builtins.isAttrs opt
85 then v
86 else if lib.isOption opt
87 then resolveForType opt.type v
88 else if builtins.isAttrs v
89 then resolveOptions opt v
90 else v;
91
92 # `foo = true` is shorthand for `foo.enable = true`, but only when an
93 # enable option actually exists under foo
94 hasEnableOption = opt:
95 builtins.isAttrs opt
96 && (
97 if lib.isOption opt
98 then (opt.type.getSubOptions opt.loc) ? enable
99 else opt ? enable && lib.isOption opt.enable
100 );
101
102 normalize = opts: name: v: let
103 opt = opts.${name} or null;
104 in
105 if builtins.isBool v && hasEnableOption opt
106 then {enable = v;}
107 else resolveValue opt v;
108
109 # dependencies go into a devshell so we can make use of stdenv setup hooks
110 # (e.g. for pkg-config and such)
111 dependencies = userConfig.dependencies or [];
112 spindleDevShell = pkgs.mkShellNoCC {
113 name = "spindle-deps";
114 packages = map resolvePackage dependencies;
115 };
116 spindleDevEnv = spindleDevShell.overrideAttrs (_: {
117 name = "spindle-deps-env";
118 # materialize the devshell like how nix print-dev-env does internally
119 args = ["${config.nix.package.src}/src/nix/get-env.sh"];
120 });
121in {
122 nix.registry = builtins.mapAttrs (name: _:
123 lib.mkForce {
124 to = {
125 type = "path";
126 path = (getFlake name).outPath;
127 };
128 })
129 registry;
130 environment.etc = lib.mkIf (dependencies != []) {
131 "spindle/devshell-drv".text = "${spindleDevEnv}";
132 };
133 services = builtins.mapAttrs (normalize (options.services or {})) (userConfig.services or {});
134 virtualisation = builtins.mapAttrs (normalize (options.virtualisation or {})) (userConfig.virtualisation or {});
135}