From 48b3b6f57a1697e17a3171b94654899f679a4467 Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: Tue, 11 Mar 2025 20:17:41 +0100
Subject: [PATCH] Make darwin/home/nixosModules not be set when no modules are
 present

Right now, if you use blueprint in a flake, the outputs
darwin/home/nixosModules will be set to a empty attribute if there are
no modules, this can be easily fixed with `lib.mkIf`. This error also
exists with darwin/system/nixosConfigurations, and using `lib.mkIf` will
make those lines quite long, I don't have a solution for that currently.
---
 lib/default.nix | 462 ++++++++++++++++++++++++------------------------
 1 file changed, 231 insertions(+), 231 deletions(-)

diff --git a/lib/default.nix b/lib/default.nix
index 5fdea7b..377e896 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -439,260 +439,260 @@ let
         );
     in
     # FIXME: maybe there are two layers to this. The blueprint, and then the mapping to flake outputs.
-    {
-      formatter = eachSystem (
-        { pkgs, perSystem, ... }:
-        perSystem.self.formatter or (pkgs.writeShellApplication {
-          name = "nixfmt-rfc-style";
-
-          runtimeInputs = [
-            pkgs.findutils
-            pkgs.gnugrep
-            pkgs.nixfmt-rfc-style
-          ];
-
-          text = ''
-            set -euo pipefail
-
-            # If no arguments are passed, default to formatting the whole project
-            # If git it not available, fallback on current directory.
-            if [[ $# = 0 ]]; then
-              prj_root=$(git rev-parse --show-toplevel 2>/dev/null || echo .)
-              set -- "$prj_root"
-            fi
+    lib.mkMerge [
+      (lib.mkIf (modules ? nixos) { nixosModules = modules.nixos or { }; })
+      (lib.mkIf (modules ? home) { homeModules = modules.home or { }; })
+      (lib.mkIf (modules ? darwin) { darwinModules = modules.darwin or { }; })
+      {
+        formatter = eachSystem (
+          { pkgs, perSystem, ... }:
+          perSystem.self.formatter or (pkgs.writeShellApplication {
+            name = "nixfmt-rfc-style";
+
+            runtimeInputs = [
+              pkgs.findutils
+              pkgs.gnugrep
+              pkgs.nixfmt-rfc-style
+            ];
+
+            text = ''
+              set -euo pipefail
+
+              # If no arguments are passed, default to formatting the whole project
+              # If git it not available, fallback on current directory.
+              if [[ $# = 0 ]]; then
+                prj_root=$(git rev-parse --show-toplevel 2>/dev/null || echo .)
+                set -- "$prj_root"
+              fi
+
+              # Not a git repo, or git is not installed. Fallback
+              if ! git rev-parse --is-inside-work-tree; then
+                exec nixfmt "$@"
+              fi
+
+              # Use git to traverse since nixfmt doesn't have good traversal
+              git ls-files "$@" | grep '\.nix$' | xargs --no-run-if-empty nixfmt
+            '';
+          })
+        );
 
-            # Not a git repo, or git is not installed. Fallback
-            if ! git rev-parse --is-inside-work-tree; then
-              exec nixfmt "$@"
-            fi
+        lib = tryImport (src + "/lib") specialArgs;
 
-            # Use git to traverse since nixfmt doesn't have good traversal
-            git ls-files "$@" | grep '\.nix$' | xargs --no-run-if-empty nixfmt
-          '';
-        })
-      );
+        # expose the functor to the top-level
+        # FIXME: only if it exists
+        __functor = x: inputs.self.lib.__functor x;
 
-      lib = tryImport (src + "/lib") specialArgs;
+        devShells =
+          let
+            namedNix = (
+              optionalPathAttrs (src + "/devshells") (
+                path:
+                (importDir path (
+                  entries:
+                  eachSystem (
+                    { newScope, ... }:
+                    lib.mapAttrs (pname: { path, type }: newScope { inherit pname; } path { }) (
+                      lib.filterAttrs (
+                        _name:
+                        { path, type }:
+                        type == "regular" || (type == "directory" && lib.pathExists "${path}/default.nix")
+                      ) entries
+                    )
+                  )
+                ))
+              )
+            );
 
-      # expose the functor to the top-level
-      # FIXME: only if it exists
-      __functor = x: inputs.self.lib.__functor x;
+            namedToml = (
+              optionalPathAttrs (src + "/devshells") (
+                path:
+                (importTomlFilesAt path (
+                  entries:
+                  eachSystem (
+                    { newScope, perSystem, ... }:
+                    lib.mapAttrs (
+                      pname: { path, type }: newScope { inherit pname; } (_: devshellFromTOML perSystem path) { }
+                    ) entries
+                  )
+                ))
+              )
+            );
 
-      devShells =
-        let
-          namedNix = (
-            optionalPathAttrs (src + "/devshells") (
-              path:
-              (importDir path (
-                entries:
+            defaultNix = (
+              optionalPathAttrs (src + "/devshell.nix") (
+                path:
                 eachSystem (
                   { newScope, ... }:
-                  lib.mapAttrs (pname: { path, type }: newScope { inherit pname; } path { }) (
-                    lib.filterAttrs (
-                      _name:
-                      { path, type }:
-                      type == "regular" || (type == "directory" && lib.pathExists "${path}/default.nix")
-                    ) entries
-                  )
+                  {
+                    default = newScope { pname = "default"; } path { };
+                  }
                 )
-              ))
-            )
-          );
+              )
+            );
 
-          namedToml = (
-            optionalPathAttrs (src + "/devshells") (
-              path:
-              (importTomlFilesAt path (
-                entries:
+            defaultToml = (
+              optionalPathAttrs (src + "/devshell.toml") (
+                path:
                 eachSystem (
                   { newScope, perSystem, ... }:
-                  lib.mapAttrs (
-                    pname: { path, type }: newScope { inherit pname; } (_: devshellFromTOML perSystem path) { }
-                  ) entries
+                  {
+                    default = newScope { pname = "default"; } (_: devshellFromTOML perSystem path) { };
+                  }
                 )
-              ))
-            )
-          );
-
-          defaultNix = (
-            optionalPathAttrs (src + "/devshell.nix") (
-              path:
-              eachSystem (
-                { newScope, ... }:
-                {
-                  default = newScope { pname = "default"; } path { };
-                }
               )
-            )
-          );
+            );
+
+            merge =
+              prev: item:
+              let
+                systems = lib.attrNames (prev // item);
+                mergeSystem = system: { ${system} = (prev.${system} or { }) // (item.${system} or { }); };
+                mergedSystems = builtins.map mergeSystem systems;
+              in
+              lib.mergeAttrsList mergedSystems;
+          in
+          lib.foldl merge { } [
+            namedToml
+            namedNix
+            defaultToml
+            defaultNix
+          ];
 
-          defaultToml = (
-            optionalPathAttrs (src + "/devshell.toml") (
-              path:
+        packages =
+          lib.traceIf (builtins.pathExists (src + "/pkgs")) "blueprint: the /pkgs folder is now /packages"
+            (
+              let
+                entries =
+                  (optionalPathAttrs (src + "/packages") (path: importDir path lib.id))
+                  // (optionalPathAttrs (src + "/package.nix") (path: {
+                    default = {
+                      inherit path;
+                    };
+                  }))
+                  // (optionalPathAttrs (src + "/formatter.nix") (path: {
+                    formatter = {
+                      inherit path;
+                    };
+                  }));
+              in
               eachSystem (
-                { newScope, perSystem, ... }:
-                {
-                  default = newScope { pname = "default"; } (_: devshellFromTOML perSystem path) { };
-                }
+                { newScope, ... }: lib.mapAttrs (pname: { path, ... }: newScope { inherit pname; } path { }) entries
               )
-            )
-          );
-
-          merge =
-            prev: item:
-            let
-              systems = lib.attrNames (prev // item);
-              mergeSystem = system: { ${system} = (prev.${system} or { }) // (item.${system} or { }); };
-              mergedSystems = builtins.map mergeSystem systems;
-            in
-            lib.mergeAttrsList mergedSystems;
-        in
-        lib.foldl merge { } [
-          namedToml
-          namedNix
-          defaultToml
-          defaultNix
-        ];
-
-      packages =
-        lib.traceIf (builtins.pathExists (src + "/pkgs")) "blueprint: the /pkgs folder is now /packages"
-          (
-            let
-              entries =
-                (optionalPathAttrs (src + "/packages") (path: importDir path lib.id))
-                // (optionalPathAttrs (src + "/package.nix") (path: {
-                  default = {
-                    inherit path;
-                  };
-                }))
-                // (optionalPathAttrs (src + "/formatter.nix") (path: {
-                  formatter = {
-                    inherit path;
-                  };
-                }));
-            in
-            eachSystem (
-              { newScope, ... }: lib.mapAttrs (pname: { path, ... }: newScope { inherit pname; } path { }) entries
-            )
-          );
-
-      # Defining homeConfigurations under legacyPackages allows the home-manager CLI
-      # to automatically detect the right output for the current system without
-      # either manually defining the pkgs set (requires explicit system) or breaking
-      # nix3 CLI output (`packages` output expects flat attrset)
-      # FIXME: Find another way to make this work without introducing legacyPackages.
-      #        May involve changing upstream home-manager.
-      legacyPackages = lib.optionalAttrs (homesNested != { }) standaloneHomeConfigurations;
+            );
 
-      darwinConfigurations = lib.mapAttrs (_: x: x.value) (hostsByCategory.darwinConfigurations or { });
-      nixosConfigurations = lib.mapAttrs (_: x: x.value) (hostsByCategory.nixosConfigurations or { });
-      systemConfigs = lib.mapAttrs (_: x: x.value) (hostsByCategory.systemConfigs or { });
+        # Defining homeConfigurations under legacyPackages allows the home-manager CLI
+        # to automatically detect the right output for the current system without
+        # either manually defining the pkgs set (requires explicit system) or breaking
+        # nix3 CLI output (`packages` output expects flat attrset)
+        # FIXME: Find another way to make this work without introducing legacyPackages.
+        #        May involve changing upstream home-manager.
+        legacyPackages = lib.optionalAttrs (homesNested != { }) standaloneHomeConfigurations;
 
-      inherit modules;
+        darwinConfigurations = lib.mapAttrs (_: x: x.value) (hostsByCategory.darwinConfigurations or { });
+        nixosConfigurations = lib.mapAttrs (_: x: x.value) (hostsByCategory.nixosConfigurations or { });
+        systemConfigs = lib.mapAttrs (_: x: x.value) (hostsByCategory.systemConfigs or { });
 
-      darwinModules = modules.darwin or { };
-      homeModules = modules.home or { };
-      # TODO: how to extract NixOS tests?
-      nixosModules = modules.nixos or { };
+        inherit modules;
 
-      templates = importDir (src + "/templates") (
-        entries:
-        lib.mapAttrs (
-          name:
-          { path, type }:
-          {
-            path = path;
-            description =
-              if builtins.pathExists (path + "/flake.nix") then
-                (import (path + "/flake.nix")).description or name
-              else
-                name;
-          }
-        ) entries
-      );
+        templates = importDir (src + "/templates") (
+          entries:
+          lib.mapAttrs (
+            name:
+            { path, type }:
+            {
+              path = path;
+              description =
+                if builtins.pathExists (path + "/flake.nix") then
+                  (import (path + "/flake.nix")).description or name
+                else
+                  name;
+            }
+          ) entries
+        );
 
-      checks = eachSystem (
-        { system, pkgs, ... }:
-        lib.mergeAttrsList (
-          [
-            # add all the supported packages, and their passthru.tests to checks
-            (withPrefix "pkgs-" (
-              lib.concatMapAttrs (
-                pname: package:
-                {
-                  ${pname} = package;
-                }
-                # also add the passthru.tests to the checks
-                // (lib.mapAttrs' (tname: test: {
-                  name = "${pname}-${tname}";
-                  value = test;
-                }) (filterPlatforms system (package.passthru.tests or { })))
-              ) (filterPlatforms system (inputs.self.packages.${system} or { }))
-            ))
-            # build all the devshells
-            (withPrefix "devshell-" (inputs.self.devShells.${system} or { }))
-            # add nixos system closures to checks
-            (withPrefix "nixos-" (
-              lib.mapAttrs (_: x: x.config.system.build.toplevel) (
-                lib.filterAttrs (_: x: x.pkgs.system == system) (inputs.self.nixosConfigurations or { })
-              )
-            ))
-            # add darwin system closures to checks
-            (withPrefix "darwin-" (
-              lib.mapAttrs (_: x: x.system) (
-                lib.filterAttrs (_: x: x.pkgs.system == system) (inputs.self.darwinConfigurations or { })
-              )
-            ))
-            # add system-manager closures to checks
-            (withPrefix "system-" (
-              lib.mapAttrs (_: x: x) (
-                lib.filterAttrs (_: x: x.system == system) (inputs.self.systemConfigs or { })
-              )
-            ))
-            # load checks from the /checks folder. Those take precedence over the others.
-            (filterPlatforms system (
-              optionalPathAttrs (src + "/checks") (
-                path:
-                let
-                  importChecksFn = lib.mapAttrs (
-                    pname:
-                    { type, path }:
-                    import path {
-                      inherit
-                        pname
-                        flake
-                        inputs
-                        system
-                        pkgs
-                        ;
-                    }
-                  );
-                in
-
-                (importDir path importChecksFn)
-              )
-            ))
-          ]
-          ++ (lib.optional (inputs.self.lib.tests or { } != { }) {
-            lib-tests = pkgs.runCommandLocal "lib-tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
-              export HOME="$(realpath .)"
-              export NIX_CONFIG='
-              extra-experimental-features = nix-command flakes
-              flake-registry = ""
-              '
-
-              nix-unit --flake ${flake}#lib.tests ${
-                toString (
-                  lib.mapAttrsToList (k: v: "--override-input ${k} ${v}") (builtins.removeAttrs inputs [ "self" ])
+        checks = eachSystem (
+          { system, pkgs, ... }:
+          lib.mergeAttrsList (
+            [
+              # add all the supported packages, and their passthru.tests to checks
+              (withPrefix "pkgs-" (
+                lib.concatMapAttrs (
+                  pname: package:
+                  {
+                    ${pname} = package;
+                  }
+                  # also add the passthru.tests to the checks
+                  // (lib.mapAttrs' (tname: test: {
+                    name = "${pname}-${tname}";
+                    value = test;
+                  }) (filterPlatforms system (package.passthru.tests or { })))
+                ) (filterPlatforms system (inputs.self.packages.${system} or { }))
+              ))
+              # build all the devshells
+              (withPrefix "devshell-" (inputs.self.devShells.${system} or { }))
+              # add nixos system closures to checks
+              (withPrefix "nixos-" (
+                lib.mapAttrs (_: x: x.config.system.build.toplevel) (
+                  lib.filterAttrs (_: x: x.pkgs.system == system) (inputs.self.nixosConfigurations or { })
                 )
-              }
+              ))
+              # add darwin system closures to checks
+              (withPrefix "darwin-" (
+                lib.mapAttrs (_: x: x.system) (
+                  lib.filterAttrs (_: x: x.pkgs.system == system) (inputs.self.darwinConfigurations or { })
+                )
+              ))
+              # add system-manager closures to checks
+              (withPrefix "system-" (
+                lib.mapAttrs (_: x: x) (
+                  lib.filterAttrs (_: x: x.system == system) (inputs.self.systemConfigs or { })
+                )
+              ))
+              # load checks from the /checks folder. Those take precedence over the others.
+              (filterPlatforms system (
+                optionalPathAttrs (src + "/checks") (
+                  path:
+                  let
+                    importChecksFn = lib.mapAttrs (
+                      pname:
+                      { type, path }:
+                      import path {
+                        inherit
+                          pname
+                          flake
+                          inputs
+                          system
+                          pkgs
+                          ;
+                      }
+                    );
+                  in
+
+                  (importDir path importChecksFn)
+                )
+              ))
+            ]
+            ++ (lib.optional (inputs.self.lib.tests or { } != { }) {
+              lib-tests = pkgs.runCommandLocal "lib-tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
+                export HOME="$(realpath .)"
+                export NIX_CONFIG='
+                extra-experimental-features = nix-command flakes
+                flake-registry = ""
+                '
+
+                nix-unit --flake ${flake}#lib.tests ${
+                  toString (
+                    lib.mapAttrsToList (k: v: "--override-input ${k} ${v}") (builtins.removeAttrs inputs [ "self" ])
+                  )
+                }
 
-              touch $out
-            '';
-          })
-        )
-      );
-    };
+                touch $out
+              '';
+            })
+          )
+        );
+      }
+    ];
 
   # Create a new flake blueprint
   mkBlueprint =