stylix: extract testbed modules to autoload.nix (#1520)

Evaluated the set of testbed modules in a separate file from where we
create the testbed NixOS systems.

The first file (autoload.nix) has no dependency on any of Stylix's flake
inputs. The second file (default.nix) takes the matrix as a parameter,
then imports each module into a NixOS system, and wrapper script.

This will enable a partitioned flake to evaluate the set of testbeds
without needing to fully convert them into NixOS systems with
dependencies on dev-inputs.

Link: https://github.com/nix-community/stylix/pull/1520

Reviewed-by: Flameopathic <64027365+Flameopathic@users.noreply.github.com>
Reviewed-by: awwpotato <awwpotato@voidq.com>
Reviewed-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
This commit is contained in:
Matt Sturgeon 2025-06-25 01:13:25 +01:00 committed by GitHub
parent edcecc02e6
commit 1fc2289454
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 139 additions and 98 deletions

118
stylix/testbed/autoload.nix Normal file
View file

@ -0,0 +1,118 @@
{
pkgs,
lib,
testbedFieldSeparator ? ":",
}:
let
isEnabled = pkgs.callPackage ./is-enabled.nix { };
# Describes all testbed modules loaded from the modules directory.
# A list of attrsets, each containing: { module, path, name }
testbeds = lib.pipe ../../modules [
builtins.readDir
builtins.attrNames
(builtins.concatMap (
module:
let
testbeds = ../../modules/${module}/testbeds;
files = lib.optionalAttrs (builtins.pathExists testbeds) (
builtins.readDir testbeds
);
in
lib.mapAttrsToList (
testbed: type:
let
path = testbeds + "/${testbed}";
pathStr = toString path;
in
if type != "regular" then
throw "${pathStr} must be regular: ${type}"
else if !lib.hasSuffix ".nix" testbed then
throw "testbed must be a Nix file: ${pathStr}"
else if testbed == ".nix" then
throw "testbed must have a name: ${pathStr}"
else
{
inherit module path;
name = lib.removeSuffix ".nix" testbed;
}
) files
))
];
# Import all the testbed themes
themes = lib.pipe ./themes [
builtins.readDir
(lib.filterAttrs (name: _: lib.strings.hasSuffix ".nix" name))
(builtins.mapAttrs (
name: type:
lib.throwIfNot (type == "regular")
"Unexpected filetype in testbed themes: ${toString ./themes/${name}} is a ${type}."
./themes/${name}
))
(lib.mapAttrs' (name: lib.nameValuePair (lib.strings.removeSuffix ".nix" name)))
];
# Construct a NixOS module for the testbed+theme combination.
#
# If the testbed module is enabled, returns an attrset containing it:
# { «name» = «modele»; }
#
# Returns an empty attrset if the testbed module is not enabled.
# { }
makeTestbedModule =
testbed: themeName: themeModule:
let
joinFields = builtins.concatStringsSep testbedFieldSeparator;
fields =
map
(
field:
lib.throwIf (lib.hasInfix testbedFieldSeparator field)
"testbed field must not contain the '${testbedFieldSeparator}' testbed field separator: ${field}"
field
)
[
"testbed"
testbed.name
themeName
];
name = joinFields fields;
in
lib.optionalAttrs (isEnabled testbed.path) {
${name} = {
# Unique key for de-duplication
key = joinFields [
(toString ./.)
name
];
# Location displayed in errors
_file = name;
# Avoid accidental imports, e.g. in submodules or home-manager
_class = "nixos";
imports = [
testbed.path
themeModule
];
config.system.name = name;
};
};
# Generates a copy of each testbed for each of the imported themes.
# I.e. a testbeds*themes matrix
makeTestbeds = testbed: lib.mapAttrsToList (makeTestbedModule testbed) themes;
in
# Testbeds are merged using lib.attrsets.unionOfDisjoint to throw an error if
# any name collides.
builtins.foldl' lib.attrsets.unionOfDisjoint { } (
builtins.concatMap makeTestbeds testbeds
)

View file

@ -2,63 +2,13 @@
pkgs,
inputs,
lib,
testbedFieldSeparator ? ":",
modules ? import ./autoload.nix { inherit pkgs lib; },
}:
let
isEnabled = pkgs.callPackage ./is-enabled.nix { };
autoload = lib.pipe ../../modules [
builtins.readDir
builtins.attrNames
(builtins.concatMap (
module:
let
testbeds = ../../modules/${module}/testbeds;
files = lib.optionalAttrs (builtins.pathExists testbeds) (
builtins.readDir testbeds
);
in
lib.mapAttrsToList (
testbed: type:
let
path = testbeds + "/${testbed}";
pathStr = toString path;
in
if type != "regular" then
throw "${pathStr} must be regular: ${type}"
else if !lib.hasSuffix ".nix" testbed then
throw "testbed must be a Nix file: ${pathStr}"
else if testbed == ".nix" then
throw "testbed must have a name: ${pathStr}"
else
{
inherit module path;
name = lib.removeSuffix ".nix" testbed;
}
) files
))
];
makeTestbed =
testbed: themeName: themeModule:
name: testbed:
let
name =
lib.concatMapStringsSep testbedFieldSeparator
(
field:
lib.throwIf (lib.hasInfix testbedFieldSeparator field)
"testbed field must not contain the '${testbedFieldSeparator}' testbed field separator: ${field}"
field
)
[
"testbed"
testbed.name
themeName
];
system = lib.nixosSystem {
inherit (pkgs) system;
@ -69,54 +19,27 @@ let
./modules/application.nix
inputs.self.nixosModules.stylix
inputs.home-manager.nixosModules.home-manager
testbed.path
themeModule
{ system.name = name; }
testbed
];
};
script = pkgs.writeShellApplication {
inherit name;
text = ''
cleanup() {
if rm --recursive "$directory"; then
printf '%s\n' 'Virtualisation disk image removed.'
fi
}
# We create a temporary directory rather than a temporary file, since
# temporary files are created empty and are not valid disk images.
directory="$(mktemp --directory)"
trap cleanup EXIT
NIX_DISK_IMAGE="$directory/nixos.qcow2" \
${lib.getExe system.config.system.build.vm}
'';
};
in
lib.optionalAttrs (isEnabled testbed.path) {
${name} = script;
pkgs.writeShellApplication {
inherit name;
text = ''
cleanup() {
if rm --recursive "$directory"; then
printf '%s\n' 'Virtualisation disk image removed.'
fi
}
# We create a temporary directory rather than a temporary file, since
# temporary files are created empty and are not valid disk images.
directory="$(mktemp --directory)"
trap cleanup EXIT
NIX_DISK_IMAGE="$directory/nixos.qcow2" \
${lib.getExe system.config.system.build.vm}
'';
};
# Import all the testbed themes
themes = lib.pipe ./themes [
builtins.readDir
(lib.filterAttrs (name: _: lib.strings.hasSuffix ".nix" name))
(builtins.mapAttrs (
name: type:
lib.throwIfNot (type == "regular")
"Unexpected filetype in testbed themes: ${toString ./themes/${name}} is a ${type}."
./themes/${name}
))
(lib.mapAttrs' (name: lib.nameValuePair (lib.strings.removeSuffix ".nix" name)))
];
# This generates a copy of each testbed for each of the imported themes.
makeTestbeds = testbed: lib.mapAttrsToList (makeTestbed testbed) themes;
in
# Testbeds are merged using lib.attrsets.unionOfDisjoint to throw an error if
# testbed names collide.
builtins.foldl' lib.attrsets.unionOfDisjoint { } (
builtins.concatMap makeTestbeds autoload
)
builtins.mapAttrs makeTestbed modules