redesign module hierarchy

This commit is contained in:
do butterflies cry? 2026-03-14 22:18:28 +10:00
parent b1b1743414
commit d891a92357
Signed by: cry
GPG key ID: F68745A836CA0412
22 changed files with 244 additions and 967 deletions

View file

@ -0,0 +1,3 @@
# Snow Module Backend
This source code was tedious so it's just a modified version of the module backend of
[github:hercules-ci/flake-parts](https://github.com/hercules-ci/flake-parts/tree/main/modules).

View file

@ -1,23 +1,31 @@
{ lib, flake-parts-lib, ... }:
let
inherit (lib)
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
types
;
inherit (flake-parts-lib)
mkTransposedPerSystemModule
inherit
(snow)
mkPerSystemFlakeOutput
;
programType = lib.types.coercedTo derivationType lib.getExe lib.types.str;
derivationType =
lib.types.package
// {
check = lib.isDerivation;
};
derivationType = lib.types.package // {
check = lib.isDerivation;
};
programType = lib.types.coercedTo derivationType lib.getExe lib.types.str;
appType = lib.types.submodule {
options = {
type = mkOption {
type = lib.types.enum [ "app" ];
type = lib.types.enum ["app"];
default = "app";
description = ''
A type tag for `apps` consumers.
@ -31,7 +39,7 @@ let
};
meta = mkOption {
type = types.lazyAttrsOf lib.types.raw;
default = { };
default = {};
# TODO refer to Nix manual 2.25
description = ''
Metadata information about the app.
@ -43,19 +51,19 @@ let
};
};
in
mkTransposedPerSystemModule {
name = "apps";
option = mkOption {
type = types.lazyAttrsOf appType;
default = { };
description = ''
Programs runnable with nix run `<name>`.
'';
example = lib.literalExpression ''
{
default.program = "''${config.packages.hello}/bin/hello";
}
'';
};
file = ./apps.nix;
}
mkPerSystemFlakeOutput {
name = "apps";
option = mkOption {
type = types.lazyAttrsOf appType;
default = {};
description = ''
Programs runnable with nix run `<name>`.
'';
example = lib.literalExpression ''
{
default.program = "''${config.packages.hello}/bin/hello";
}
'';
};
file = ./apps.nix;
}

View file

@ -1,21 +1,26 @@
{ lib, flake-parts-lib, ... }:
let
inherit (lib)
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
types
;
inherit (flake-parts-lib)
mkTransposedPerSystemModule
inherit
(snow)
mkPerSystemFlakeOutput
;
in
mkTransposedPerSystemModule {
name = "checks";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = { };
description = ''
Derivations to be built by [`nix flake check`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-check.html).
'';
};
file = ./checks.nix;
}
mkPerSystemFlakeOutput {
name = "checks";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = {};
description = ''
Derivations to be built by [`nix flake check`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-check.html).
'';
};
file = ./checks.nix;
}

View file

@ -1,78 +0,0 @@
{ config, flake-parts-lib, lib, options, getSystem, extendModules, ... }:
let
inherit (lib)
mapAttrs
mkIf
mkOption
optionalAttrs
types
;
inherit (flake-parts-lib)
mkPerSystemOption
;
mkDebugConfig = { config, options, extendModules }: config // {
inherit config;
inherit (config) _module;
inherit options;
inherit extendModules;
};
in
{
options = {
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to add the attributes `debug`, `allSystems` and `currentSystem`
to the flake output. When `true`, this allows inspection of options via
`nix repl`.
```
$ nix repl
nix-repl> :lf .
nix-repl> currentSystem._module.args.pkgs.hello
«derivation /nix/store/7vf0d0j7majv1ch1xymdylyql80cn5fp-hello-2.12.1.drv»
```
Each of `debug`, `allSystems.<system>` and `currentSystem` is an
attribute set consisting of the `config` attributes, plus the extra
attributes `_module`, `config`, `options`, `extendModules`. So note that
these are not part of the `config` parameter, but are merged in for
debugging convenience.
- `debug`: The top-level options
- `allSystems`: The `perSystem` submodule applied to the configured `systems`.
- `currentSystem`: Shortcut into `allSystems`. Only available in impure mode.
Works for arbitrary system values.
See [Expore and debug option values](../debug.html) for more examples.
'';
};
perSystem = mkPerSystemOption
({ options, config, extendModules, ... }: {
_file = ./formatter.nix;
options = {
debug = mkOption {
description = ''
Values to return in e.g. `allSystems.<system>` when
[`debug = true`](#opt-debug).
'';
type = types.lazyAttrsOf types.raw;
};
};
config = {
debug = mkDebugConfig { inherit config options extendModules; };
};
});
};
config = mkIf config.debug {
flake = {
debug = mkDebugConfig { inherit config options extendModules; };
allSystems = mapAttrs (_s: c: c.debug) config.allSystems;
} // optionalAttrs (builtins?currentSystem) {
currentSystem = (getSystem builtins.currentSystem).debug;
};
};
}

View file

@ -0,0 +1,15 @@
{...}: {
imports = [
./outputs.nix
./apps.nix
./checks.nix
./devShells.nix
./formatter.nix
./legacyPackages.nix
./nixosConfigurations.nix
./nixosModules.nix
./overlays.nix
./packages.nix
];
}

View file

@ -1,30 +1,35 @@
{ lib, flake-parts-lib, ... }:
let
inherit (lib)
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
types
literalExpression
;
inherit (flake-parts-lib)
mkTransposedPerSystemModule
inherit
(snow)
mkPerSystemFlakeOutput
;
in
mkTransposedPerSystemModule {
name = "devShells";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = { };
description = ''
An attribute set of packages to be used as shells.
[`nix develop .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop.html) will run `devShells.<name>`.
'';
example = literalExpression ''
{
default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ wget bat cargo ];
};
}
'';
};
file = ./devShells.nix;
}
mkPerSystemFlakeOutput {
name = "devShells";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = {};
description = ''
An attribute set of packages to be used as shells.
[`nix develop .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop.html) will run `devShells.<name>`.
'';
example = literalExpression ''
{
default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ wget bat cargo ];
};
}
'';
};
file = ./devShells.nix;
}

View file

@ -1,52 +1,26 @@
{ config, lib, flake-parts-lib, ... }:
let
inherit (lib)
filterAttrs
mapAttrs
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
optionalAttrs
types
;
inherit (flake-parts-lib)
mkPerSystemOption
inherit
(snow)
mkPerSystemFlakeOutput
;
in
{
options = {
flake.formatter = mkOption {
type = types.lazyAttrsOf types.package;
default = { };
mkPerSystemFlakeOutput {
name = "formatter";
option = mkOption {
type = types.nullOr types.package;
default = null;
description = ''
An attribute set of per system a package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
A package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
'';
};
perSystem = mkPerSystemOption {
_file = ./formatter.nix;
options = {
formatter = mkOption {
type = types.nullOr types.package;
default = null;
description = ''
A package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
'';
};
};
};
};
config = {
flake.formatter =
mapAttrs
(k: v: v.formatter)
(filterAttrs
(k: v: v.formatter != null)
config.allSystems
);
perInput = system: flake:
optionalAttrs (flake?formatter.${system}) {
formatter = flake.formatter.${system};
};
};
}
file = ./apps.nix;
}

View file

@ -1,21 +1,26 @@
{ lib, flake-parts-lib, ... }:
let
inherit (lib)
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
types
;
inherit (flake-parts-lib)
mkTransposedPerSystemModule
inherit
(snow)
mkPerSystemFlakeOutput
;
in
mkTransposedPerSystemModule {
name = "legacyPackages";
option = mkOption {
type = types.lazyAttrsOf types.raw;
default = { };
description = ''
An attribute set of unmergeable values. This is also used by [`nix build .#<attrpath>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
'';
};
file = ./legacyPackages.nix;
}
mkPerSystemFlakeOutput {
name = "legacyPackages";
option = mkOption {
type = types.lazyAttrsOf types.raw;
default = {};
description = ''
Used for nixpkgs packages, also accessible via `nix build .#<name>` [`nix build .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
'';
};
file = ./legacyPackages.nix;
}

View file

@ -1,32 +0,0 @@
{ withSystem, ... }:
{
config = {
_module.args = {
moduleWithSystem =
module:
{ config, ... }:
let
system =
config._module.args.system or
config._module.args.pkgs.stdenv.hostPlatform.system or
(throw "moduleWithSystem: Could not determine the configuration's system parameter for this module system application.");
allArgs = withSystem system (args: args);
lazyArgsPerParameter = f: builtins.mapAttrs
(k: v: allArgs.${k} or (throw "moduleWithSystem: module argument `${k}` does not exist."))
(builtins.functionArgs f);
# Use reflection to make the call lazy in the argument.
# Restricts args to the ones declared.
callLazily = f: a: f (lazyArgsPerParameter f);
in
{
imports = [
(callLazily module allArgs)
];
};
};
};
}

View file

@ -1,16 +1,15 @@
{ lib, ... }:
let
inherit (lib)
{lib, ...}: let
inherit
(lib)
mkOption
types
literalExpression
;
in
{
in {
options = {
flake.nixosConfigurations = mkOption {
outputs.nixosConfigurations = mkOption {
type = types.lazyAttrsOf types.raw;
default = { };
default = {};
description = ''
Instantiated NixOS configurations. Used by `nixos-rebuild`.

View file

@ -1,20 +1,23 @@
{ self, lib, moduleLocation, ... }:
let
inherit (lib)
{
lib,
moduleLocation,
...
}: let
inherit
(lib)
mapAttrs
mkOption
types
;
in
{
in {
options = {
flake.nixosModules = mkOption {
outputs.nixosModules = mkOption {
type = types.lazyAttrsOf types.deferredModule;
default = { };
default = {};
apply = mapAttrs (k: v: {
_class = "nixos";
_file = "${toString moduleLocation}#nixosModules.${k}";
imports = [ v ];
imports = [v];
});
description = ''
NixOS modules.

View file

@ -1,26 +0,0 @@
#
# Nixpkgs module. The only exception to the rule.
#
# Provides a `pkgs` argument in `perSystem`.
#
# Arguably, this shouldn't be in flake-parts, but in nixpkgs.
# Nixpkgs could define its own module that does this, which would be
# a more consistent UX, but for now this will do.
#
# The existence of this module does not mean that other flakes' logic
# will be accepted into flake-parts, because it's against the
# spirit of Flakes.
#
{
config = {
perSystem = { inputs', lib, ... }: {
config = {
_module.args.pkgs = lib.mkOptionDefault (
builtins.seq
(inputs'.nixpkgs or (throw "flake-parts: The flake does not have a `nixpkgs` input. Please add it, or set `perSystem._module.args.pkgs` yourself."))
inputs'.nixpkgs.legacyPackages
);
};
};
};
}

View file

@ -9,7 +9,7 @@
types
;
flake = mkOption {
outputs = mkOption {
type = types.submoduleWith {
modules = [
{
@ -39,8 +39,7 @@
};
in {
options = {
inherit flake;
output = {inherit flake;};
inherit outputs;
};
config = {inherit (config) flake;};

View file

@ -1,19 +1,18 @@
{ lib, ... }:
let
inherit (lib)
{lib, ...}: let
inherit
(lib)
mkOption
types
;
in
{
in {
options = {
flake.overlays = mkOption {
outputs.overlays = mkOption {
# uniq -> ordered: https://github.com/NixOS/nixpkgs/issues/147052
# also update description when done
type = types.lazyAttrsOf (types.uniq (types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified))));
# This eta expansion exists for the sole purpose of making nix flake check happy.
apply = lib.mapAttrs (_k: f: final: prev: f final prev);
default = { };
default = {};
example = lib.literalExpression ''
{
default = final: prev: {};

View file

@ -1,23 +1,29 @@
{ lib, flake-parts-lib, ... }:
let
inherit (lib)
{
lib,
snow,
...
}: let
inherit
(lib)
mkOption
types
;
inherit (flake-parts-lib)
mkTransposedPerSystemModule
inherit
(snow)
mkPerSystemFlakeOutput
;
in
mkTransposedPerSystemModule {
name = "packages";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = { };
description = ''
An attribute set of packages to be built by [`nix build`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
mkPerSystemFlakeOutput {
name = "packages";
option = mkOption {
type = types.lazyAttrsOf types.package;
default = {};
description = ''
An attribute set of packages to be built by [`nix build`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
`nix build .#<name>` will build `packages.<name>`.
'';
};
file = ./packages.nix;
}
`nix build .#<name>` will build `packages.<name>`.
'';
};
file = ./packages.nix;
}

View file

@ -1,159 +0,0 @@
{ config, lib, flake-parts-lib, self, ... }:
let
inherit (lib)
genAttrs
mapAttrs
mkOption
types
;
inherit (lib.strings)
escapeNixIdentifier
;
inherit (flake-parts-lib)
mkPerSystemType
;
rootConfig = config;
# Stubs for self and inputs. While it'd be possible to define aliases
# inside perSystem, that is not a general solution, and it would make
# top.config harder to discover, stretching the learning curve rather
# than flattening it.
throwAliasError' = param:
throw ''
`${param}` (without `'`) is not a `perSystem` module argument, but a
module argument of the top level config.
The following is an example usage of `${param}`. Note that its binding
is in the `top` parameter list, which is declared by the top level module
rather than the `perSystem` module.
top@{ config, lib, ${param}, ... }: {
perSystem = { config, ${param}', ... }: {
# in scope here:
# - ${param}
# - ${param}'
# - config (of perSystem)
# - top.config (note the `top@` pattern)
};
}
'';
throwAliasError = param:
throw ''
`${param}` is not a `perSystem` module argument, but a module argument of
the top level config.
The following is an example usage of `${param}`. Note that its binding
is in the `top` parameter list, which is declared by the top level module
rather than the `perSystem` module.
top@{ config, lib, ${param}, ... }: {
perSystem = { config, ... }: {
# in scope here:
# - ${param}
# - config (of perSystem)
# - top.config (note the `top@` pattern)
};
}
'';
/**
We primarily use `systems` to help memoize the per system context, but that
doesn't extend to arbitrary `system`s.
For that, we use the slightly less efficient, but perfectly acceptable
`memoizeStr` function.
*/
otherMemoizedSystems = flake-parts-lib.memoizeStr config.perSystem;
in
{
options = {
systems = mkOption {
description = ''
All the system types to enumerate in the flake output subattributes.
In other words, all valid values for `system` in e.g. `packages.<system>.foo`.
'';
type = types.listOf types.str;
};
perInput = mkOption {
description = ''
A function that pre-processes flake inputs.
It is called for users of `perSystem` such that `inputs'.''${name} = config.perInput system inputs.''${name}`.
This is used for [`inputs'`](../module-arguments.html#inputs) and [`self'`](../module-arguments.html#self).
The attributes returned by the `perInput` function definitions are merged into a single namespace (per input),
so each module should return an attribute set with usually only one or two predictable attribute names. Otherwise,
the `inputs'` namespace gets polluted.
'';
type = types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified));
};
perSystem = mkOption {
description = ''
A function from system to flake-like attributes omitting the `<system>` attribute.
Modules defined here have access to the suboptions and [some convenient module arguments](../module-arguments.html).
'';
type = mkPerSystemType ({ config, system, ... }: {
_file = ./perSystem.nix;
config = {
_module.args.inputs' =
mapAttrs
(inputName: input:
builtins.addErrorContext "while retrieving system-dependent attributes for input ${escapeNixIdentifier inputName}" (
if input._type or null == "flake"
then rootConfig.perInput system input
else
throw "Trying to retrieve system-dependent attributes for input ${escapeNixIdentifier inputName}, but this input is not a flake. Perhaps flake = false was added to the input declarations by mistake, or you meant to use a different input, or you meant to use plain old inputs, not inputs'."
)
)
self.inputs;
_module.args.self' =
builtins.addErrorContext "while retrieving system-dependent attributes for a flake's own outputs" (
rootConfig.perInput system self
);
# Custom error messages
_module.args.self = throwAliasError' "self";
_module.args.inputs = throwAliasError' "inputs";
_module.args.getSystem = throwAliasError "getSystem";
_module.args.withSystem = throwAliasError "withSystem";
_module.args.moduleWithSystem = throwAliasError "moduleWithSystem";
};
});
apply = modules: system:
(lib.evalModules {
inherit modules;
prefix = [ "perSystem" system ];
specialArgs = {
inherit system;
};
class = "perSystem";
}).config;
};
allSystems = mkOption {
type = types.lazyAttrsOf types.unspecified;
description = "The system-specific config for each of systems.";
internal = true;
};
};
config = {
allSystems = genAttrs config.systems config.perSystem;
_module.args.getSystem = system: config.allSystems.${system} or (otherMemoizedSystems system);
# The warning is there for a reason. Only use this in situations where the
# performance cost has already been incurred, such as in `flakeModules.easyOverlay`,
# where we run in the context of an overlay, and the performance cost of the
# extra `pkgs` makes the cost of running `perSystem` probably negligible.
_module.args.getSystemIgnoreWarning = system: config.allSystems.${system} or (config.perSystem system);
};
}

View file

@ -1,132 +0,0 @@
{ config, lib, flake-parts-lib, ... }:
let
inherit (lib)
filterAttrs
mapAttrs
mkOption
types
;
inherit (lib.strings)
escapeNixIdentifier
;
transpositionModule = {
options = {
adHoc = mkOption {
type = types.bool;
default = false;
description = ''
Whether to provide a stub option declaration for {option}`perSystem.<name>`.
The stub option declaration does not support merging and lacks
documentation, so you are recommended to declare the {option}`perSystem.<name>`
option yourself and avoid {option}`adHoc`.
'';
};
};
};
perInputAttributeError = { flake, attrName, system, attrConfig }:
# This uses flake.outPath for lack of a better identifier.
# Consider adding a perInput variation that has a normally-redundant argument for the input name.
# Tested manually with
# perSystem = { inputs', ... }: {
# packages.extra = inputs'.nixpkgs.extra;
# packages.default = inputs'.nixpkgs.packages.default;
# packages.veryWrong = (top.config.perInput "x86_64-linux" inputs'.nixpkgs.legacyPackages.hello).packages.default;
# };
# transposition.extra = {};
let
attrPath = "${escapeNixIdentifier attrName}.${escapeNixIdentifier system}";
flakeIdentifier =
if flake._type or null != "flake"
then
throw "An attempt was made to access attribute ${attrPath} on a value that's supposed to be a flake, but may not be a proper flake."
else
builtins.addErrorContext "while trying to find out how to describe what is supposedly a flake, whose attribute ${attrPath} was accessed but does not exist" (
toString flake.outPath
);
# This ought to be generalized by extending attrConfig, but this is the only known and common mistake for now.
alternateAttrNameHint =
if attrName == "packages" && flake?legacyPackages
then # Unfortunately we can't just switch them out, because that will put packages *sets* where single packages are expected in user code, resulting in potentially much worse and more confusing errors down the line.
"\nIt does define legacyPackages; try that instead?"
else "";
in
if flake?${attrName}
then
throw ''
Attempt to access ${attrPath} of flake ${flakeIdentifier}, but it does not have it.
It does have attribute ${escapeNixIdentifier attrName}, so it appears that it does not support system type ${escapeNixIdentifier system}.
''
else
throw ''
Attempt to access ${attrPath} of flake ${flakeIdentifier}, but it does not have attribute ${escapeNixIdentifier attrName}.${alternateAttrNameHint}
'';
in
{
options = {
transposition = lib.mkOption {
description = ''
A helper that defines transposed attributes in the flake outputs.
When you define `transposition.foo = { };`, definitions are added to the effect of (pseudo-code):
```nix
flake.foo.''${system} = (perSystem system).foo;
perInput = system: inputFlake: inputFlake.foo.''${system};
```
Transposition is the operation that swaps the indices of a data structure.
Here it refers specifically to the transposition between
```plain
perSystem: .''${system}.''${attribute}
outputs: .''${attribute}.''${system}
```
It also defines the reverse operation in [{option}`perInput`](#opt-perInput).
'';
type =
types.lazyAttrsOf
(types.submoduleWith { modules = [ transpositionModule ]; });
};
};
config = {
flake =
lib.mapAttrs
(attrName: attrConfig:
mapAttrs
(system: v: v.${attrName} or (
abort ''
Could not find option ${attrName} in the perSystem module. It is required to declare such an option whenever transposition.<name> is defined (and in this instance <name> is ${attrName}).
''))
config.allSystems
)
config.transposition;
perInput =
system: flake:
mapAttrs
(attrName: attrConfig:
flake.${attrName}.${system} or (
throw (perInputAttributeError { inherit system flake attrName attrConfig; })
)
)
config.transposition;
perSystem = {
options =
mapAttrs
(k: v: lib.mkOption { })
(filterAttrs
(k: v: v.adHoc)
config.transposition
);
};
};
}

View file

@ -1,37 +0,0 @@
{ lib, flake-parts-lib, getSystem, ... }:
let
inherit (lib)
mkOption
types
;
inherit (flake-parts-lib)
mkPerSystemOption
;
in
{
options = {
perSystem = mkPerSystemOption ({ config, options, specialArgs, ... }: {
_file = ./perSystem.nix;
options = {
allModuleArgs = mkOption {
type = types.lazyAttrsOf (types.raw or types.unspecified);
internal = true;
readOnly = true;
description = "Internal option that exposes _module.args, for use by withSystem.";
};
};
config = {
allModuleArgs = config._module.args // specialArgs // { inherit config options; };
};
});
};
config = {
_module.args = {
withSystem =
system: f:
f
(getSystem system).allModuleArgs;
};
};
}