diff --git a/stylix/testbed/autoload.nix b/stylix/testbed/autoload.nix new file mode 100644 index 000000000..29f5f1a2c --- /dev/null +++ b/stylix/testbed/autoload.nix @@ -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 +) diff --git a/stylix/testbed/default.nix b/stylix/testbed/default.nix index bcb61484b..68014ca0b 100644 --- a/stylix/testbed/default.nix +++ b/stylix/testbed/default.nix @@ -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