stylix: add mkTarget function
Add the mkTarget function, providing a consistent target interface to minimize boilerplate
and automatically safeguard declarations related to disabled options.
The mkTarget function was first discussed in [1] ("extensive mkTarget
function").
[1]: https://github.com/danth/stylix/discussions/1009
This commit is contained in:
parent
42b1521816
commit
c4fa684471
3 changed files with 294 additions and 11 deletions
|
|
@ -41,15 +41,34 @@ folder, using any name which is not on the list above.
|
||||||
|
|
||||||
## Module template
|
## Module template
|
||||||
|
|
||||||
All modules should have an enable option created using `mkEnableTarget`. This is
|
Modules should be created using the `mkTarget` function whenever possible (see
|
||||||
similar to
|
the [`/stylix/mk-target.nix`](
|
||||||
[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
|
https://github.com/danth/stylix/blob/-/stylix/mk-target.nix) in-source
|
||||||
from the standard library, however it integrates with
|
documentation for more details):
|
||||||
[`stylix.enable`](./options/nixos.md#stylixenable) and
|
|
||||||
[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
|
|
||||||
specific documentation.
|
|
||||||
|
|
||||||
A general format for modules is shown below.
|
```nix
|
||||||
|
{ config, lib, mkTarget ... }:
|
||||||
|
mkTarget {
|
||||||
|
name = "«name»";
|
||||||
|
humanName = "«human readable name»";
|
||||||
|
|
||||||
|
configElements =
|
||||||
|
{ colors }:
|
||||||
|
{
|
||||||
|
programs.«name».theme.background = colors.base00;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The `mkTarget` argument is only available to modules imported by Stylix's
|
||||||
|
> [autoload system](https://github.com/danth/stylix/blob/-/stylix/autoload.nix),
|
||||||
|
> e.g., `modules/«target»/«platform».nix` modules.
|
||||||
|
>
|
||||||
|
> I.e., it is not available to normal modules imported via the `imports` list.
|
||||||
|
|
||||||
|
When the `mkTarget` function cannot be used, modules must manually replicate its
|
||||||
|
safeguarding behaviour:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
@ -66,8 +85,8 @@ A general format for modules is shown below.
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> You **must** check _both_ `config.stylix.enable` _and_ your target's own
|
> If not using `mkTarget`, you **must** check _both_ `config.stylix.enable`
|
||||||
> `enable` option before defining any config.
|
> _and_ your target's own`enable` option before defining any config.
|
||||||
>
|
>
|
||||||
> In the above example this is done using
|
> In the above example this is done using
|
||||||
> `config = lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)`.
|
> `config = lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)`.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,44 @@ builtins.concatLists (
|
||||||
path: kind:
|
path: kind:
|
||||||
let
|
let
|
||||||
file = "${inputs.self}/modules/${path}/${for}.nix";
|
file = "${inputs.self}/modules/${path}/${for}.nix";
|
||||||
|
module = import file;
|
||||||
|
|
||||||
|
# Detect whether the file's value has an argument named `mkTarget`
|
||||||
|
useMkTarget =
|
||||||
|
builtins.isFunction module && (builtins.functionArgs module) ? mkTarget;
|
||||||
|
|
||||||
|
# NOTE: `mkTarget` cannot be distributed normally through the module system
|
||||||
|
# due to issues of infinite recursion.
|
||||||
|
mkTarget = import ./mk-target.nix;
|
||||||
in
|
in
|
||||||
lib.optional (kind == "directory" && builtins.pathExists file) file
|
lib.optional (kind == "directory" && builtins.pathExists file) (
|
||||||
|
if useMkTarget then
|
||||||
|
{ config, ... }@args:
|
||||||
|
let
|
||||||
|
# Based on `lib.modules.applyModuleArgs`
|
||||||
|
#
|
||||||
|
# Apply `mkTarget` as a special arg without actually using `specialArgs`,
|
||||||
|
# which cannot be defined from within a configuration.
|
||||||
|
context =
|
||||||
|
name: ''while evaluating the module argument `${name}' in "${toString file}":'';
|
||||||
|
extraArgs = lib.pipe module [
|
||||||
|
builtins.functionArgs
|
||||||
|
(lib.flip builtins.removeAttrs [ "mkTarget" ])
|
||||||
|
(builtins.mapAttrs (
|
||||||
|
name: _:
|
||||||
|
builtins.addErrorContext (context name) (
|
||||||
|
args.${name} or config._module.args.${name}
|
||||||
|
)
|
||||||
|
))
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
key = file;
|
||||||
|
_file = file;
|
||||||
|
imports = [ (module (args // extraArgs // { inherit mkTarget; })) ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
file
|
||||||
|
)
|
||||||
) (builtins.readDir "${inputs.self}/modules")
|
) (builtins.readDir "${inputs.self}/modules")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
227
stylix/mk-target.nix
Normal file
227
stylix/mk-target.nix
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
/**
|
||||||
|
Provides a consistent target interface, minimizing boilerplate and
|
||||||
|
automatically safeguarding declarations related to disabled options.
|
||||||
|
|
||||||
|
# Type
|
||||||
|
|
||||||
|
```
|
||||||
|
mkTarget :: AttrSet -> ModuleBody
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `ModuleBody` is a module that doesn't take any arguments. This allows
|
||||||
|
the caller to use module arguments.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ mkTarget, lib... }:
|
||||||
|
mkTarget {
|
||||||
|
name = "«name»";
|
||||||
|
humanName = "«human readable name»";
|
||||||
|
|
||||||
|
generalConfig =
|
||||||
|
lib.mkIf complexCondition {
|
||||||
|
home.packages = [ pkgs.hello ];
|
||||||
|
};
|
||||||
|
|
||||||
|
configElements = [
|
||||||
|
{ programs.«name».theme.name = "stylix"; }
|
||||||
|
|
||||||
|
(
|
||||||
|
{ colors }:
|
||||||
|
{
|
||||||
|
programs.«name».theme.background = colors.base00;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
{ fonts }:
|
||||||
|
{
|
||||||
|
programs.«name».font.name = fonts.monospace.name;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
|
||||||
|
`config` (Attribute set)
|
||||||
|
|
||||||
|
: `name` (String)
|
||||||
|
: The target name used to generate options in the `stylix.targets.${name}`
|
||||||
|
namespace.
|
||||||
|
|
||||||
|
`humanName` (String)
|
||||||
|
: The descriptive target name passed to the lib.mkEnableOption function
|
||||||
|
when generating the `stylix.targets.${name}.enable` option.
|
||||||
|
|
||||||
|
`autoEnable` (Boolean)
|
||||||
|
: Whether the target should be automatically enabled by default according
|
||||||
|
to the `stylix.autoEnable` option.
|
||||||
|
|
||||||
|
This should be disabled if manual setup is required or if auto-enabling
|
||||||
|
causes issues.
|
||||||
|
|
||||||
|
`extraOptions` (Attribute set)
|
||||||
|
: Additional options to be added in the `stylix.targets.${name}` namespace
|
||||||
|
along the `stylix.targets.${name}.enable` option.
|
||||||
|
|
||||||
|
For example, an extension guard used in the configuration can be declared
|
||||||
|
as follows:
|
||||||
|
```nix
|
||||||
|
{ extension.enable = lib.mkEnableOption "the bloated dependency"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
`configElements` (List or attribute set or function)
|
||||||
|
: Configuration functions that are automatically safeguarded when any of
|
||||||
|
their arguments is disabled. The provided `cfg` argument conveniently
|
||||||
|
aliases to `config.stylix.targets.${name}`.
|
||||||
|
|
||||||
|
For example, the following configuration is not merged if the stylix
|
||||||
|
colors option is null:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(
|
||||||
|
{ colors }:
|
||||||
|
{
|
||||||
|
programs.«name».theme.background = colors.base00;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `cfg` alias can be accessed as follows:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(
|
||||||
|
{ cfg }:
|
||||||
|
{
|
||||||
|
programs.«name».extension.enable = cfg.extension.enable;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`generalConfig` (Attribute set or function)
|
||||||
|
: This argument mirrors the `configElements` argument but intentionally
|
||||||
|
lacks automatic safeguarding and should only be used for complex
|
||||||
|
configurations where `configElements` is unsuitable.
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
The function is provided alongside module arguments in any modules imported
|
||||||
|
through `/stylix/autoload.nix`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# TODO: Ideally, in the future, this function returns an actual module by better
|
||||||
|
# integrating with the /stylix/autoload.nix logic, allowing the following target
|
||||||
|
# simplification and preventing access to unguarded module arguments by
|
||||||
|
# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
|
||||||
|
# of modules:
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# name = "example";
|
||||||
|
# humanName = "Example Target";
|
||||||
|
#
|
||||||
|
# generalConfig =
|
||||||
|
# { lib, pkgs }:
|
||||||
|
# lib.mkIf complexCondition {
|
||||||
|
# home.packages = [ pkgs.hello ];
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# configElements = [
|
||||||
|
# { programs.example.theme.name = "stylix"; }
|
||||||
|
#
|
||||||
|
# (
|
||||||
|
# { colors }:
|
||||||
|
# {
|
||||||
|
# programs.example.theme.background = colors.base00;
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# (
|
||||||
|
# { fonts }:
|
||||||
|
# {
|
||||||
|
# programs.example.font.name = fonts.monospace.name;
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# ];
|
||||||
|
# }
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
humanName,
|
||||||
|
autoEnable ? true,
|
||||||
|
extraOptions ? { },
|
||||||
|
configElements ? [ ],
|
||||||
|
generalConfig ? null,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
module =
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.stylix.targets.${name};
|
||||||
|
|
||||||
|
# Get the list of function de-structured argument names.
|
||||||
|
functionArgNames =
|
||||||
|
fn:
|
||||||
|
lib.pipe fn [
|
||||||
|
lib.functionArgs
|
||||||
|
builtins.attrNames
|
||||||
|
];
|
||||||
|
|
||||||
|
getStylixAttrs =
|
||||||
|
fn:
|
||||||
|
lib.genAttrs (functionArgNames fn) (
|
||||||
|
arg:
|
||||||
|
if arg == "cfg" then
|
||||||
|
cfg
|
||||||
|
else if arg == "colors" then
|
||||||
|
config.lib.stylix.colors
|
||||||
|
else
|
||||||
|
config.stylix.${arg}
|
||||||
|
or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
|
||||||
|
lib.concatMapStringsSep ", " (name: "`${name}`") (
|
||||||
|
builtins.attrNames config.stylix
|
||||||
|
)
|
||||||
|
}, but got: ${arg}")
|
||||||
|
);
|
||||||
|
|
||||||
|
# Call the configuration function with its required Stylix arguments.
|
||||||
|
mkConfig = fn: fn (getStylixAttrs fn);
|
||||||
|
|
||||||
|
# Safeguard configuration functions when any of their arguments is
|
||||||
|
# disabled, while non-function configurations are unguarded.
|
||||||
|
mkConditionalConfig =
|
||||||
|
c:
|
||||||
|
if builtins.isFunction c then
|
||||||
|
let
|
||||||
|
allAttrsNonNull = lib.pipe c [
|
||||||
|
getStylixAttrs
|
||||||
|
builtins.attrValues
|
||||||
|
(builtins.all (attr: attr != null))
|
||||||
|
];
|
||||||
|
in
|
||||||
|
lib.mkIf allAttrsNonNull (mkConfig c)
|
||||||
|
else
|
||||||
|
c;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.stylix.targets.${name}.enable =
|
||||||
|
config.lib.stylix.mkEnableTarget humanName autoEnable;
|
||||||
|
|
||||||
|
config = lib.mkIf (config.stylix.enable && cfg.enable) (
|
||||||
|
lib.mkMerge (
|
||||||
|
lib.optional (generalConfig != null) (mkConfig generalConfig)
|
||||||
|
++ map mkConditionalConfig (lib.toList configElements)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
{ options.stylix.targets.${name} = extraOptions; }
|
||||||
|
module
|
||||||
|
];
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue