The memoize function is not a pretty thing in terms of implementation, but it's exactly what we need to solve this UX problem and performance problem. Without this, every distinct `withSystem` call will cause a re-evaluation of the `perSystem` module, which is inefficient. Now, it's a one-time 13KB and length(system) attribute lookups: negligible compared to any instantiations and such. Nix doesn't offer memoization for functions yet, so this is the best we can do.
159 lines
5.7 KiB
Nix
159 lines
5.7 KiB
Nix
{ config, lib, flake-parts-lib, self, ... }:
|
|
let
|
|
inherit (lib)
|
|
genAttrs
|
|
mapAttrs
|
|
mkOption
|
|
types
|
|
;
|
|
inherit (lib.strings)
|
|
escapeNixIdentifier
|
|
;
|
|
inherit (flake-parts-lib)
|
|
mkPerSystemType
|
|
;
|
|
|
|
rootConfig = config;
|
|
|
|
# Stubs for self and inputs. While it'd be possible to define aliases
|
|
# inside perSystem, that is not a general solution, and it would make
|
|
# top.config harder to discover, stretching the learning curve rather
|
|
# than flattening it.
|
|
|
|
throwAliasError' = param:
|
|
throw ''
|
|
`${param}` (without `'`) is not a `perSystem` module argument, but a
|
|
module argument of the top level config.
|
|
|
|
The following is an example usage of `${param}`. Note that its binding
|
|
is in the `top` parameter list, which is declared by the top level module
|
|
rather than the `perSystem` module.
|
|
|
|
top@{ config, lib, ${param}, ... }: {
|
|
perSystem = { config, ${param}', ... }: {
|
|
# in scope here:
|
|
# - ${param}
|
|
# - ${param}'
|
|
# - config (of perSystem)
|
|
# - top.config (note the `top@` pattern)
|
|
};
|
|
}
|
|
'';
|
|
|
|
throwAliasError = param:
|
|
throw ''
|
|
`${param}` is not a `perSystem` module argument, but a module argument of
|
|
the top level config.
|
|
|
|
The following is an example usage of `${param}`. Note that its binding
|
|
is in the `top` parameter list, which is declared by the top level module
|
|
rather than the `perSystem` module.
|
|
|
|
top@{ config, lib, ${param}, ... }: {
|
|
perSystem = { config, ... }: {
|
|
# in scope here:
|
|
# - ${param}
|
|
# - config (of perSystem)
|
|
# - top.config (note the `top@` pattern)
|
|
};
|
|
}
|
|
'';
|
|
|
|
/**
|
|
We primarily use `systems` to help memoize the per system context, but that
|
|
doesn't extend to arbitrary `system`s.
|
|
For that, we use the slightly less efficient, but perfectly acceptable
|
|
`memoizeStr` function.
|
|
*/
|
|
otherMemoizedSystems = flake-parts-lib.memoizeStr config.perSystem;
|
|
|
|
in
|
|
{
|
|
options = {
|
|
systems = mkOption {
|
|
description = ''
|
|
All the system types to enumerate in the flake output subattributes.
|
|
|
|
In other words, all valid values for `system` in e.g. `packages.<system>.foo`.
|
|
'';
|
|
type = types.listOf types.str;
|
|
};
|
|
|
|
perInput = mkOption {
|
|
description = ''
|
|
A function that pre-processes flake inputs.
|
|
|
|
It is called for users of `perSystem` such that `inputs'.''${name} = config.perInput system inputs.''${name}`.
|
|
|
|
This is used for [`inputs'`](../module-arguments.html#inputs) and [`self'`](../module-arguments.html#self).
|
|
|
|
The attributes returned by the `perInput` function definitions are merged into a single namespace (per input),
|
|
so each module should return an attribute set with usually only one or two predictable attribute names. Otherwise,
|
|
the `inputs'` namespace gets polluted.
|
|
'';
|
|
type = types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified));
|
|
};
|
|
|
|
perSystem = mkOption {
|
|
description = ''
|
|
A function from system to flake-like attributes omitting the `<system>` attribute.
|
|
|
|
Modules defined here have access to the suboptions and [some convenient module arguments](../module-arguments.html).
|
|
'';
|
|
type = mkPerSystemType ({ config, system, ... }: {
|
|
_file = ./perSystem.nix;
|
|
config = {
|
|
_module.args.inputs' =
|
|
mapAttrs
|
|
(inputName: input:
|
|
builtins.addErrorContext "while retrieving system-dependent attributes for input ${escapeNixIdentifier inputName}" (
|
|
if input._type or null == "flake"
|
|
then rootConfig.perInput system input
|
|
else
|
|
throw "Trying to retrieve system-dependent attributes for input ${escapeNixIdentifier inputName}, but this input is not a flake. Perhaps flake = false was added to the input declarations by mistake, or you meant to use a different input, or you meant to use plain old inputs, not inputs'."
|
|
)
|
|
)
|
|
self.inputs;
|
|
_module.args.self' =
|
|
builtins.addErrorContext "while retrieving system-dependent attributes for a flake's own outputs" (
|
|
rootConfig.perInput system self
|
|
);
|
|
|
|
# Custom error messages
|
|
_module.args.self = throwAliasError' "self";
|
|
_module.args.inputs = throwAliasError' "inputs";
|
|
_module.args.getSystem = throwAliasError "getSystem";
|
|
_module.args.withSystem = throwAliasError "withSystem";
|
|
_module.args.moduleWithSystem = throwAliasError "moduleWithSystem";
|
|
};
|
|
});
|
|
apply = modules: system:
|
|
(lib.evalModules {
|
|
inherit modules;
|
|
prefix = [ "perSystem" system ];
|
|
specialArgs = {
|
|
inherit system;
|
|
};
|
|
class = "perSystem";
|
|
}).config;
|
|
};
|
|
|
|
allSystems = mkOption {
|
|
type = types.lazyAttrsOf types.unspecified;
|
|
description = "The system-specific config for each of systems.";
|
|
internal = true;
|
|
};
|
|
};
|
|
|
|
config = {
|
|
allSystems = genAttrs config.systems config.perSystem;
|
|
_module.args.getSystem = system: config.allSystems.${system} or (otherMemoizedSystems system);
|
|
|
|
# The warning is there for a reason. Only use this in situations where the
|
|
# performance cost has already been incurred, such as in `flakeModules.easyOverlay`,
|
|
# where we run in the context of an overlay, and the performance cost of the
|
|
# extra `pkgs` makes the cost of running `perSystem` probably negligible.
|
|
_module.args.getSystemIgnoreWarning = system: config.allSystems.${system} or (config.perSystem system);
|
|
};
|
|
|
|
}
|