Add postprocessing, add flakeModules.touchup

This commit is contained in:
Robert Hensing 2025-05-27 14:32:12 +02:00
parent 157c95de4d
commit 9d0762c18a
7 changed files with 334 additions and 2 deletions

57
extras/touchup.nix Normal file
View 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
View 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
View 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);
};
}