diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..12301490 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..758069ef --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,51 @@ +name: CI +on: + push: + workflow_dispatch: + pull_request: + types: [opened] + +jobs: + quality-gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: cachix/install-nix-action@v13 + with: + nix_path: nixpkgs=channel:nixos-unstable + install_url: https://github.com/numtide/nix-flakes-installer/releases/download/nix-3.0pre20201007_5257a25/install + extra_nix_config: experimental-features = nix-command flakes + + - name: Check Nix parsing + run: find . -name "*.nix" -exec nix-instantiate --parse --quiet {} >/dev/null + + + - name: Run `nix flake check` + run: cd examples/fully-featured && nix flake check --show-trace + + - name: Run `nix flake show` + run: cd examples/fully-featured && nix flake show --show-trace + + - name: Build Morty configuration + run: cd examples/fully-featured && nix build .#nixosConfigurations.Morty.config.system.build.toplevel --dry-run + + - name: Build Rick configuration + run: cd examples/fully-featured && nix build .#someConfigurations.Rick.config.system.build.toplevel --dry-run + + - name: Build Summer checks + run: cd examples/fully-featured && nix build .#checks.x86_64-linux.summerHasUnfreeConfigured && nix build .#checks.x86_64-linux.summerHasPackageOverridesConfigured && nix build .#checks.x86_64-linux.summerHasCustomModuleConfigured + + - name: Run `nix flake check` + run: cd examples/somewhat-realistic && nix flake check --show-trace + + - name: Run `nix flake show` + run: cd examples/somewhat-realistic && nix flake show --show-trace + + - name: Build HostnameOne configuration + run: cd examples/somewhat-realistic && nix build .#nixosConfigurations.HostnameOne.config.system.build.toplevel --dry-run + + - name: Check Nix formatting + run: nix shell nixpkgs\#nixpkgs-fmt -c nixpkgs-fmt --check . + + - name: Build HostnameThree configuration + run: cd examples/somewhat-realistic && nix build .#darwinConfigurations.HostnameThree.config.system.build.toplevel --dry-run + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..31d263bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +examples/*/flake.lock diff --git a/README.md b/README.md index f353312e..4f365149 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ +[![Discord](https://img.shields.io/discord/591914197219016707.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.com/invite/RbvHtGa) + +Need help? Createn an issue or ping @Gytis#0001 in discord server above. + # What is this flake # This flake exposes a library abstraction to *painlessly* generate nixos flake configurations. @@ -11,101 +15,159 @@ This flake provides two main features (visible from `flake.nix`): - `nixosModules.saneFlakeDefaults` - Configures `nix.*` attributes. Generates `nix.nixPath`/`nix.registry` from flake `inputs`, sets `pkgs.nixUnstable` as the default also enables `ca-references` and `flakes`. - `lib.systemFlake { ... }` - Generates a system flake that may then be built. -- `lib.modulesFromList [ ./a.nix ./b.nix ]` - Generates modules attributes which looks like this `{ a = import ./a.nix; b = import ./b.nix; }`. +- `lib.exporter.modulesFromListExporter [ ./a.nix ./b.nix ]` - Generates modules attributes which looks like this `{ a = import ./a.nix; b = import ./b.nix; }`. +- `lib.exporter.overlaysFromChannelsExporter channels` - Collects all overlays from channels and exports them as an appropriately namespaced attribute set. Users can instantiate with their nixpkgs version. +- `lib.builder.packagesFromOverlayBuilderConstructor channels pkgs` - Similar to the overlay generator, but outputs them as packages, instead. Users can use your cache. # Examples # - [Gytis Dotfiles (Author of this project)](https://github.com/gytis-ivaskevicius/nixfiles/blob/master/flake.nix) -- [fufexan Dotfiles](https://github.com/fufexan/dotfiles/blob/main/flake.nix) +- [Fufexan Dotfiles](https://github.com/fufexan/dotfiles/blob/main/flake.nix) +- [Bobbbay Dotfiles](https://github.com/Bobbbay/dotfiles/blob/master/flake.nix) +- [Charlotte Dotfiles](https://github.com/chvp/nixos-config/blob/master/flake.nix) # How to use this flake # -Example flake with all available attributes can be found [Here](https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/master/examples/fully-featured-flake.nix). +Example flake with all available attributes can be found [Here](https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/master/examples/fully-featured/flake.nix). (WARNING: Quite overwhelming) -And more realistic flake example can be found [Here](https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/master/examples/somewhat-realistic-flake.nix). +And more realistic flake example can be found [Here](https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/master/examples/somewhat-realistic/flake.nix). +I strongly recommend referring to actual people examples above when setting up your system. + +Looking to add a kick-ass repl to your config? Create and import something along the lines of this: ```nix +{ inputs, ... }: + { - outputs = inputs@{ self, nixpkgs, unstable, nur, utils, home-manager, neovim }: - utils.lib.systemFlake { + environment.shellAliases = { + very-cool-nix-repl = "nix repl ${inputs.utils.lib.repl}"; + }; +} + +``` + +## Documentation as code. Options with their example usage and description. + +```nix +let + inherit (builtins) removeAttrs; + mkApp = utils.lib.mkApp; + # If there is a need to get direct reference to nixpkgs - do this: + pkgs = self.pkgs.x86_64-linux.nixpkgs; +in flake-utils-plus.lib.systemFlake { + + + # `self` and `inputs` arguments are REQUIRED!!!!!!!!!!!!!! + inherit self inputs; + + # Supported systems, used for packages, apps, devShell and multiple other definitions. Defaults to `flake-utils.lib.defaultSystems`. + supportedSystems = [ "x86_64-linux" ]; + - # `self` and `inputs` arguments are REQUIRED!!!!!!!!! - inherit self inputs; + ################ + ### channels ### + ################ - # Supported systems, used for packages, apps, devShell and multiple other definitions. Defaults to `flake-utils.lib.defaultSystems` - supportedSystems = [ "aarch64-linux" "x86_64-linux" ]; + # Configuration that is shared between all channels. + channelsConfig = { allowBroken = true; }; - # Default architecture to be used for `nixosProfiles` defaults to "x86_64-linux" - defaultSystem = "aarch64-linux"; + # Overlays which are applied to all channels. + sharedOverlays = [ nur.overlay ]; - # Channel definitions. `channels..{input,overlaysBuilder,config,patches}` - channels.nixpkgs = { - # Channel input to import - input = nixpkgs; + # Nixpkgs flake reference to be used in the configuration. + channels..input = nixpkgs; - # Channel specific overlays - overlaysBuilder = channels: [ ]; + # Channel specific config options. + channels..config = { allowUnfree = true; }; - # Channel specific configuration. Overwrites `channelsConfig` argument - config = { allowUnfree = false; }; - }; + # Patches to apply on selected channel. + channels..patches = [ ./someAwesomePatch.patch ]; - # Additional channel input - channels.unstable.input = unstable; - # Yep, you see it first folks - you can patch nixpkgs! - channels.unstable.patches = [ ./myNixpkgsPatch.patch ]; + # Overlays to apply on a selected channel. + channels..overlaysBuilder = channels: [ + (final: prev: { inherit (channels.unstable) neovim; }) + ]; - # Default configuration values for `channels..config = {...}` - channelsConfig.allowUnfree = true; + #################### + ### hostDefaults ### + #################### - # Profiles, gets parsed into `nixosConfigurations` - nixosProfiles = { - # Profile name / System hostname - FirstHost = { - # System architecture. Defaults to `defaultSystem` argument - system = "x86_64-linux"; - # of the channel to be used. Defaults to `nixpkgs` - channelName = "unstable"; - # Extra arguments to be passed to the modules. Overwrites `sharedExtraArgs` argument - extraArgs = { }; - # Host specific configuration - modules = [ ]; - }; + # Default architecture to be used for `hosts` defaults to "x86_64-linux". + hostDefaults.system = "x86_64-linux"; - OtherHost = { ... }; - }; + # Default modules to be passed to all hosts. + hostDefaults.modules = [ utils.nixosModules.saneFlakeDefaults ]; - # Extra arguments to be passed to modules. Defaults to `{ inherit inputs; }` - sharedExtraArgs = { }; + # Reference to `channels..*`, defines default channel to be used by hosts. Defaults to "nixpkgs". + hostDefaults.channelName = "unstable"; - # Shared overlays between channels, gets applied to all `channels..input` - sharedOverlays = [ ]; + # Extra arguments to be passed to all modules. Merged with host's extraArgs. + hostDefaults.extraArgs = { inherit utils inputs; foo = "foo"; }; - # Shared modules/configurations between `nixosProfiles` - sharedModules = [ utils.nixosModules.saneFlakeDefaults ]; + ############# + ### hosts ### + ############# - # Evaluates to `packages..attributeKey = "attributeValue"` - packagesBuilder = channels: { attributeKey = "attributeValue"; }; + # System architecture. Defaults to `defaultSystem` argument. + hosts..system = "aarch64-linux"; - # Evaluates to `defaultPackage..attributeKey = "attributeValue"` - defaultPackageBuilder = channels: { attributeKey = "attributeValue"; }; + # of the channel to be used. Defaults to `nixpkgs`; + hosts..channelName = "unstable"; - # Evaluates to `apps..attributeKey = "attributeValue"` - appsBuilder = channels: { attributeKey = "attributeValue"; }; + # Extra arguments to be passed to the modules. + hosts..extraArgs = { abc = 123; }; - # Evaluates to `defaultApp..attributeKey = "attributeValue"` - defaultAppBuilder = channels: { attributeKey = "attributeValue"; }; + # These are not part of the module system, so they can be used in `imports` lines without infinite recursion. + hosts..specialArgs = { thing = "abc"; }; - # Evaluates to `devShell..attributeKey = "attributeValue"` - devShellBuilder = channels: { attributeKey = "attributeValue"; }; + # Host specific configuration. + hosts..modules = [ ./configuration.nix ]; - # Evaluates to `checks..attributeKey = "attributeValue"` - checksBuilder = channels: { attributeKey = "attributeValue"; }; + # Flake output for configuration to be passed to. Defaults to `nixosConfigurations`. + hosts..output = "darwinConfigurations"; + + # System builder. Defaults to `channels..input.lib.nixosSystem`. + # `removeAttrs` workaround due to this issue https://github.com/LnL7/nix-darwin/issues/319 + hosts..builder = args: nix-darwin.lib.darwinSystem (removeAttrs args [ "system" ]); + + + ############################# + ### flake output builders ### + ############################# + + # Evaluates to `packages..coreutils = .coreutils`. + packagesBuilder = channels: { inherit (channels.unstable) coreutils; }; + + # Evaluates to `defaultPackage..neovim = .neovim`. + defaultPackageBuilder = channels: channels.nixpkgs.neovim; + + # Evaluates to `apps..custom-neovim = utils.lib.mkApp { drv = ...; exePath = ...; };`. + appsBuilder = channels: with channels.nixpkgs; { + custom-neovim = mkApp { + drv = fancy-neovim; + exePath = "/bin/nvim"; }; + }; + + # Evaluates to `apps..firefox = utils.lib.mkApp { drv = ...; };`. + defaultAppBuilder = channels: mkApp { drv = channels.nixpkgs.firefox; }; + + # Evaluates to `devShell. = .mkShell { name = "devShell"; };`. + devShellBuilder = channels: channels.nixpkgs.mkShell { name = "devShell"; }; + + + ######################################################### + ### All other properties are passed down to the flake ### + ######################################################### + + checks.x86_64-linux.someCheck = pkgs.hello; + packages.x86_64-linux.somePackage = pkss.hello; + overlay = import ./overlays; + abc = 132; + } ``` - diff --git a/examples/fully-featured-flake.nix b/examples/fully-featured-flake.nix deleted file mode 100644 index 9ce8863c..00000000 --- a/examples/fully-featured-flake.nix +++ /dev/null @@ -1,139 +0,0 @@ -{ - description = "A highly awesome system configuration."; - - inputs = { - nixpkgs.url = github:nixos/nixpkgs/release-20.09; - unstable.url = github:nixos/nixpkgs/nixos-unstable; - nur.url = github:nix-community/NUR; - utils.url = github:gytis-ivaskevicius/flake-utils-plus; - - home-manager = { - url = github:nix-community/home-manager/master; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - neovim = { - url = github:neovim/neovim?dir=contrib; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - }; - - - outputs = inputs@{ self, nixpkgs, unstable, nur, utils, home-manager, neovim }: - utils.lib.systemFlake { - - # `self` and `inputs` arguments are REQUIRED!!!!!!!!!!!!!! - inherit self inputs; - - # Supported systems, used for packages, apps, devShell and multiple other definitions. Defaults to `flake-utils.lib.defaultSystems` - supportedSystems = [ "aarch64-linux" "x86_64-linux" ]; - - # Default architecture to be used for `nixosProfiles` defaults to "x86_64-linux" - defaultSystem = "aarch64-linux"; - - # Channel definitions. `channels..{input,overlaysBuilder,config,patches}` - channels.nixpkgs = { - # Channel input to import - input = nixpkgs; - - # Channel specific overlays - overlaysBuilder = channels: [ - (final: prev: { inherit (channels.unstable) zsh; }) - ]; - - # Channel specific configuration. Overwrites `channelsConfig` argument - config = { - allowUnfree = false; - }; - }; - - # Additional channel input - channels.unstable.input = unstable; - # Yep, you see it first folks - you can patch nixpkgs! - channels.unstable.patches = [ ./myNixpkgsPatch.patch ]; - channels.unstable.overlaysBuilder = channels: [ - (final: prev: { - neovim-nightly = neovim.defaultPackage.${prev.system}; - }) - ]; - - - # Default configuration values for `channels..config = {...}` - channelsConfig = { - allowBroken = true; - allowUnfree = true; - }; - - # Profiles, gets parsed into `nixosConfigurations` - nixosProfiles = { - # Profile name / System hostname - Morty = { - # System architecture. Defaults to `defaultSystem` argument - system = "x86_64-linux"; - # of the channel to be used. Defaults to `nixpkgs` - channelName = "unstable"; - # Extra arguments to be passed to the modules. Overwrites `sharedExtraArgs` argument - extraArgs = { - abc = 123; - }; - # Host specific configuration. Same as `sharedModules` - modules = [ - (import ./configurations/Morty.host.nix) - ]; - }; - }; - - # Extra arguments to be passed to modules. Defaults to `{ inherit inputs; }` - sharedExtraArgs = { inherit utils inputs; }; - - # Shared overlays between channels, gets applied to all `channels..input` - sharedOverlays = [ - # Overlay imported from `./overlays`. (Defined below) - self.overlays - # Nix User Repository overlay - nur.overlay - ]; - - # Shared modules/configurations between `nixosProfiles` - sharedModules = [ - home-manager.nixosModules.home-manager - # Sets sane `nix.*` defaults. Please refer to implementation/readme for more details. - utils.nixosModules.saneFlakeDefaults - (import ./modules) - { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - } - ]; - - - # Evaluates to `packages..attributeKey = "attributeValue"` - packagesBuilder = channels: { attributeKey = "attributeValue"; }; - - # Evaluates to `defaultPackage..attributeKey = "attributeValue"` - defaultPackageBuilder = channels: { attributeKey = "attributeValue"; }; - - # Evaluates to `apps..attributeKey = "attributeValue"` - appsBuilder = channels: { attributeKey = "attributeValue"; }; - - # Evaluates to `defaultApp..attributeKey = "attributeValue"` - defaultAppBuilder = channels: { attributeKey = "attributeValue"; }; - - # Evaluates to `devShell..attributeKey = "attributeValue"` - devShellBuilder = channels: { attributeKey = "attributeValue"; }; - - # Evaluates to `checks..attributeKey = "attributeValue"` - checksBuilder = channels: { attributeKey = "attributeValue"; }; - - # All other values gets passed down to the flake - overlay = import ./overlays; - abc = 132; - # etc - - }; -} - - - - diff --git a/examples/fully-featured/configurations/Morty.host.nix b/examples/fully-featured/configurations/Morty.host.nix new file mode 100644 index 00000000..1f0fabe2 --- /dev/null +++ b/examples/fully-featured/configurations/Morty.host.nix @@ -0,0 +1,4 @@ +{ ... }: { + boot.loader.grub.devices = [ "nodev" ]; + fileSystems."/" = { device = "test"; fsType = "ext4"; }; +} diff --git a/examples/fully-featured/configurations/Rick.host.nix b/examples/fully-featured/configurations/Rick.host.nix new file mode 120000 index 00000000..6e58fce7 --- /dev/null +++ b/examples/fully-featured/configurations/Rick.host.nix @@ -0,0 +1 @@ +Morty.host.nix \ No newline at end of file diff --git a/examples/fully-featured/configurations/Summer.host.nix b/examples/fully-featured/configurations/Summer.host.nix new file mode 100644 index 00000000..8f3273eb --- /dev/null +++ b/examples/fully-featured/configurations/Summer.host.nix @@ -0,0 +1,6 @@ +{ lib, ... }: { + boot.loader.grub.devices = [ "nodev" ]; + fileSystems."/" = { device = "test"; fsType = "ext4"; }; + patchedModule.test = lib.patchedFunction "test"; + nixpkgs.config.packageOverrides = pkgs: { }; +} diff --git a/examples/fully-featured/flake.nix b/examples/fully-featured/flake.nix new file mode 100644 index 00000000..2f4f45d4 --- /dev/null +++ b/examples/fully-featured/flake.nix @@ -0,0 +1,170 @@ +{ + description = "A highly awesome system configuration."; + + inputs = { + nixpkgs.url = github:nixos/nixpkgs/release-20.09; + unstable.url = github:nixos/nixpkgs/nixos-unstable; + nur.url = github:nix-community/NUR; + utils.url = path:../../; + + home-manager = { + url = github:nix-community/home-manager/master; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + neovim = { + url = github:neovim/neovim?dir=contrib; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + }; + + + outputs = inputs@{ self, nixpkgs, unstable, nur, utils, home-manager, neovim }: + utils.lib.systemFlake { + + # `self` and `inputs` arguments are REQUIRED!!!!!!!!!!!!!! + inherit self inputs; + + # Supported systems, used for packages, apps, devShell and multiple other definitions. Defaults to `flake-utils.lib.defaultSystems` + supportedSystems = [ "x86_64-linux" ]; + + + # Default host settings. + hostDefaults = { + # Default architecture to be used for `hosts` defaults to "x86_64-linux" + system = "x86_64-linux"; + # Default channel to be used for `hosts` defaults to "nixpkgs" + channelName = "unstable"; + # Extra arguments to be passed to modules. Merged with host's extraArgs + extraArgs = { inherit utils inputs; foo = "foo"; }; + # Default modules to be passed to all hosts. + modules = [ + home-manager.nixosModules.home-manager + # Sets sane `nix.*` defaults. Please refer to implementation/readme for more details. + utils.nixosModules.saneFlakeDefaults + (import ./modules) + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + } + ]; + + }; + + # Shared overlays between channels, gets applied to all `channels..input` + sharedOverlays = [ + # Overlay imported from `./overlays`. (Defined below) + self.overlay + # Nix User Repository overlay + nur.overlay + ]; + + + + # Channel definitions. `channels..{input,overlaysBuilder,config,patches}` + channels.nixpkgs = { + # Channel input to import + input = nixpkgs; + + # Channel specific overlays + overlaysBuilder = channels: [ + (final: prev: { inherit (channels.unstable) zsh; }) + ]; + + # Channel specific configuration. Overwrites `channelsConfig` argument + config = { + allowUnfree = false; + }; + }; + + # Additional channel input + channels.unstable.input = unstable; + # Yep, you see it first folks - you can patch nixpkgs! + channels.unstable.patches = [ ./myNixpkgsPatch.patch ]; + channels.unstable.overlaysBuilder = channels: [ + (final: prev: { + neovim-nightly = neovim.defaultPackage.${prev.system}; + }) + ]; + + + # Default configuration values for `channels..config = {...}` + channelsConfig = { + allowBroken = true; + allowUnfree = true; + }; + + # Host definitions + hosts = { + # Profile name / System hostname + Morty = { + # System architecture. + system = "x86_64-linux"; + # of the channel to be used. Defaults to `nixpkgs` + channelName = "unstable"; + # Extra arguments to be passed to the modules. + extraArgs = { + abc = 123; + }; + # Host specific configuration. + modules = [ ./configurations/Morty.host.nix ]; + }; + + Rick = { + modules = [ ./configurations/Rick.host.nix ]; + output = "someConfigurations"; + }; + + Summer = { + channelName = "unstable"; + modules = [ ./configurations/Summer.host.nix ]; + }; + }; + + # Evaluates to `packages..attributeKey = "attributeValue"` + packagesBuilder = channels: { inherit (channels.unstable) coreutils; }; + + # Evaluates to `defaultPackage..attributeKey = "attributeValue"` + defaultPackageBuilder = channels: channels.nixpkgs.runCommandNoCC "package" { } "echo package > $out"; + + # Evaluates to `apps..attributeKey = "attributeValue"` + appsBuilder = channels: { package = { type = "app"; program = channels.nixpkgs.runCommandNoCC "package" { } "echo test > $out"; }; }; + + # Evaluates to `defaultApp..attributeKey = "attributeValue"` + defaultAppBuilder = channels: { type = "app"; program = channels.nixpkgs.runCommandNoCC "package" { } "echo test > $out"; }; + + # Evaluates to `devShell. = "attributeValue"` + devShellBuilder = channels: channels.nixpkgs.mkShell { name = "devShell"; }; + + # Evaluates to `checks..attributeKey = "attributeValue"` + checksBuilder = channels: + let + booleanCheck = cond: + if cond + then channels.nixpkgs.runCommandNoCC "success" { } "echo success > $out" + else channels.nixpkgs.runCommandNoCC "failure" { } "exit 1"; + in + { + check = channels.nixpkgs.runCommandNoCC "test" { } "echo test > $out"; + # Modules (and lib) from patched nixpkgs are used + summerHasCustomModuleConfigured = booleanCheck (self.nixosConfigurations.Summer.config.patchedModule.test == "test"); + # nixpkgs config from host-specific module is used + summerHasPackageOverridesConfigured = booleanCheck (self.nixosConfigurations.Summer.config.nixpkgs.pkgs.config ? packageOverrides); + # nixpkgs config from channel is also used + summerHasUnfreeConfigured = booleanCheck (self.nixosConfigurations.Summer.config.nixpkgs.pkgs.config ? allowUnfree); + }; + + # All other values gets passed down to the flake + checks.x86_64-linux.merge-with-checksBuilder-test = self.pkgs.x86_64-linux.nixpkgs.hello; + packages.x86_64-linux.patched-package = self.pkgs.x86_64-linux.unstable.flake-utils-plus-test; + overlay = import ./overlays; + abc = 132; + # etc + + }; +} + + + + diff --git a/examples/fully-featured/modules/default.nix b/examples/fully-featured/modules/default.nix new file mode 100644 index 00000000..c915eb0a --- /dev/null +++ b/examples/fully-featured/modules/default.nix @@ -0,0 +1 @@ +{ ... }: { } diff --git a/examples/fully-featured/myNixpkgsPatch.patch b/examples/fully-featured/myNixpkgsPatch.patch new file mode 100644 index 00000000..793ba283 --- /dev/null +++ b/examples/fully-featured/myNixpkgsPatch.patch @@ -0,0 +1,38 @@ +diff --git a/lib/default.nix b/lib/default.nix +index 50320669e28..ff835870e4a 100644 +--- a/lib/default.nix ++++ b/lib/default.nix +@@ -145,5 +145,7 @@ let + nixType imap; + inherit (self.versions) + splitVersion; ++ ++ patchedFunction = x: x; + }); + in lib +diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix +index f72be732216..fbbf9776a6b 100644 +--- a/nixos/modules/module-list.nix ++++ b/nixos/modules/module-list.nix +@@ -1101,4 +1101,10 @@ + ./virtualisation/vmware-guest.nix + ./virtualisation/xen-dom0.nix + ./virtualisation/xe-guest-utilities.nix ++ ({ lib, config, ... }: { ++ options.patchedModule.test = lib.mkOption { ++ default = null; ++ example = "test"; ++ }; ++ }) + ] +diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix +index cdf28b9782c..48ed72bf3c1 100644 +--- a/pkgs/top-level/all-packages.nix ++++ b/pkgs/top-level/all-packages.nix +@@ -31025,4 +31025,6 @@ in + lc3tools = callPackage ../development/tools/lc3tools {}; + + zktree = callPackage ../applications/misc/zktree {}; ++ ++ flake-utils-plus-test = callPackage ../applications/misc/hello { }; + } diff --git a/examples/fully-featured/overlays/default.nix b/examples/fully-featured/overlays/default.nix new file mode 100644 index 00000000..ce168707 --- /dev/null +++ b/examples/fully-featured/overlays/default.nix @@ -0,0 +1 @@ +final: prev: { } diff --git a/examples/somewhat-realistic-flake.nix b/examples/somewhat-realistic-flake.nix deleted file mode 100644 index f723de56..00000000 --- a/examples/somewhat-realistic-flake.nix +++ /dev/null @@ -1,95 +0,0 @@ -{ - description = "A highly awesome system configuration."; - - inputs = { - nixpkgs.url = github:nixos/nixpkgs/release-20.09; - unstable.url = github:nixos/nixpkgs/nixos-unstable; - utils.url = github:gytis-ivaskevicius/flake-utils-plus; - - home-manager = { - url = github:nix-community/home-manager/master; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - - outputs = inputs@{ self, nixpkgs, unstable, utils, home-manager }: - utils.lib.systemFlake { - - # `self` and `inputs` arguments are REQUIRED!!!!!!!!!!!!!! - inherit self inputs; - - - - # Channel definitions. `channels..{input,overlaysBuilder,config,patches}` - channels.nixpkgs.input = nixpkgs; - channels.unstable.input = unstable; - - # Default configuration values for `channels..config = {...}` - channelsConfig.allowUnfree = true; - - # Channel specific overlays - channels.nixpkgs.overlaysBuilder = channels: [ - (final: prev: { - # Overwrites specified packages to be used from unstable channel. - inherit (channels.unstable) alacritty ranger jdk15_headless; - }) - ]; - - - - - - # Profiles, gets parsed into `nixosConfigurations` - nixosProfiles.HostnameOne.modules = [ - (import ./HostnameOneConfiguration.nix) - ]; - - - nixosProfiles.HostnameTwo = { - # This host uses `channels.unstable.{input,overlaysBuilder,config,patches}` attributes instead of `channels.nixpkgs.<...>` - channelName = "unstable"; - - # Host specific configuration. Same as `sharedModules` - modules = [ - (import ./configurations/Morty.host.nix) - ]; - }; - - - - - - - overlay = import ./overlays; - - # Shared overlays between channels, gets applied to all `channels..input` - sharedOverlays = [ - # Overlay imported from `./overlays`. (Defined above) - self.overlays - ]; - - - - - - # Shared modules/configurations between `nixosProfiles` - sharedModules = [ - home-manager.nixosModules.home-manager - # Sets sane `nix.*` defaults. Please refer to implementation/readme for more details. - utils.nixosModules.saneFlakeDefaults - (import ./modules) - { - home-manager.useGlobalPkgs = true; - home-manager.useUserPackages = true; - } - ]; - - - - }; -} - - - - diff --git a/examples/somewhat-realistic/flake.nix b/examples/somewhat-realistic/flake.nix new file mode 100644 index 00000000..9981cf07 --- /dev/null +++ b/examples/somewhat-realistic/flake.nix @@ -0,0 +1,111 @@ +{ + description = "A highly awesome system configuration."; + + inputs = { + nixpkgs.url = github:nixos/nixpkgs/release-20.09; + unstable.url = github:nixos/nixpkgs/nixos-unstable; + utils.url = path:../../; + + nix-darwin.url = github:LnL7/nix-darwin; + home-manager = { + url = github:nix-community/home-manager/release-20.09; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + + outputs = inputs@{ self, nixpkgs, unstable, utils, nix-darwin, home-manager }: + utils.lib.systemFlake { + + # `self` and `inputs` arguments are REQUIRED!!!!!!!!!!!!!! + inherit self inputs; + + + + + # Shared overlays between channels, gets applied to all `channels..input` + sharedOverlays = [ + # Overlay imported from `./overlays`. (Defined above) + self.overlay + ]; + + # Channel definitions. `channels..{input,overlaysBuilder,config,patches}` + channels.nixpkgs.input = nixpkgs; + channels.unstable.input = unstable; + + # Default configuration values for `channels..config = {...}` + channelsConfig.allowUnfree = true; + + # Channel specific overlays + channels.nixpkgs.overlaysBuilder = channels: [ + (final: prev: { + # Overwrites specified packages to be used from unstable channel. + inherit (channels.unstable) alacritty ranger jdk15_headless; + }) + ]; + + + + + + + # Shared modules/configurations between `hosts` + hostDefaults = { + modules = [ + # Sets sane `nix.*` defaults. Please refer to implementation/readme for more details. + utils.nixosModules.saneFlakeDefaults + (import ./modules) + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + } + ]; + }; + + + # Profiles, gets parsed into `nixosConfigurations` + hosts.HostnameOne.modules = [ + home-manager.nixosModules.home-manager + ./hosts/One.nix + ]; + + + hosts.HostnameTwo = { + # This host uses `channels.unstable.{input,overlaysBuilder,config,patches}` attributes instead of `channels.nixpkgs.<...>` + channelName = "unstable"; + + # Host specific configuration. + modules = [ + home-manager.nixosModules.home-manager + ./hosts/Two.nix + ]; + }; + + hosts."HostnameThree" = { + # This host will be exported under the flake's `darwinConfigurations` output + output = "darwinConfigurations"; + + # Build host with darwinSystem. `removeAttrs` workaround due to https://github.com/LnL7/nix-darwin/issues/319 + builder = args: nix-darwin.lib.darwinSystem (builtins.removeAttrs args [ "system" ]); + + system = "x86_64-darwin"; + + # This host uses `channels.unstable.{input,overlaysBuilder,config,patches}` attributes instead of `channels.nixpkgs.<...>` + channelName = "unstable"; + + # Host specific configuration. + modules = [ + home-manager.darwinModules.home-manager + ./hosts/Three.nix + ]; + }; + + + + + + overlay = import ./overlays; + + }; +} + diff --git a/examples/somewhat-realistic/hosts/One.nix b/examples/somewhat-realistic/hosts/One.nix new file mode 100644 index 00000000..74d4c7bb --- /dev/null +++ b/examples/somewhat-realistic/hosts/One.nix @@ -0,0 +1,5 @@ +{ lib, pkgs, config, ... }: { + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + fileSystems."/" = { device = "/dev/disk/by-label/One"; }; +} diff --git a/examples/somewhat-realistic/hosts/Three.nix b/examples/somewhat-realistic/hosts/Three.nix new file mode 100644 index 00000000..c915eb0a --- /dev/null +++ b/examples/somewhat-realistic/hosts/Three.nix @@ -0,0 +1 @@ +{ ... }: { } diff --git a/examples/somewhat-realistic/hosts/Two.nix b/examples/somewhat-realistic/hosts/Two.nix new file mode 120000 index 00000000..b7f0569b --- /dev/null +++ b/examples/somewhat-realistic/hosts/Two.nix @@ -0,0 +1 @@ +One.nix \ No newline at end of file diff --git a/examples/somewhat-realistic/modules/default.nix b/examples/somewhat-realistic/modules/default.nix new file mode 100644 index 00000000..c915eb0a --- /dev/null +++ b/examples/somewhat-realistic/modules/default.nix @@ -0,0 +1 @@ +{ ... }: { } diff --git a/examples/somewhat-realistic/overlays/default.nix b/examples/somewhat-realistic/overlays/default.nix new file mode 100644 index 00000000..ce168707 --- /dev/null +++ b/examples/somewhat-realistic/overlays/default.nix @@ -0,0 +1 @@ +final: prev: { } diff --git a/flake.lock b/flake.lock index 8b5c27ac..eaa6295b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1614513358, - "narHash": "sha256-LakhOx3S1dRjnh0b5Dg3mbZyH0ToC9I8Y2wKSkBaTzU=", + "lastModified": 1618217525, + "narHash": "sha256-WGrhVczjXTiswQaoxQ+0PTfbLNeOQM6M36zvLn78AYg=", "owner": "numtide", "repo": "flake-utils", - "rev": "5466c5bbece17adaab2d82fae80b46e807611bf3", + "rev": "c6169a2772643c4a93a0b5ac1c61e296cba68544", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1fd414f2..3e25d69e 100644 --- a/flake.nix +++ b/flake.nix @@ -3,53 +3,42 @@ inputs.flake-utils.url = "github:numtide/flake-utils"; - outputs = { self, flake-utils }: { - - nixosModules.saneFlakeDefaults = import ./modules/saneFlakeDefaults.nix; - - lib = - let - removeSuffix = suffix: str: - let - sufLen = builtins.stringLength suffix; - sLen = builtins.stringLength str; - in - if sufLen <= sLen && suffix == builtins.substring (sLen - sufLen) sufLen str then - builtins.substring 0 (sLen - sufLen) str - else - str; - - genAttrs' = func: values: builtins.listToAttrs (map func values); - mapAttrsToList = f: attrs: - map (name: f name attrs.${name}) (builtins.attrNames attrs); - in - flake-utils.lib - // rec { - - modulesFromList = paths: - genAttrs' - (path: { - name = removeSuffix ".nix" (baseNameOf path); - value = import path; - }) - paths; - - systemFlake = import ./systemFlake.nix { inherit flake-utils; }; - - nixPathFromInputs = inputs: - mapAttrsToList (name: _: "${name}=${inputs.${name}}") inputs; - - nixRegistryFromInputs = inputs: - builtins.mapAttrs (name: v: { flake = v; }) inputs; - - nixDefaultsFromInputs = inputs: { - extraOptions = "experimental-features = nix-command ca-references flakes"; - nixPath = nixPathFromInputs inputs; - registry = nixRegistryFromInputs inputs; + outputs = { self, flake-utils }: + let + fupArgs = { flake-utils-plus = self; }; + systemFlake = import ./systemFlake.nix fupArgs; + packagesFromOverlaysBuilderConstructor = import ./packagesFromOverlaysBuilderConstructor.nix fupArgs; + overlaysFromChannelsExporter = import ./overlaysFromChannelsExporter.nix fupArgs; + modulesFromList = import ./moduleFromListExporter.nix fupArgs; + in + rec { + + nixosModules.saneFlakeDefaults = import ./modules/saneFlakeDefaults.nix; + + lib = flake-utils.lib // { + # modulesFromList is deprecated, will be removed in future releases + inherit systemFlake modulesFromList; + + builder = { + inherit packagesFromOverlaysBuilderConstructor; }; + exporter = { + inherit overlaysFromChannelsExporter modulesFromList; + }; + + repl = ./repl.nix; + + patchChannel = system: channel: patches: + if patches == [ ] then channel else + (import channel { inherit system; }).pkgs.applyPatches { + name = "nixpkgs-patched-${channel.shortRev}"; + src = channel; + patches = patches; + }; + }; - }; + }; } diff --git a/moduleFromListExporter.nix b/moduleFromListExporter.nix new file mode 100644 index 00000000..7d91652b --- /dev/null +++ b/moduleFromListExporter.nix @@ -0,0 +1,48 @@ +{ flake-utils-plus }: +let + + modulesFromListExporter = paths: + /** + Synopsis: modulesFromListExporter _paths_ + + paths: [ ] + + Returns an attribute set of modules from a list of paths by converting + the path's basename into the attribute key. + + Example: + + paths: [ ./path/to/moduleA.nix ./path/to/moduleBfolder ] + + { + moduleA = import ./path/to/moduleA.nix; + moduleBfolder = import ./path/to/moduleBfolder; + } + + **/ + + let + + removeSuffix = suffix: str: + let + sufLen = builtins.stringLength suffix; + sLen = builtins.stringLength str; + in + if sufLen <= sLen && suffix == builtins.substring (sLen - sufLen) sufLen str then + builtins.substring 0 (sLen - sufLen) str + else + str; + + genAttrs' = func: values: builtins.listToAttrs (map func values); + + in + + genAttrs' + (path: { + name = removeSuffix ".nix" (baseNameOf path); + value = import path; + }) + paths; + +in +modulesFromListExporter diff --git a/modules/saneFlakeDefaults.nix b/modules/saneFlakeDefaults.nix index b93f8f76..ccbe3004 100644 --- a/modules/saneFlakeDefaults.nix +++ b/modules/saneFlakeDefaults.nix @@ -3,18 +3,14 @@ let flakes = lib.filterAttrs (name: value: value ? outputs) inputs; - nixPath = lib.mapAttrsToList - (name: _: "${name}=${inputs.${name}}") - flakes; - nixRegistry = builtins.mapAttrs (name: v: { flake = v; }) flakes; -in { +in +{ - nix = { + nix = { extraOptions = "experimental-features = nix-command ca-references flakes"; - nixPath = nixPath; registry = nixRegistry; package = pkgs.nixUnstable; }; diff --git a/overlaysFromChannelsExporter.nix b/overlaysFromChannelsExporter.nix new file mode 100644 index 00000000..8b33722a --- /dev/null +++ b/overlaysFromChannelsExporter.nix @@ -0,0 +1,67 @@ +{ flake-utils-plus }: +let + + overlaysFromChannelsExporter = channels: + /** + Synopsis: overlaysFromChannelsExporter _channels_ + + channels: channels..overlays + + Returns an attribute set of all packages defined in an overlay by any channel + intended to be passed to be exported via _self.overlays_. This method of + sharing has the advantage over _self.packages_, that the user will instantiate + overlays with his proper nixpkgs version, and thereby significantly reduce their system's + closure as they avoid depending on entirely different nixpkgs versions dependency + trees. On the flip side, any caching that is set up for one's packages will essentially + be useless to users. + + It can happen that an overlay is not compatible with the version of nixpkgs a user tries + to instantiate it. In order to provide users with a visual clue for which nixpkgs version + an overlay was originally created, we prefix the channle name: "/". + In the case of the unstable channel, this information is still of varying usefulness, + as effective cut dates can vary heavily between repositories. + + Example: + + overlays = [ + "unstable/development" = final: prev: { }; + "nixos2009/chromium" = final: prev: { }; + "nixos2009/pythonPackages" = final: prev: { }; + ]; + + **/ + let + inherit (builtins) mapAttrs attrNames concatMap listToAttrs; + nameValuePair = name: value: { inherit name value; }; + + pathStr = path: builtins.concatStringsSep "/" path; + + channelNames = attrNames channels; + overlayNames = overlay: attrNames (overlay null null); + + extractAndNamespaceEachOverlay = channelName: overlay: + map + (overlayName: + nameValuePair + (pathStr [ channelName overlayName ]) + (final: prev: { + ${overlayName} = (overlay final prev).${overlayName}; + }) + ) + (overlayNames overlay); + + in + listToAttrs ( + concatMap + (channelName: + concatMap + (overlay: + extractAndNamespaceEachOverlay channelName overlay + ) + channels.${channelName}.overlays + ) + channelNames + ); + +in +overlaysFromChannelsExporter diff --git a/packagesFromOverlaysBuilderConstructor.nix b/packagesFromOverlaysBuilderConstructor.nix new file mode 100644 index 00000000..f042abc3 --- /dev/null +++ b/packagesFromOverlaysBuilderConstructor.nix @@ -0,0 +1,92 @@ +{ flake-utils-plus }: +let + + overlayFromPackagesBuilderConstructor = channels: + let + # channels: channels..overlays + + overlayFromPackagesBuilder = pkgs: + /** + Synopsis: overlayFromPackagesBuilder _pkgs_ + + pkgs: pkgs.. + + Returns valid packges that have been defined within an overlay so they + can be shared via _self.packages_ with the world. This is especially useful + over sharing one's art via _self.overlays_ in case you have a binary cache + running from which third parties could benefit. + + First, flattens an arbitrarily nested _pkgs_ tree for each system into a flat + key in which nesting is aproximated by a "/" (e.g. "development/kakoune"). + Also filter the resulting packages for attributes that _trivially_ would + fail flake checks (broken, system not supported or not a derivation). + + Second, collects all overlays' packages' keys of all channels into a flat list. + + Finally, only passes packages through the seive that are prefixed with a top level + key exposed by any of the overlays. Since overlays override (and do not emrge) + top level attributes, by filtering on the prefix, an overlay's entire packages + tree will be correctly captured. + + Example: + + pkgs'. = { + "development/kakoune" = { ... }; + "development/vim" = { ... }; + }; + + overlays' = [ + "development" + ]; + + overlays' will pass both pkgs' through the sieve. + + **/ + let + + inherit (flake-utils-plus.lib) flattenTree filterPackages; + inherit (builtins) attrNames mapAttrs listToAttrs attrValues concatStringSep concatMap any; + nameValuePair = name: value: { inherit name value; }; + filterAttrs = pred: set: + listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [ (nameValuePair name v) ] else [ ]) (attrNames set)); + hasPrefix = + pref: + str: builtins.substring 0 (builtins.stringLength pref) str == pref; + + + # first, flatten and filter on valid packages (by nix flake check criterion) + flattenedPackages = + let + f = system: tree: (filterPackages system (flattenTree tree)); + in + mapAttrs f pkgs; + + # second, flatten all overlays' packages' keys into a single list + flattendOverlaysNames = + let + allOverlays = concatMap (c: c.overlays) (attrValues channels); + + overlayNamesList = overlay: + attrNames (overlay null null); + in + concatMap (o: overlayNamesList o) allOverlays; + + in + # finally, only retain those packages defined by overlays + # pkgs'.. + # overlays' = [ "prefix" ... ]; + mapAttrs + (_: pkgs: + filterAttrs + (pkgName: + any (overlayName: hasPrefix pkgName overlayName) flattendOverlaysNames + ) + pkgs + ) + flattenedPackages; + + in + overlayFromPackagesBuilder; + +in +overlayFromPackagesBuilderConstructor diff --git a/repl.nix b/repl.nix new file mode 100644 index 00000000..ee4457f0 --- /dev/null +++ b/repl.nix @@ -0,0 +1,11 @@ +let + flake = builtins.getFlake "flake:self"; + hostname = builtins.head (builtins.match "([a-zA-Z0-9]+)\n" (builtins.readFile "/etc/hostname")); + nixpkgs = flake.pkgs.${builtins.currentSystem}.nixpkgs; +in +{ inherit flake; } +// flake +// builtins +// flake.nixosConfigurations.${hostname} +// nixpkgs.lib + // (builtins.removeAttrs nixpkgs [ "config" ]) diff --git a/systemFlake.nix b/systemFlake.nix index 302ae4b1..3feda486 100644 --- a/systemFlake.nix +++ b/systemFlake.nix @@ -1,55 +1,77 @@ -{ flake-utils }: +{ flake-utils-plus }: { self -, defaultSystem ? "x86_64-linux" -, sharedExtraArgs ? { inherit inputs; } -, supportedSystems ? flake-utils.lib.defaultSystems +, defaultSystem ? "x86_64-linux" # will be deprecated soon use hostDefaults.system instead +, supportedSystems ? flake-utils-plus.lib.defaultSystems , inputs -, nixosConfigurations ? { } -, nixosProfiles ? { } + , channels ? { } , channelsConfig ? { } -, sharedModules ? [ ] , sharedOverlays ? [ ] - # `Func` postfix is soon to be deprecated. Replaced with `Builder` instead -, packagesFunc ? null -, defaultPackageFunc ? null -, appsFunc ? null -, defaultAppFunc ? null -, devShellFunc ? null -, checksFunc ? null - -, packagesBuilder ? packagesFunc -, defaultPackageBuilder ? defaultPackageFunc -, appsBuilder ? appsFunc -, defaultAppBuilder ? defaultAppFunc -, devShellBuilder ? devShellFunc -, checksBuilder ? checksFunc +, nixosProfiles ? { } # will be deprecated soon, use hosts, instead. +, hosts ? nixosProfiles +, sharedExtraArgs ? { } # deprecate soon, prefer hostDefaults +, sharedModules ? [ ] # deprecate soon, prefer hostDefaults +, hostDefaults ? { + system = defaultSystem; + modules = sharedModules; + extraArgs = sharedExtraArgs; + } + +, packagesBuilder ? null +, defaultPackageBuilder ? null +, appsBuilder ? null +, defaultAppBuilder ? null +, devShellBuilder ? null +, checksBuilder ? null , ... }@args: let - otherArguments = builtins.removeAttrs args [ - "defaultSystem" - "sharedExtraArgs" + inherit (flake-utils-plus.lib) eachSystem patchChannel; + inherit (builtins) foldl' mapAttrs removeAttrs attrValues isAttrs isList; + + # set defaults and validate host arguments + evalHostArgs = + { channelName ? "nixpkgs" + , system ? "x86_64-linux" + , output ? "nixosConfigurations" + , builder ? channels.${channelName}.input.lib.nixosSystem + , modules ? [ ] + , extraArgs ? { } + # These are not part of the module system, so they can be used in `imports` lines without infinite recursion + , specialArgs ? { } + }: { inherit channelName system output builder modules extraArgs specialArgs; }; + + # recursively merge attribute sets and lists up to a certain depth + mergeAny = lhs: rhs: + lhs // mapAttrs + (name: value: + if isAttrs value then lhs.${name} or { } // value + else if isList value then lhs.${name} or [ ] ++ value + else value + ) + rhs; + + foldHosts = foldl' mergeAny { }; + + optionalAttrs = check: value: if check then value else { }; + + otherArguments = removeAttrs args [ + "defaultSystem" # TODO: deprecated, remove + "sharedExtraArgs" # deprecated "inputs" + "hosts" + "hostDefaults" "nixosProfiles" "channels" "channelsConfig" "self" - "sharedModules" + "sharedModules" # deprecated "sharedOverlays" "supportedSystems" - # `Func` postfix is deprecated. Replaced with `Builder` instead - "packagesFunc" - "defaultPackageFunc" - "appsFunc" - "defaultAppFunc" - "devShellFunc" - "checksFunc" - "packagesBuilder" "defaultPackageBuilder" "appsBuilder" @@ -58,70 +80,114 @@ let "checksBuilder" ]; - nixosConfigurationBuilder = name: value: ( - # It would be nice to get `nixosSystem` reference from `selectedNixpkgs` but it is not possible at this moment - inputs.nixpkgs.lib.nixosSystem (genericConfigurationBuilder name value) - ); + getNixpkgs = host: self.pkgs."${host.system}"."${host.channelName}"; - genericConfigurationBuilder = name: value: ( + configurationBuilder = hostname: host': ( let - system = if (value ? system) then value.system else defaultSystem; - channelName = if (value ? channelName) then value.channelName else "nixpkgs"; - selectedNixpkgs = self.pkgs."${system}"."${channelName}"; + selectedNixpkgs = getNixpkgs host; + host = evalHostArgs (mergeAny hostDefaults host'); + patchedChannel = selectedNixpkgs.path; + # Use lib from patched nixpkgs + lib = selectedNixpkgs.lib; + # Use nixos modules from patched nixpkgs + baseModules = import (patchedChannel + "/nixos/modules/module-list.nix"); + # Override `modulesPath` because otherwise imports from there will not use patched nixpkgs + specialArgs = { modulesPath = builtins.toString (patchedChannel + "/nixos/modules"); } // host.specialArgs; + # The only way to find out if a host has `nixpkgs.config` set to + # the non-default value is by evalling most of the config. + hostConfig = (lib.evalModules { + prefix = [ ]; + check = false; + modules = baseModules ++ host.modules; + args = { inherit inputs; } // host.extraArgs; + inherit specialArgs; + }).config; in - with selectedNixpkgs.lib; { - inherit system; - modules = [ - { - networking.hostName = name; - - nixpkgs = { - pkgs = selectedNixpkgs; - config = selectedNixpkgs.config; - }; - - system.configurationRevision = mkIf (self ? rev) self.rev; - nix.package = mkDefault selectedNixpkgs.nixUnstable; - } - ] - ++ sharedModules - ++ (optionals (value ? modules) value.modules); - extraArgs = sharedExtraArgs // optionalAttrs (value ? extraArgs) value.extraArgs; + { + ${host.output}.${hostname} = host.builder ({ + inherit (host) system; + modules = [ + ({ pkgs, lib, options, config, ... }: { + # 'mkMerge` to separate out each part into its own module + _type = "merge"; + contents = [ + (optionalAttrs (options ? networking.hostName) { + networking.hostName = hostname; + }) + + (if options ? nixpkgs.pkgs then + { + nixpkgs.config = selectedNixpkgs.config; + nixpkgs.pkgs = + # Make sure we don't import nixpkgs again if not + # necessary. We can't use `config.nixpkgs.config` + # because that triggers infinite recursion. + if (hostConfig.nixpkgs.config == { }) then + selectedNixpkgs + else + import patchedChannel { + inherit (host) system; + overlays = selectedNixpkgs.overlays; + config = selectedNixpkgs.config // config.nixpkgs.config; + }; + } + else { _module.args.pkgs = selectedNixpkgs; }) + + (optionalAttrs (options ? system.configurationRevision) { + system.configurationRevision = lib.mkIf (self ? rev) self.rev; + }) + + (optionalAttrs (options ? nix.package) { + nix.package = lib.mkDefault pkgs.nixUnstable; + }) + + { + # at this point we assume, that an evaluator at least + # uses nixpkgs.lib to evaluate modules. + _module.args = { inherit inputs; } // host.extraArgs; + } + ]; + }) + ] ++ host.modules; + specialArgs = host.specialArgs; + } // (optionalAttrs (host.output == "nixosConfigurations") { + inherit lib baseModules specialArgs; + })); } ); + in -otherArguments - -// flake-utils.lib.eachSystem supportedSystems (system: - let - patchChannel = channel: patches: - if patches == [ ] then channel else - (import channel { inherit system; }).pkgs.applyPatches { - name = "nixpkgs-patched-${channel.shortRev}"; - src = channel; - patches = patches; - }; - - importChannel = name: value: import (patchChannel value.input (value.patches or [ ])) { - inherit system; - overlays = sharedOverlays ++ (if (value ? overlaysBuilder) then (value.overlaysBuilder pkgs) else [ ]); - config = channelsConfig // (if (value ? config) then value.config else { }); - }; - - pkgs = builtins.mapAttrs importChannel channels; - - optional = check: value: (if check != null then value else { }); - in - { inherit pkgs; } - // optional packagesBuilder { packages = packagesBuilder pkgs; } - // optional defaultPackageBuilder { defaultPackage = defaultPackageBuilder pkgs; } - // optional appsBuilder { apps = appsBuilder pkgs; } - // optional defaultAppBuilder { defaultApp = defaultAppBuilder pkgs; } - // optional devShellBuilder { devShell = devShellBuilder pkgs; } - // optional checksBuilder { checks = checksBuilder pkgs; } +mergeAny otherArguments ( + + eachSystem supportedSystems + (system: + let + importChannel = name: value: import (patchChannel system value.input (value.patches or [ ])) { + inherit system; + overlays = sharedOverlays ++ (if (value ? overlaysBuilder) then (value.overlaysBuilder pkgs) else [ ]); + config = channelsConfig // (value.config or { }); + }; + + pkgs = mapAttrs importChannel channels; + + mkOutput = output: builder: + mergeAny + # prevent override of nested outputs in otherArguments + (optionalAttrs (otherArguments ? ${output}.${system}) + { ${output} = otherArguments.${output}.${system}; }) + (optionalAttrs (args ? ${builder}) + { ${output} = args.${builder} pkgs; }); + in + { inherit pkgs; } + // mkOutput "packages" "packagesBuilder" + // mkOutput "defaultPackage" "defaultPackageBuilder" + // mkOutput "apps" "appsBuilder" + // mkOutput "defaultApp" "defaultAppBuilder" + // mkOutput "devShell" "devShellBuilder" + // mkOutput "checks" "checksBuilder" + ) + # produces attrset in the shape of + # { nixosConfigurations = {}; darwinConfigurations = {}; ... } + # according to profile.output or the default `nixosConfigurations` + // foldHosts (attrValues (mapAttrs configurationBuilder hosts)) ) - - // { - nixosConfigurations = nixosConfigurations // (builtins.mapAttrs nixosConfigurationBuilder nixosProfiles); -} -