4.flake-parts/modules/formatter.nix
Robert Hensing b437169e50 fix(inputs'): do not unnecessarily evaluate formatters
This compromises the ability to `inputs'?formatter`, but that seems
like a particularly rare use case.
Instead, we treat `inputs'` as another "programmatic" as opposed to
"polished" interfaces, similar to the `config` vs flake attributes
distinction.
The programmatic interfaces always prioritize laziness whereas the
polished interfaces may choose to omit known-missing things, to a
reasonable degree.
2026-05-04 13:42:13 +02:00

108 lines
4.4 KiB
Nix

{ config, lib, flake-parts-lib, ... }:
let
inherit (lib)
mapAttrs
mkOption
optionalAttrs
types
;
inherit (flake-parts-lib)
mkPerSystemOption
;
# Do not copy this pattern! (probe, haveFormatterProbably, optionalAttrs)
# It kind of works somewhat for `formatter`, but it is bad.
# Nothing critical must rely on this!
# - `tryEval` makes debugging harder and more annoying
# - There's a performance cost to this otherwise useless evaluation
# - We only use it *in `formatter`* because the effects are limited to that
# particular attribute. This makes the risk somewhat manageable.
probe =
config.perSystem
# Elaborate error message that users should never see.
# The reason to even do this, is that we can sidestep
# https://github.com/hercules-ci/flake-parts/issues/288 in many cases,
# without flake author intervention to keep `nix flake check` happy.
(throw ''
For the purpose of finding out whether an option may be unset for all systems, flake-parts probes the perSystem module without a valid `system` argument, and tries to catch this exception if it finds that `system` is required for this determination. If you see this message, it means that for some reason, flake-parts was unable to catch this exception.
This may be a bug in Nix or in flake-parts, but ultimately this is due to the quirky requirement of flakes that the "system" attribute does not come first.
Flake-parts tries its best to correct that UX, but ultimately, this needs to be solved in Nix.
'');
haveFormatterProbably =
let
ev =
builtins.tryEval
probe.formatter;
in
# If it fails, we can't assume that we don't have a formatter, because it may well evaluate for a real system.
!ev.success
|| ev.value != null;
in
{
options = {
flake.formatter = mkOption {
type = types.lazyAttrsOf (types.nullOr types.package);
default = { };
description = ''
An attribute set of per system a package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
'';
};
perSystem = mkPerSystemOption {
_file = ./formatter.nix;
options = {
formatter = mkOption {
type = types.nullOr types.package;
default = null;
description = ''
A package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
'';
};
};
};
};
config = {
flake.formatter =
# Work around `nix flake check` not allowing `null` values in output attributes.
optionalAttrs
haveFormatterProbably
(mapAttrs
(k: v:
if v.formatter != null then v.formatter
else
throw ''
flake-parts could not determine statically that no formatter is defined for *all* systems.
What happened?
1. For performance reasons, flake-parts must not query `perSystem` for every system, so it uses a heuristic to determine whether a formatter is defined for all systems.
2. Unfortunately, this heuristic is not perfect, and it wasn't able to determine for your flake that `perSystem.formatter` is always `null` (if it even is always `null`).
As a consequence of (1), flake-parts had to provide the output attribute `formatter.${k}`, but as a consequence of (2), you're seeing this error.
What to do?
This whole situation should be temporary. `nix flake check`/`show` can be changed to allow `null` values, which gives flake-parts and other frameworks a way to avoid this situation.
To change the `formatter` output attribute, you can control it precisely with the `touchup` module, for example:
imports = [ inputs.flake-parts.flakeModules.touchup ];
# Remove it
touchup.attr.formatter.enable = false;
# ... or only have it for listed systems
touchup.attr.formatter.any.enable = lib.mkDefault false;
touchup.attr.formatter.attr.x86_64-linux.enable = true;
touchup.attr.formatter.attr.aarch64-darwin.enable = true;
'')
config.allSystems);
perInput = system: flake: {
formatter = flake.formatter.${system};
};
};
}