treewide: use mkEnableTargetWith for dynamic autoEnable conditions (#1244)

Use the new `mkEnableTargetWith` function for dynamic `autoEnable`
conditions to prevent the evaluation of dynamic conditions during
documentation rendering.

The "same as `stylix.autoEnable`" `defaultText` is replaced with
`stylix.autoEnable` or `stylix.autoEnable && ${autoEnableExpr}`,
depending on whether `autoEnableExpr` is provided. For readability,
`autoEnable` expressions containing Nix operators of lower precedence
than `&&` are automatically wrapped in parentheses, unless
`autoWrapExpr` is disabled.

Closes: https://github.com/nix-community/stylix/issues/98
Link: https://github.com/nix-community/stylix/pull/1244

Reviewed-by: awwpotato <awwpotato@voidq.com>
Reviewed-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: Daniel Thwaites <danth@danth.me>
This commit is contained in:
Daniel Thwaites 2025-06-09 16:11:02 +01:00 committed by GitHub
commit d73d8f6a48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 173 additions and 39 deletions

View file

@ -106,6 +106,13 @@ the following applies:
- There is no reliable way to detect whether the target is installed, *and*
enabling it unconditionally would cause problems.
> [!CAUTION]
> The boolean value after `mkEnableTarget` should usually be a static `true` or
> `false` literal.
>
> Using a dynamic value requires you to document the dynamic expression using
> `mkEnableTargetWith`'s `autoEnableExpr` argument.
### Overlays
If your module is provided as an overlay it uses a special format, where config

View file

@ -15,6 +15,14 @@ mkTarget {
|| i3.enable
|| spectrwm.enable
|| xmonad.enable;
autoEnableExpr = ''
with config.xsession.windowManager;
bspwm.enable
|| herbstluftwm.enable
|| i3.enable
|| spectrwm.enable
|| xmonad.enable
'';
configElements =
{ imageScalingMode, image }:

View file

@ -11,6 +11,10 @@ mkTarget {
autoEnable =
with config.services.xserver.windowManager;
xmonad.enable || i3.enable;
autoEnableExpr = ''
with services.xserver.windowManager;
xmonad.enable || i3.enable
'';
configElements =
{ image, imageScalingMode }:

View file

@ -44,7 +44,11 @@ let
in
{
options.stylix.targets.gnome = {
enable = config.lib.stylix.mkEnableTarget "GNOME" pkgs.stdenv.hostPlatform.isLinux;
enable = config.lib.stylix.mkEnableTargetWith {
name = "GNOME";
autoEnable = pkgs.stdenv.hostPlatform.isLinux;
autoEnableExpr = "pkgs.stdenv.hostPlatform.isLinux";
};
useWallpaper = config.lib.stylix.mkEnableWallpaper "GNOME" true;
};

View file

@ -7,9 +7,11 @@
mkTarget {
name = "hyprland";
humanName = "Hyprland";
extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
config.stylix.image != null
);
extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTargetWith {
name = "Hyprpaper";
autoEnable = config.stylix.image != null;
autoEnableExpr = "stylix.image != null";
};
configElements = [
(
{ colors }:

View file

@ -13,9 +13,11 @@
#
# [1]: https://github.com/nix-community/stylix/issues/933
# [2]: https://github.com/nix-community/home-manager/issues/6565
enable = config.lib.stylix.mkEnableTarget "QT" (
pkgs.stdenv.hostPlatform.isLinux && osConfig != null
);
enable = config.lib.stylix.mkEnableTargetWith {
name = "QT";
autoEnable = pkgs.stdenv.hostPlatform.isLinux && osConfig != null;
autoEnableExpr = "pkgs.stdenv.hostPlatform.isLinux && osConfig != null";
};
platform = lib.mkOption {
description = ''

View file

@ -1,9 +1,4 @@
{
lib,
pkgs,
config,
...
}:
{ lib, config, ... }:
let
@ -16,7 +11,7 @@ let
in
{
options.stylix.targets.qt = {
enable = config.lib.stylix.mkEnableTarget "QT" pkgs.stdenv.hostPlatform.isLinux;
enable = config.lib.stylix.mkEnableTarget "QT" true;
platform = lib.mkOption {
description = ''
Selects the platform theme to use for Qt applications.

View file

@ -26,15 +26,19 @@ in
})
];
options.stylix.targets.swaylock = {
enable =
config.lib.stylix.mkEnableTarget "Swaylock"
# When the state version is older than 23.05, Swaylock enables itself
# automatically if `settings != {}` [1]. Therefore, Swaylock theming
# shouldn't be enabled by default for such state versions, to avoid
# inadvertently installing Swaylock when it's not desired.
#
# [1]: https://github.com/nix-community/home-manager/blob/5cfbf5cc37a3bd1da07ae84eea1b828909c4456b/modules/programs/swaylock.nix#L12-L17
(lib.versionAtLeast config.home.stateVersion "23.05");
enable = config.lib.stylix.mkEnableTargetWith {
name = "Swaylock";
# When the state version is older than 23.05, Swaylock enables itself
# automatically if `settings != {}` [1]. Therefore, Swaylock theming
# shouldn't be enabled by default for such state versions, to avoid
# inadvertently installing Swaylock when it's not desired.
#
# [1]: https://github.com/nix-community/home-manager/blob/5cfbf5cc37a3bd1da07ae84eea1b828909c4456b/modules/programs/swaylock.nix#L12-L17
autoEnable = lib.versionAtLeast config.home.stateVersion "23.05";
autoEnableExpr = ''
lib.versionAtLeast home.stateVersion "23.05"
'';
};
useWallpaper = config.lib.stylix.mkEnableWallpaper "Swaylock" true;
};

View file

@ -66,6 +66,27 @@
This should be disabled if manual setup is required or if auto-enabling
causes issues.
The default (`true`) is inherited from `mkEnableTargetWith`.
`autoEnableExpr` (String)
: A string representation of `autoEnable`, for use in documentation.
Not required if `autoEnable` is a literal `true` or `false`, but **must**
be used when `autoEnable` is a dynamic expression.
E.g. `"pkgs.stdenv.hostPlatform.isLinux"`.
`autoWrapEnableExpr` (Boolean)
: Whether to automatically wrap `autoEnableExpr` with parenthesis, when it
contains a potentially problematic infix.
The default (`true`) is inherited from `mkEnableTargetWith`.
`enableExample` (Boolean or literal expression)
: An example to include on the enable option. The default is calculated
automatically by `mkEnableTargetWith` and depends on `autoEnable` and
whether an `autoEnableExpr` is used.
`extraOptions` (Attribute set)
: Additional options to be added in the `stylix.targets.${name}` namespace
along the `stylix.targets.${name}.enable` option.
@ -152,12 +173,15 @@
{
name,
humanName,
autoEnable ? true,
autoEnable ? null,
autoEnableExpr ? null,
autoWrapEnableExpr ? null,
enableExample ? null,
extraOptions ? { },
configElements ? [ ],
generalConfig ? null,
imports ? [ ],
}:
}@args:
let
module =
{ config, lib, ... }:
@ -212,7 +236,19 @@ let
inherit imports;
options.stylix.targets.${name}.enable =
config.lib.stylix.mkEnableTarget humanName autoEnable;
let
enableArgs =
{
name = humanName;
}
// lib.optionalAttrs (args ? autoEnable) { inherit autoEnable; }
// lib.optionalAttrs (args ? autoEnableExpr) { inherit autoEnableExpr; }
// lib.optionalAttrs (args ? autoWrapEnableExpr) {
autoWrapExpr = autoWrapEnableExpr;
}
// lib.optionalAttrs (args ? enableExample) { example = enableExample; };
in
config.lib.stylix.mkEnableTargetWith enableArgs;
config = lib.mkIf (config.stylix.enable && cfg.enable) (
lib.mkMerge (

View file

@ -34,28 +34,100 @@
config.lib.stylix =
let
cfg = config.stylix;
self = config.lib.stylix;
# Will wrap with (parentheses) if the expr contains operators with lower precedence than `&&`
wrapExprWith =
{
autoWrapExpr ? true,
trimExpr ? true,
indentMultilineExpr ? true,
}:
expr:
let
trimmed = if trimExpr then lib.trim expr else expr;
isWrapped = builtins.match ''[(].*[)]'' trimmed != null;
hasNewlines = lib.hasInfix "\n" trimmed;
needsWrapping = builtins.any (op: lib.hasInfix op trimmed) [
# These operators have lower precedence than `&&`
# See https://nix.dev/manual/nix/2.28/language/operators
"||"
"->"
"|>"
"<|"
# These keywords would also need wrapping
"with "
"assert "
];
indented =
if indentMultilineExpr then
lib.pipe trimmed [
(lib.strings.splitString "\n")
(map (line: if line == "" then "" else " " + line))
(builtins.concatStringsSep "\n")
]
else
trimmed;
wrapped = if hasNewlines then "(\n${indented}\n)" else "(${trimmed})";
in
if autoWrapExpr && !isWrapped && needsWrapping then wrapped else trimmed;
in
{
mkEnableTarget =
humanName: autoEnable:
lib.mkEnableOption "theming for ${humanName}"
// {
name: autoEnable:
config.lib.stylix.mkEnableTargetWith { inherit name autoEnable; };
mkEnableTargetWith =
{
name,
autoEnable ? true,
autoEnableExpr ? null,
autoWrapExpr ? true,
example ? if args ? autoEnableExpr then true else !autoEnable,
}@args:
let
wrapExpr = wrapExprWith {
inherit autoWrapExpr;
};
in
self.mkEnableIf {
description = "Whether to enable theming for ${name}";
default = cfg.autoEnable && autoEnable;
example = !autoEnable;
}
// lib.optionalAttrs autoEnable {
defaultText = lib.literalMD "same as `stylix.autoEnable`";
defaultText =
if args ? autoEnableExpr then
lib.literalExpression "stylix.autoEnable && ${wrapExpr autoEnableExpr}"
else if autoEnable then
lib.literalExpression "stylix.autoEnable"
else
false;
inherit example;
};
mkEnableWallpaper =
humanName: autoEnable:
lib.mkOption {
default = config.stylix.image != null && autoEnable;
example = config.stylix.image == null;
self.mkEnableIf {
description = "Whether to set the wallpaper for ${humanName}.";
default = config.stylix.image != null && autoEnable;
defaultText =
if autoEnable then lib.literalExpression "stylix.image != null" else false;
example = config.stylix.image == null;
};
mkEnableIf =
{
description,
default,
defaultText ? null,
example ? if args ? defaultText then true else !default,
}@args:
lib.mkOption {
type = lib.types.bool;
}
// lib.optionalAttrs autoEnable {
defaultText = lib.literalMD "`stylix.image != null`";
defaultText = if args ? defaultText then defaultText else default;
inherit
default
description
example
;
};
};
}