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
|
||||
{ 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 = {
|
||||
"test: conditional null throws helpful error" = {
|
||||
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;
|
||||
partitions = ./extras/partitions.nix;
|
||||
bundlers = ./extras/bundlers.nix;
|
||||
touchup = ./extras/touchup.nix;
|
||||
};
|
||||
in
|
||||
lib.mkFlake { inherit inputs; } {
|
||||
|
|
|
|||
2
lib.nix
2
lib.nix
|
|
@ -159,7 +159,7 @@ let
|
|||
let
|
||||
eval = flake-parts-lib.evalFlakeModule args module;
|
||||
in
|
||||
eval.config.flake;
|
||||
eval.config.processedFlake;
|
||||
|
||||
/**
|
||||
Deprecated. Declare options directly, e.g. `options.foo.bar = mkOption { ... }`,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{ lib, ... }:
|
||||
{ config, lib, options, ... }:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
|
|
@ -33,6 +33,27 @@ in
|
|||
Raw flake output attributes. Any attribute can be set here, but some
|
||||
attributes are represented by options, to provide appropriate
|
||||
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