Add postprocessing, add flakeModules.touchup
This commit is contained in:
parent
157c95de4d
commit
9d0762c18a
7 changed files with 334 additions and 2 deletions
|
|
@ -30,6 +30,14 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Replace derivations with a simple attrset so nix-unit can compare without
|
||||||
|
# hitting stack overflow on self-referential derivation attrsets.
|
||||||
|
# See NOTE: Derivation comparison
|
||||||
|
canon = v:
|
||||||
|
if v.type or null == "derivation" then { type = "derivation"; inherit (v) drvPath outPath name system; }
|
||||||
|
else if builtins.isAttrs v then builtins.mapAttrs (_: canon) v
|
||||||
|
else v;
|
||||||
|
|
||||||
empty = mkFlake
|
empty = mkFlake
|
||||||
{ inputs.self = { }; }
|
{ inputs.self = { }; }
|
||||||
{
|
{
|
||||||
|
|
@ -465,6 +473,100 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
touchup = {
|
||||||
|
"test: any filter keeps only matching attrs" = {
|
||||||
|
expr =
|
||||||
|
let
|
||||||
|
result = mkFlake { inputs.self = { }; } {
|
||||||
|
imports = [ flake-parts.flakeModules.touchup ];
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
touchup.any = { attrName, ... }: { enable = attrName == "overlays"; };
|
||||||
|
perSystem = { ... }: {
|
||||||
|
packages.default = throw "packages.default should not be evaluated";
|
||||||
|
packages.hello = throw "packages.hello should not be evaluated";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
result;
|
||||||
|
expected = {
|
||||||
|
overlays = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"test: any with mkDefault, attr override" = {
|
||||||
|
expr =
|
||||||
|
let
|
||||||
|
result = mkFlake { inputs.self = { }; } {
|
||||||
|
imports = [ flake-parts.flakeModules.touchup ];
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
touchup.any = { ... }: { enable = lib.mkDefault false; };
|
||||||
|
touchup.attr.overlays = { enable = true; };
|
||||||
|
perSystem = { ... }: {
|
||||||
|
packages.default = throw "packages.default should not be evaluated";
|
||||||
|
packages.hello = throw "packages.hello should not be evaluated";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
result;
|
||||||
|
expected = {
|
||||||
|
overlays = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"test: nested attr filtering per system" = {
|
||||||
|
expr =
|
||||||
|
let
|
||||||
|
result = mkFlake { inputs.self = { }; } {
|
||||||
|
imports = [ flake-parts.flakeModules.touchup ];
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
touchup.attr.packages.attr.aarch64-darwin.attr.bar.enable = false;
|
||||||
|
perSystem = { system, ... }: {
|
||||||
|
packages.foo = pkg system "foo";
|
||||||
|
# This assertion proves the filtered value is never evaluated for darwin
|
||||||
|
packages.bar = assert system == "x86_64-linux"; pkg system "bar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
canon result.packages;
|
||||||
|
expected = canon {
|
||||||
|
aarch64-darwin = { foo = pkg "aarch64-darwin" "foo"; };
|
||||||
|
x86_64-linux = { foo = pkg "x86_64-linux" "foo"; bar = pkg "x86_64-linux" "bar"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"test: finish and attr composition" = {
|
||||||
|
expr =
|
||||||
|
mkFlake { inputs.self = { }; } {
|
||||||
|
imports = [ flake-parts.flakeModules.touchup ];
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
touchup.any = { ... }: { enable = lib.mkDefault false; };
|
||||||
|
touchup.attr.overlays = { enable = true; finish = x: "hoi"; };
|
||||||
|
touchup.finish = x: x // { foo = "bar"; };
|
||||||
|
};
|
||||||
|
expected = {
|
||||||
|
overlays = "hoi";
|
||||||
|
foo = "bar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO: assert that the error context ("while touching up attribute 'broken'")
|
||||||
|
# appears in the trace. nix-unit's expectedError.msg only matches the thrown
|
||||||
|
# message, not addErrorContext frames.
|
||||||
|
"test: error context when enabled attr throws" = {
|
||||||
|
expr =
|
||||||
|
let
|
||||||
|
result = mkFlake { inputs.self = { }; } {
|
||||||
|
imports = [ flake-parts.flakeModules.touchup ];
|
||||||
|
systems = [ "x86_64-linux" ];
|
||||||
|
flake.broken = throw "the value is broken";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
result.broken;
|
||||||
|
expectedError.type = "ThrownError";
|
||||||
|
expectedError.msg = "the value is broken";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
formatter = {
|
formatter = {
|
||||||
"test: conditional null throws helpful error" = {
|
"test: conditional null throws helpful error" = {
|
||||||
expr =
|
expr =
|
||||||
|
|
|
||||||
57
extras/touchup.nix
Normal file
57
extras/touchup.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
touchup = mkOption {
|
||||||
|
description = ''
|
||||||
|
Controls which attributes appear in [`processedFlake`](flake-parts.md#opt-processedFlake) and how they are transformed.
|
||||||
|
|
||||||
|
The touchup configuration forms a tree that mirrors the flake output structure.
|
||||||
|
At each level, [`attr`](#opt-touchup.attr) targets specific attributes by name,
|
||||||
|
and [`any`](#opt-touchup.any) applies to all attributes at that level.
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
Only output explicitly listed flake output attributes:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
touchup = {
|
||||||
|
any = {
|
||||||
|
enable = lib.mkDefault false;
|
||||||
|
};
|
||||||
|
attr.packages.enable = true;
|
||||||
|
attr.checks.enable = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Hide a package from users, but not from your own modules:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
touchup = {
|
||||||
|
attr.packages.any.attr.hello.enable = false;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Hide a package on a set of systems:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
touchup = {
|
||||||
|
attr.packages.any = { attrName, ... }: { attr.hello.enable = ! lib.strings.hasSuffix "-darwin" attrName; }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
'';
|
||||||
|
type = types.submoduleWith {
|
||||||
|
modules = [ ./touchup/attrs.nix ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
processedFlake = config.touchup.touchupApply config.flake;
|
||||||
|
};
|
||||||
|
}
|
||||||
20
extras/touchup/attr.nix
Normal file
20
extras/touchup/attr.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [ ./attrs.nix ];
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to include the attribute in the output.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
_module.args.docsVisible = "shallow";
|
||||||
|
};
|
||||||
|
}
|
||||||
131
extras/touchup/attrs.nix
Normal file
131
extras/touchup/attrs.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{ config, lib, options, docsVisible, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
addErrorContext
|
||||||
|
concatMapAttrs
|
||||||
|
evalModules
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
unNull = v:
|
||||||
|
if v == null then
|
||||||
|
{ }
|
||||||
|
else
|
||||||
|
v;
|
||||||
|
|
||||||
|
touchupAttrs =
|
||||||
|
v:
|
||||||
|
concatMapAttrs
|
||||||
|
(name: value:
|
||||||
|
let
|
||||||
|
eval = evalModules {
|
||||||
|
prefix = lib.lists.init options.attr.loc; # arbitrary pick
|
||||||
|
specialArgs = {
|
||||||
|
attrName = name;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
(config.attr.${name} or ./attr.nix)
|
||||||
|
(unNull config.any)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
if addErrorContext "while figuring out whether to enable '${name}'" eval.config.enable then
|
||||||
|
# Apply the touchup configuration to the value.
|
||||||
|
{
|
||||||
|
"${name}" =
|
||||||
|
addErrorContext "while touching up attribute '${name}'" (
|
||||||
|
(addErrorContext "while evaluating the touchup configuration for '${name}'" eval.config.touchupApply)
|
||||||
|
(addErrorContext "while evaluating the original value of '${name}'" value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ }
|
||||||
|
)
|
||||||
|
v;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
attr = mkOption {
|
||||||
|
type = types.lazyAttrsOf (types.deferredModuleWith {
|
||||||
|
staticModules = [ ./attr.nix ];
|
||||||
|
});
|
||||||
|
default = { };
|
||||||
|
visible = docsVisible;
|
||||||
|
description =
|
||||||
|
if docsVisible == "shallow" then ''
|
||||||
|
Touchup configuration for the next level of nesting.
|
||||||
|
Same structure as [`attr`](#opt-touchup.attr); see its description.
|
||||||
|
''
|
||||||
|
else ''
|
||||||
|
Per-attribute touchup configuration. Each value is a module that controls
|
||||||
|
whether and how the corresponding attribute appears in the output.
|
||||||
|
|
||||||
|
Each module contains the full touchup option set (`enable`, `attr`, `any`, `finish`),
|
||||||
|
so nested attributes can be configured to arbitrary depth.
|
||||||
|
|
||||||
|
This module is called with module argument `attrName`, which is the name of the attribute being touched up.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
any = mkOption {
|
||||||
|
type = types.nullOr (types.deferredModuleWith {
|
||||||
|
staticModules = [ ./attr.nix ];
|
||||||
|
});
|
||||||
|
default = null;
|
||||||
|
visible = docsVisible;
|
||||||
|
description =
|
||||||
|
if docsVisible == "shallow" then ''
|
||||||
|
Default configuration for all attributes at the next level.
|
||||||
|
Same structure as [`any`](#opt-touchup.any); see its description.
|
||||||
|
''
|
||||||
|
else ''
|
||||||
|
A module whose options are merged into every attribute's touchup configuration.
|
||||||
|
For example, `any.enable = false` disables all attributes by default.
|
||||||
|
Override specific ones via `attr.<name>`.
|
||||||
|
|
||||||
|
Only applies to immediate children — does not recurse into nested attributes automatically.
|
||||||
|
|
||||||
|
This module is called with module argument `attrName`, which is the name of the attribute being touched up.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
type = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
default = types.raw;
|
||||||
|
defaultText = "raw";
|
||||||
|
description = ''
|
||||||
|
The type used for merging multiple definitions of ${options.finish}.
|
||||||
|
Override this if multiple modules need to compose their ${options.finish} functions.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
finish = mkOption {
|
||||||
|
type = types.functionTo config.type;
|
||||||
|
default = v: v;
|
||||||
|
defaultText = lib.literalMD "`v: v`, the identity function";
|
||||||
|
description = ''
|
||||||
|
A function applied after filtering and transforming (e.g. by ${options.attr} and ${options.any} at this level).
|
||||||
|
It receives the resulting attribute set and must return the value to use in its place.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
touchupApply = mkOption {
|
||||||
|
internal = true;
|
||||||
|
description = ''
|
||||||
|
A generated function that applies the touchups that are configured with the other options in this module.
|
||||||
|
'';
|
||||||
|
type = types.functionTo types.raw;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
_module.args.docsVisible = lib.mkDefault true;
|
||||||
|
touchupApply = v:
|
||||||
|
config.finish
|
||||||
|
(if config.attr != { } || config.any != null
|
||||||
|
then
|
||||||
|
addErrorContext "while applying touchups from ${options.attr}: ${lib.options.showDefs options.attr.definitionsWithLocations}\n and from ${options.any}: ${lib.options.showDefs options.any.definitionsWithLocations}"
|
||||||
|
(touchupAttrs v)
|
||||||
|
else v);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
modules = ./extras/modules.nix;
|
modules = ./extras/modules.nix;
|
||||||
partitions = ./extras/partitions.nix;
|
partitions = ./extras/partitions.nix;
|
||||||
bundlers = ./extras/bundlers.nix;
|
bundlers = ./extras/bundlers.nix;
|
||||||
|
touchup = ./extras/touchup.nix;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib.mkFlake { inherit inputs; } {
|
lib.mkFlake { inherit inputs; } {
|
||||||
|
|
|
||||||
2
lib.nix
2
lib.nix
|
|
@ -159,7 +159,7 @@ let
|
||||||
let
|
let
|
||||||
eval = flake-parts-lib.evalFlakeModule args module;
|
eval = flake-parts-lib.evalFlakeModule args module;
|
||||||
in
|
in
|
||||||
eval.config.flake;
|
eval.config.processedFlake;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deprecated. Declare options directly, e.g. `options.foo.bar = mkOption { ... }`,
|
Deprecated. Declare options directly, e.g. `options.foo.bar = mkOption { ... }`,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ lib, ... }:
|
{ config, lib, options, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib)
|
inherit (lib)
|
||||||
mkOption
|
mkOption
|
||||||
|
|
@ -33,6 +33,27 @@ in
|
||||||
Raw flake output attributes. Any attribute can be set here, but some
|
Raw flake output attributes. Any attribute can be set here, but some
|
||||||
attributes are represented by options, to provide appropriate
|
attributes are represented by options, to provide appropriate
|
||||||
configuration merging.
|
configuration merging.
|
||||||
|
|
||||||
|
Further processing may be applied to these attributes. See [`processedFlake`](flake-parts.md#opt-processedFlake) for more information.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
processedFlake = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
readOnly = true;
|
||||||
|
apply =
|
||||||
|
if options.processedFlake.isDefined
|
||||||
|
then x: x
|
||||||
|
else x: config.flake;
|
||||||
|
description = ''
|
||||||
|
The final flake output, as returned by `mkFlake`.
|
||||||
|
|
||||||
|
This separates two concerns:
|
||||||
|
|
||||||
|
- [`flake`](#opt-flake): the internal representation, where all attributes are available for use by other modules, regardless of whether they evaluate successfully for all configurations.
|
||||||
|
- `processedFlake`: the external output, which may have attributes removed or transformed to satisfy tools like `nix flake check`.
|
||||||
|
|
||||||
|
By default, `processedFlake` equals `flake` (no processing).
|
||||||
|
Import a module such as [`flakeModules.touchup`](#opt-touchup) to define it, or set it directly.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue