diff --git a/README.md b/README.md index 801fea49..db478ab0 100644 --- a/README.md +++ b/README.md @@ -125,12 +125,12 @@ source by. | `home-manager` | `home-manager` | Home Manager configurations | | `home-manager-minimal` | `home-manager` | the `homeManager` class with Home Manager's necessary modules alone, the configuration importing what it uses from `modulesPath`; a second integration over the class `home-manager` owns | | `hjem` | `hjem` | the files and packages of a user, as hjem manifests; activated by the NixOS configuration they are declared inside, or applied with `hjem standalone` | -| `terranix` | `terranix` | Terranix and Terraform configurations | +| `terranix` | `terranix` | Terraform configurations written as terranix modules, published as `terranixConfigurations` of the `config.tf.json` files Terraform reads | | `caisson-colmena` | `colmena` | colmena configurations whose machines are NixOS configurations declared inside them, published as the hive colmena's command reads; the colmena integration we recommend for new configurations | | `colmena` | `colmena` | a hive written for colmena, taken as it is, with each node a NixOS configuration; for porting an existing hive and the modules written for it | | `caisson-disko` | `disko` | disk layouts, with the NixOS configurations installed onto each declared inside it; a machine gets the layout's file systems and no disko scripts; the disko integration we recommend for new configurations | | `disko` | `disko` | disk layouts written as disko reads them, whose machines import disko's NixOS module, so disko's command, `disko-install` and its image builder work on them | -| `system-manager` | `system-manager` | system-manager configurations on foreign distros | +| `system-manager` | `system-manager` | the systems of machines running another Linux distribution, published as the `systemConfigs` that `system-manager switch` reads | | `structural` | none | a configuration that only exports and holds configurations beneath it: the top of a repository whose point is what it exports, such as caisson itself, or a layer at any depth | Each integration finds its ecosystem in this order: the `ecosystemSrc` diff --git a/configs/flake/caisson/partitions/checks.nix b/configs/flake/caisson/partitions/checks.nix index 0b6ac347..fd9acec4 100644 --- a/configs/flake/caisson/partitions/checks.nix +++ b/configs/flake/caisson/partitions/checks.nix @@ -178,7 +178,7 @@ }; in builtins.seq suite.summary (pkgs.runCommand "pinned-world" { } "touch $out"); - # The manifest file of an hjem configuration inside a NixOS + # The manifest file of a hjem configuration inside a NixOS # machine and the units that activate it, built. Building # the manifest file checks it against hjem's own schema # with `cue vet`, so the file the machine activates is one @@ -297,7 +297,7 @@ }; }; in - # The bare machine, and the machine with a home and an hjem + # The bare machine, and the machine with a home and a hjem # configuration inside it, whose system holds the activation # of the home and the launchd agents of hjem. probe.pkgs.linkFarm "darwin-system" [ diff --git a/docs/development/testing-architecture.md b/docs/development/testing-architecture.md index 4f4def03..16368171 100644 --- a/docs/development/testing-architecture.md +++ b/docs/development/testing-architecture.md @@ -312,7 +312,7 @@ The checks partition merges outputs from all test and example flakes into the | `literate-flake-greeting` | `examples/literate-flake/` | Example greeting package builds | | `debug-disabled` | `checks.nix` (inline) | `self.debug` is not exposed in production | | `pinned-world` | `checks.nix` + `tests/pinned-world/` | caisson composed with the pinned upstream world (nixpkgs, home-manager, colmena, disko, nix-darwin, hjem, terranix, system-manager) evaluates end to end; the `drift` workflow runs the same check against advanced pins | -| `hjem-system` | `checks.nix` + `tests/hjem-system/` | The manifest file of an hjem configuration inside a NixOS machine builds, which checks it against hjem's schema with `cue vet`, and so do the units that activate it, which runs shellcheck on their scripts | -| `darwin-system` (aarch64-darwin) | `checks.nix` + `tests/darwin-system/` | The two macOS machines of the pinned-world suite, declared through the darwin integration, build: the bare machine, and the machine with a home and an hjem configuration inside it; the `darwin-build` job of the check workflow builds them on a macOS runner | +| `hjem-system` | `checks.nix` + `tests/hjem-system/` | The manifest file of a hjem configuration inside a NixOS machine builds, which checks it against hjem's schema with `cue vet`, and so do the units that activate it, which runs shellcheck on their scripts | +| `darwin-system` (aarch64-darwin) | `checks.nix` + `tests/darwin-system/` | The two macOS machines of the pinned-world suite, declared through the darwin integration, build: the bare machine, and the machine with a home and a hjem configuration inside it; the `darwin-build` job of the check workflow builds them on a macOS runner | | `argument-errors` | `checks.nix` + `tests/argument-errors/` | The argument error of every entry point comes from Nix itself, raised at the call site with no frame of caisson | | `eval-weight` | `checks.nix` + `tests/eval-weight/` | Framework evaluation cost held to committed ceilings ([guide](https://nix-caisson.github.io/docs/eval-weight.html)) | diff --git a/docs/development/testing.md b/docs/development/testing.md index 6b3b3a13..5cd74043 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -28,7 +28,7 @@ plumbing works. configuration, a NixOS system with homes declared inside it, a colmena hive, disk layouts with the machines installed onto them, a darwin machine evaluated at aarch64-darwin with and without a home - and an hjem configuration inside it, hjem configurations on + and a hjem configuration inside it, hjem configurations on their own and inside a NixOS machine, a terranix and a system-manager configuration, and the composition guarantees of the real exported overlays. The `hjem-system` check builds the manifest file of the hjem diff --git a/lib-overlays/hjem/default.nix b/lib-overlays/hjem/default.nix index 5adc3221..4e0cff25 100644 --- a/lib-overlays/hjem/default.nix +++ b/lib-overlays/hjem/default.nix @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT # # The hjem integration: it owns the `hjem` class, hjem's class for the -# files and packages of a user. An hjem configuration is the home of a +# files and packages of a user. A hjem configuration is the home of a # user, evaluated at every system in force where it is declared. # # hjem itself evaluates a user only inside its NixOS and nix-darwin @@ -52,7 +52,7 @@ context = "caisson.hjem"; }; - # An hjem source: the hjem flake or source tree, which holds the + # A hjem source: the hjem flake or source tree, which holds the # user module and hjem's library. assertHjemSrc = ecosystemSrc: @@ -66,7 +66,7 @@ then ecosystemSrc else - throw "${context} requires `ecosystemSrc` to be an hjem flake or source tree (holding modules/common/user.nix and lib.nix)."; + throw "${context} requires `ecosystemSrc` to be a hjem flake or source tree (holding modules/common/user.nix and lib.nix)."; # The manifest of an evaluated user, as hjem's OS modules write # it: version 3, with an entry for every enabled file of the @@ -232,7 +232,7 @@ }; }; - # An hjem configuration is published as + # A hjem configuration is published as # `hjemConfigurations.`, with `manifest` and `packages`, # which is what `hjem standalone switch --flake` reads. exportsTo = { @@ -242,7 +242,7 @@ }; }; - # What a reader of a file gets from an hjem configuration that is + # What a reader of a file gets from a hjem configuration that is # a top: `manifest` and `packages`, which `hjem standalone switch # --config` reads, and the manifest of the evaluation under # `caisson.manifest`. hjem's CLI serializes the whole value with @@ -273,7 +273,7 @@ args: final.caisson.integrations.mkModuleConfiguration { type = "hjem"; - # An hjem configuration is evaluated at a system: it has an + # A hjem configuration is evaluated at a system: it has an # evaluation for every system in force where it is declared. perSystem = true; defaultPkgs = args.defaultPkgs or null; diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index 2b4e6b81..7282ebc4 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -2,9 +2,37 @@ # # The system-manager integration: it owns the `systemManager` # class and evaluates it with `makeSystemConfig` from the -# system-manager flake. +# system-manager flake. A system-manager configuration is the system +# of a machine running another Linux distribution, evaluated per +# system, once for each system in force where it is declared, and +# published as +# `systemConfigs.`, the system `system-manager switch --flake` +# reads. +# +# A system-manager configuration runs on the package set selected for +# it (`defaultPkgs`, else the set named `default`), as every other +# integration does, and on the nixpkgs the composition declares, as a +# NixOS configuration does. system-manager's flake publishes +# `makeSystemConfig` from `nix/lib.nix` called with the nixpkgs it pins; +# caisson calls the same file with the composition's nixpkgs and the +# composed library, as the NixOS integration calls +# `nixos/lib/eval-config.nix`, so the library, the NixOS modules +# system-manager imports and the evaluation all come from there. +# +# `makeSystemConfig` makes `pkgs` itself, as a `_module.args` entry that +# imports its nixpkgs, and declares `nixpkgs.pkgs` read-only with that +# argument as its default. caisson hands the selected set in as the +# `pkgs` special argument instead. The module system takes a special +# argument before `_module.args`, so the selected set is the `pkgs` of +# every module and `nixpkgs.pkgs`. `userborn`, which `makeSystemConfig` takes from the +# userborn flake system-manager pins, is built from that same source on +# the selected set. The options `nixpkgs.overlays` and `nixpkgs.config` +# would then change nothing, so a configuration that sets them is +# refused, as one whose `nixpkgs.hostPlatform` differs from the system +# of the selected set is; package configs and package overlays are +# declared on `mkLib`. The package sets of the composition at the +# system reach the modules as the `pkgSets` argument. { - closure-inputs, mkLibOverlay, ... }: @@ -23,82 +51,45 @@ overlay = final: prev: let - selection = final.caisson.integrations; + context = "lib.caisson.system-manager.mkConfiguration"; resolveEcosystemSrc = final.caisson.integrations.resolveEcosystemSrc { name = "system-manager"; context = "caisson.system-manager"; }; + # The nixpkgs the evaluation runs on: the one the composition + # declares, as for a NixOS configuration. + resolveNixpkgsSrc = final.caisson.integrations.resolveEcosystemSrc { + name = "nixpkgs"; + context = "caisson.system-manager"; + }; + + # A system-manager source: the system-manager flake, which holds + # `nix/lib.nix` and pins the userborn flake its modules run. assertSystemManagerEcosystemSrc = ecosystemSrc: - if ecosystemSrc ? lib && ecosystemSrc.lib ? makeSystemConfig then + if + builtins.isAttrs ecosystemSrc + && (ecosystemSrc.inputs or { }) ? userborn + && builtins.pathExists "${ecosystemSrc}/nix/lib.nix" + then ecosystemSrc else - throw "lib.caisson.system-manager.mkConfiguration requires `ecosystemSrc.lib.makeSystemConfig`."; + throw "${context} requires `ecosystemSrc` to be the system-manager flake (with its inputs, holding nix/lib.nix)."; - mkCommonArgs = - { - configModule, - moduleImports ? selection.defaultModuleImports, - extraModuleImports ? (_registry: [ ]), - specialArgs ? { }, - pkgSets ? null, - ... - }: + # system-manager imports selected NixOS modules from the nixpkgs + # the evaluation runs on; current nixos-unstable restructured + # nixos/modules/config/nix.nix in ways system-manager's module set + # does not absorb. They are bridged here, where every + # system-manager evaluation composes. Delete the bridge when + # upstream absorbs the restructure: each half fails loudly + # (duplicate declaration / unused disable) when its reason + # disappears. + compatModulesFor = + { src, nixpkgsSrc }: let - registry = final.caisson.modules.systemManager or { }; - # The framework module of the class: every registered `core`, forced. - coreModules = selection.coreModules registry; - selectedModules = moduleImports registry ++ extraModuleImports registry; - # system-manager instantiates nixpkgs itself from - # `nixpkgs.hostPlatform`; a supplied package set seeds that - # platform (an explicit hostPlatform wins) and is passed as - # the `pkgSets` module argument. - pkgSetsModule = - if pkgSets != null && pkgSets ? pkgs then - [ - { - _file = "caisson-system-manager:pkgSets"; - nixpkgs.hostPlatform = final.mkDefault pkgSets.pkgs.stdenv.hostPlatform.system; - } - ] - else - [ ]; - in - { - modules = coreModules ++ selectedModules ++ pkgSetsModule ++ [ configModule ]; - # Framework defaults first; the values the caller passed win - # on conflict. - specialArgs = { - inputs = closure-inputs; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // specialArgs; - }; - - # makeSystemConfig's arguments, composed from the caisson arguments, - # plus the compatibility bridge below. - compose = - args: - let - src = assertSystemManagerEcosystemSrc (resolveEcosystemSrc { - explicit = args.ecosystemSrc or null; - manifest = final.caisson-core.libManifest or { }; - }); - common = mkCommonArgs args; - - # system-manager imports selected NixOS modules from the - # nixpkgs input pinned in its flake; current nixos-unstable - # restructured nixos/modules/config/nix.nix in ways - # system-manager's module set (tip 48d4734) does not absorb. - # They are bridged here, where every system-manager eval - # composes. - # Delete the bridge when upstream absorbs the restructure: each - # half fails loudly (duplicate declaration / unused disable) - # when its reason disappears. - smNixpkgs = src.inputs.nixpkgs; - nixosNixModuleText = builtins.readFile "${smNixpkgs}/nixos/modules/config/nix.nix"; + nixosNixModuleText = builtins.readFile "${nixpkgsSrc}/nixos/modules/config/nix.nix"; # (1) That module now defines `services.displayManager.hiddenUsers` # (hiding nixbld users from display managers), an option nothing @@ -159,67 +150,212 @@ ) ]; }; + in + final.optional (!smDeclaresDisplayManager) displayManagerSinkModule + ++ final.optional nixosNixOwnsDaemonOptions nixStubReplacementModule; + + # The evaluator's call, on the view being evaluated: + # `makeSystemConfig` over the framework modules, the selection, + # the configuration's module and the bridge, at the + # configuration's system. `ecosystemArgs`, which only the twin's + # pattern admits, is merged over the call last; `modules`, + # `specialArgs` and `allowUnsupportedNixpkgs` can be set or + # replaced there. `overlays` is refused: the selected set is the + # package set, so an overlay passed there would change nothing. + evaluate = + args: + { lib, manifest }: + let + selection = lib.caisson.integrations; + src = assertSystemManagerEcosystemSrc (resolveEcosystemSrc { + explicit = args.ecosystemSrc or null; + manifest = lib.caisson-core.libManifest or { }; + }); - compatModules = - final.optional (!smDeclaresDisplayManager) displayManagerSinkModule - ++ final.optional nixosNixOwnsDaemonOptions nixStubReplacementModule; + name = manifest.name or null; + inherit (manifest) system; + what = + if name != null then + "the system-manager configuration `${name}` at ${system}" + else + "this system-manager configuration at ${system}"; + + # The package sets available to the configuration, each at the + # system of the evaluation, and the set it runs on: the + # selection in force at its manifest, applied to those. + pkgSets = selection.pkgSetsAt { inherit context what; } manifest system; + pkgs = selection.pkgSetOf { inherit context what; } manifest pkgSets; + + nixpkgsSrc = resolveNixpkgsSrc { + explicit = null; + manifest = lib.caisson-core.libManifest or { }; + }; + + # `makeSystemConfig` from system-manager's `nix/lib.nix`, called + # as system-manager's flake calls it, with the composition's + # nixpkgs and the composed library in place of its own. + systemManagerLib = import "${src}/nix/lib.nix" { + nixpkgs = nixpkgsSrc; + inherit lib; + inherit (src.inputs) userborn; + }; + + # The userborn system-manager pins, built on the selected set. + userborn = (import "${src.inputs.userborn}/nix/packages" { inherit pkgs; }).userborn; + + ecosystemArgs = if (args.ecosystemArgs or null) != null then args.ecosystemArgs else { }; + + registry = lib.caisson.modules.systemManager or { }; + frameworkModules = selection.frameworkModules "systemManager" registry; + moduleImports = selection.moduleImportsOf "systemManager" { inherit lib manifest; } args; + + # The configuration's module: the module passed, else the + # configuration registered under the configuration's name + # (`configs/systemManager/`), else none. + registered = lib.caisson.configs.systemManager or { }; + configModule = + if (args.configModule or null) != null then + args.configModule + else if name != null then + registered.${name} or null + else + null; + + # The platform of the selected set, and the refusals of what + # the selected set makes ineffective. + systemModule = + { config, lib, ... }: + { + _file = "caisson-system-manager:system"; + nixpkgs.hostPlatform = lib.mkDefault pkgs.stdenv.hostPlatform.system; + assertions = [ + { + assertion = config.nixpkgs.overlays == [ ] && config.nixpkgs.config == { }; + message = "caisson: ${what} sets `nixpkgs.overlays` or `nixpkgs.config`, which have no effect: it runs on the package set caisson selects. Declare package configs and package overlays on `mkLib`, and select a set with `defaultPkgs`."; + } + { + assertion = + ( + if builtins.isString config.nixpkgs.hostPlatform then + config.nixpkgs.hostPlatform + else + config.nixpkgs.hostPlatform.system or null + ) == pkgs.stdenv.hostPlatform.system; + message = "caisson: ${what} sets `nixpkgs.hostPlatform` to a platform other than that of the package set it runs on, ${pkgs.stdenv.hostPlatform.system}."; + } + ]; + }; + + value = + if (ecosystemArgs.overlays or [ ]) != [ ] then + throw "${context}: ${what} passes `overlays` in `ecosystemArgs`, which have no effect: it runs on the package set caisson selects. Declare package overlays on `mkLib`." + else + systemManagerLib.makeSystemConfig ( + { + modules = + frameworkModules + ++ moduleImports registry + ++ (if configModule == null then [ ] else [ configModule ]) + ++ [ systemModule ] + ++ compatModulesFor { inherit src nixpkgsSrc; }; + # The module system takes a special argument before an + # entry of `_module.args`, so `pkgs` and `userborn` + # here are the ones every module receives, and the + # default of the read-only `nixpkgs.pkgs`. + specialArgs = { + inherit + lib + pkgSets + pkgs + userborn + ; + } + // (if (args.specialArgs or null) != null then args.specialArgs else { }); + } + // ecosystemArgs + ); in { - inherit src; - ecosystemArgs = { - inherit (common) specialArgs; - modules = common.modules ++ compatModules; - }; + inherit value; + # What `system-manager switch` activates: the system itself. + outputs.toplevel = value; }; - # Everything makeSystemConfig takes (modules, overlays, - # specialArgs, allowUnsupportedNixpkgs) can be set or replaced - # through the twin's `ecosystemArgs`. - evaluate = composed: callArgs: composed.src.lib.makeSystemConfig callArgs; + # A system-manager configuration is published as + # `systemConfigs.`, which `system-manager switch --flake` + # reads, with `default` as the name it reads when none is given. + exportsTo = { + attrset = "systemConfigs"; + value = manifest: manifest.value; + }; - evaluation = selection.mkEvaluation { inherit compose evaluate; }; + configuration = + args: + final.caisson.integrations.mkModuleConfiguration { + type = "system-manager"; + # A system-manager configuration is evaluated at a system: it + # has an evaluation for every system in force where it is + # declared. + perSystem = true; + defaultPkgs = args.defaultPkgs or null; + inherit exportsTo; + evaluate = evaluate args; + }; - integration = selection.mkIntegration { + integration = final.caisson.integrations.mkIntegration { name = "system-manager"; class = "systemManager"; - # `mkCommonArgs` destructures `configModule` without a default - # and supplies the value of every optional argument left out; - # system-manager instantiates nixpkgs itself, so `pkgSets` is - # optional. + # A system-manager configuration that is a top, as a tool reads + # it: the system, or the systems by system where several are in + # force. + top = final.caisson.integrations.topValue; + # What these return is a configuration, a function of + # `{ name, parent }`: a parent that declares it under + # `caisson.system-manager.configurations.` finalizes it, + # and `mkTopConfiguration` finalizes it at a top. mkConfiguration = { - # The configuration's module. Further modules of the class - # are selected with `moduleImports`, from the registry. - configModule, - # The package sets, handed to the modules as the `pkgSets` - # special argument; `pkgSets.pkgs` seeds - # `nixpkgs.hostPlatform`. - pkgSets ? null, + # The configuration's module. When absent, the configuration + # registered under the configuration's name + # (`lib.caisson.configs.systemManager.`), if any. + # Further modules of the class are selected with + # `moduleImports`, from the registry. + configModule ? null, # The system-manager flake; resolved from the composition's # declarations when absent. ecosystemSrc ? null, + # The package set the configuration runs on: a function + # that receives the package sets available where it is + # declared, by package config name, each at the system of + # the evaluation, and returns the set to run on. When + # absent, the selection of the nearest configuration above, + # and the set named `default` where none above selects. + defaultPkgs ? null, # The selection over the systemManager class of the - # registry; every entry named `default` when absent. + # registry. It replaces the default of the class, which is + # every entry named `default` followed by what the + # configurations above added. moduleImports ? null, + # A selection added to that selection, whichever it is. extraModuleImports ? null, # Extra module arguments, merged over those the framework - # supplies; system-manager names these `extraSpecialArgs`. + # supplies. specialArgs ? null, }@args: - evaluation args; + configuration args; # The same arguments and `ecosystemArgs`, the evaluator's # arguments merged over the composed call last. mkConfigurationWithEcosystemArgs = { - configModule, - pkgSets ? null, + configModule ? null, ecosystemSrc ? null, + defaultPkgs ? null, moduleImports ? null, extraModuleImports ? null, specialArgs ? null, ecosystemArgs ? null, }@args: - evaluation args; + configuration args; }; in { diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index a9233a93..6c9701aa 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -1,9 +1,24 @@ # SPDX-License-Identifier: MIT # # The terranix integration: it owns the `terranix` class and -# evaluates it with `terranixConfiguration` from the terranix flake. +# evaluates it with `terranixConfiguration` from the terranix flake. A +# terranix configuration is a Terraform configuration written as +# modules, evaluated at every system in force where it is declared, +# on the package set selected for it. +# +# Its output is `json`, the `config.tf.json` file Terraform reads, +# which is also the value: `terranixConfiguration` returns that file +# with the evaluated `config` in its `passthru`. A configuration is +# published as `terranixConfigurations.`, the name terranix's +# flake-parts module gives its configurations. +# +# terranix runs its own module system on the library of its package +# set, so the composed library reaches the modules as the `lib` +# argument while terranix merges and checks the options with its own. +# terranix returns only the configuration Terraform reads and its +# `_meta`, so caisson reads its own options through `_meta.caisson`, +# and a terranix configuration holds no configurations beneath it. { - closure-inputs, mkLibOverlay, ... }: @@ -22,7 +37,7 @@ overlay = final: prev: let - selection = final.caisson.integrations; + context = "lib.caisson.terranix.mkConfiguration"; resolveEcosystemSrc = final.caisson.integrations.resolveEcosystemSrc { name = "terranix"; @@ -34,98 +49,164 @@ if ecosystemSrc ? lib && ecosystemSrc.lib ? terranixConfiguration then ecosystemSrc else - throw "lib.caisson.terranix requires `ecosystemSrc.lib.terranixConfiguration`."; + throw "${context} requires `ecosystemSrc.lib.terranixConfiguration`."; - # terranixConfiguration's arguments, composed from the caisson - # arguments: pkgSets.pkgs is `pkgs`, the selected class modules - # and the config module are `modules`, and specialArgs becomes - # terranix's `extraArgs` (framework defaults first; the values the - # caller passed win on conflict). - compose = - { - ecosystemSrc ? null, - configModule, - moduleImports ? selection.defaultModuleImports, - extraModuleImports ? (_registry: [ ]), - specialArgs ? { }, - pkgSets ? null, - ... - }: + # The evaluator's call, on the view being evaluated: + # `terranixConfiguration` over the framework modules, the + # selection and the configuration's module, on the package set + # selected for the configuration. `ecosystemArgs`, which only the + # twin's pattern admits, is merged over the call last; everything + # `terranixConfiguration` takes (`pkgs`, `modules`, `extraArgs`, + # `strip_nulls`) can be set or replaced there. + evaluate = + args: + { lib, manifest }: let + selection = lib.caisson.integrations; src = assertTerranixEcosystemSrc (resolveEcosystemSrc { - explicit = ecosystemSrc; - manifest = final.caisson-core.libManifest or { }; + explicit = args.ecosystemSrc or null; + manifest = lib.caisson-core.libManifest or { }; }); - registry = final.caisson.modules.terranix or { }; - # The framework module of the class: every registered `core`, forced. - coreModules = selection.coreModules registry; - selectedModules = moduleImports registry ++ extraModuleImports registry; + + name = manifest.name or null; + inherit (manifest) system; + what = + if name != null then + "the terranix configuration `${name}` at ${system}" + else + "this terranix configuration at ${system}"; + + pkgSets = selection.pkgSetsAt { inherit context what; } manifest system; + pkgs = selection.pkgSetOf { inherit context what; } manifest pkgSets; + + registry = lib.caisson.modules.terranix or { }; + frameworkModules = selection.frameworkModules "terranix" registry; + moduleImports = selection.moduleImportsOf "terranix" { inherit lib manifest; } args; + + # The configuration's module: the module passed, else the + # configuration registered under the configuration's name + # (`configs/terranix/`), else none. + registered = lib.caisson.configs.terranix or { }; + configModule = + if (args.configModule or null) != null then + args.configModule + else if name != null then + registered.${name} or null + else + null; + + # terranix returns the configuration with the keys Terraform + # reads alone, and `_meta`, the metadata it passes through + # without rendering it. caisson's options reach the result + # there. + metaModule = + { config, ... }: + { + _file = "caisson-terranix:meta"; + _meta.caisson = config.caisson; + }; + + value = src.lib.terranixConfiguration ( + { + inherit pkgs; + modules = + frameworkModules + ++ moduleImports registry + ++ (if configModule == null then [ ] else [ configModule ]) + ++ [ metaModule ]; + # terranix names the special arguments `extraArgs`. + extraArgs = { + inherit lib pkgSets; + } + // (if (args.specialArgs or null) != null then args.specialArgs else { }); + } + // (if (args.ecosystemArgs or null) != null then args.ecosystemArgs else { }) + ); in { - inherit src; - ecosystemArgs = (if pkgSets != null && pkgSets ? pkgs then { pkgs = pkgSets.pkgs; } else { }) // { - modules = coreModules ++ selectedModules ++ [ configModule ]; - extraArgs = { - inputs = closure-inputs; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // specialArgs; - }; + inherit value; + # What Terraform reads: the `config.tf.json` file. + outputs.json = value; + # caisson's options, from `_meta`. terranix keeps the options + # of the evaluation to itself, so a terranix configuration + # holds no configurations beneath it. + config.caisson = value._meta.caisson; + options = { }; }; - # terranix evaluates against a package set, `pkgs` or a set it - # instantiates for `system`. The composed call carries `pkgs` from - # `pkgSets.pkgs`; the twin may supply either in `ecosystemArgs`. - evaluate = - composed: callArgs: - if !(callArgs ? pkgs || callArgs ? system) then - throw "lib.caisson.terranix: terranixConfiguration needs a package set; pass `pkgSets.pkgs`, or `pkgs` or `system` in `ecosystemArgs`." - else - composed.src.lib.terranixConfiguration callArgs; + # A terranix configuration is published as + # `terranixConfigurations.`, its `config.tf.json` file. + exportsTo = { + attrset = "terranixConfigurations"; + value = manifest: manifest.value; + }; - evaluation = selection.mkEvaluation { inherit compose evaluate; }; + configuration = + args: + final.caisson.integrations.mkModuleConfiguration { + type = "terranix"; + # A terranix configuration is evaluated at a system: it has an + # evaluation for every system in force where it is declared. + perSystem = true; + defaultPkgs = args.defaultPkgs or null; + inherit exportsTo; + evaluate = evaluate args; + }; - integration = selection.mkIntegration { + integration = final.caisson.integrations.mkIntegration { name = "terranix"; class = "terranix"; - # `compose` destructures `configModule` without a default and - # supplies the value of every optional argument left out. - # `pkgSets` is optional here: the evaluator step accepts a - # package set from `ecosystemArgs` instead, and reports the - # miss when neither arrives. + # A terranix configuration that is a top, as a tool reads it: + # the `config.tf.json` file, or the files by system where + # several systems are in force. + top = final.caisson.integrations.topValue; + # What these return is a configuration, a function of + # `{ name, parent }`: a parent that declares it under + # `caisson.terranix.configurations.` finalizes it, and + # `mkTopConfiguration` finalizes it at a top. mkConfiguration = { - # The configuration's module. Further modules of the class - # are selected with `moduleImports`, from the registry. - configModule, - # The package sets; `pkgSets.pkgs` is the `pkgs` terranix - # evaluates against, in place of `system`. - pkgSets ? null, + # The configuration's module. When absent, the configuration + # registered under the configuration's name + # (`lib.caisson.configs.terranix.`), if any. Further + # modules of the class are selected with `moduleImports`, + # from the registry. + configModule ? null, # The terranix flake; resolved from the composition's # declarations when absent. ecosystemSrc ? null, - # The selection over the terranix class of the registry; - # every entry named `default` when absent. + # The package set the configuration runs on: a function + # that receives the package sets available where it is + # declared, by package config name, each at the system of + # the evaluation, and returns the set to run on. When + # absent, the selection of the nearest configuration above, + # and the set named `default` where none above selects. + defaultPkgs ? null, + # The selection over the terranix class of the registry. It + # replaces the default of the class, which is every entry + # named `default` followed by what the configurations + # above added. moduleImports ? null, + # A selection added to that selection, whichever it is. extraModuleImports ? null, # Extra module arguments, merged over those the framework # supplies; terranix names these `extraArgs`. specialArgs ? null, }@args: - evaluation args; + configuration args; # The same arguments and `ecosystemArgs`, the evaluator's # arguments merged over the composed call last. mkConfigurationWithEcosystemArgs = { - configModule, - pkgSets ? null, + configModule ? null, ecosystemSrc ? null, + defaultPkgs ? null, moduleImports ? null, extraModuleImports ? null, specialArgs ? null, ecosystemArgs ? null, }@args: - evaluation args; + configuration args; }; in { diff --git a/modules/darwin/default/caisson/forChildren.nix b/modules/darwin/default/caisson/forChildren.nix index f70efda5..50601e4d 100644 --- a/modules/darwin/default/caisson/forChildren.nix +++ b/modules/darwin/default/caisson/forChildren.nix @@ -7,7 +7,7 @@ # it through the options under `caisson.forChildren`. This file adds # the home-manager module `darwin-parent` to the modules that a home # declared inside this darwin configuration imports by default, and -# the hjem module `darwin-parent` to those of an hjem configuration +# the hjem module `darwin-parent` to those of a hjem configuration # declared inside it. Each sets the options that come from the machine: # the home directory from the account of the user, and for a home the # user ID and the Nix of the machine. diff --git a/modules/darwin/hjem-activation/default.nix b/modules/darwin/hjem-activation/default.nix index b9594e8c..dece6258 100644 --- a/modules/darwin/hjem-activation/default.nix +++ b/modules/darwin/hjem-activation/default.nix @@ -3,7 +3,7 @@ # A darwin module with which a macOS machine activates the hjem # configurations declared inside it. # -# Activating an hjem configuration means applying its manifest with +# Activating a hjem configuration means applying its manifest with # hjem's CLI. hjem has a nix-darwin module that does this for the users # it embeds, with a launchd agent, `org.hjem.activate`, that applies the # manifest of the user it runs for and keeps the state in diff --git a/modules/generic/core/caisson/modules.nix b/modules/generic/core/caisson/modules.nix index a62f9db6..790bc974 100644 --- a/modules/generic/core/caisson/modules.nix +++ b/modules/generic/core/caisson/modules.nix @@ -1,5 +1,10 @@ # SPDX-License-Identifier: MIT -{ config, lib, ... }: +{ + config, + lib, + options, + ... +}: let registeredModules = config.caisson.manifest.modules; @@ -24,23 +29,44 @@ let ++ builtins.attrNames (if evalManifest == null then { } else evalManifest.modules or { }) ); + # The types of the options, built once for every class. The modules + # are exported as they were registered. + types = { + enabled = lib.types.bool; + exported = lib.types.functionTo (lib.types.lazyAttrsOf lib.types.raw); + }; + + # True when a module of the evaluation defines the option: its highest + # priority is above the priority of an option default. + defined = option: option.highestPrio < 1500; + + # A class whose options no module defines exports its default, the + # modules the composition registers itself, without merging the two + # options, which spares merging them in every evaluation for every + # class. exportedClassModules = builtins.mapAttrs ( class: classModules: let + classOptions = options.caisson.modules.${class}; classConfig = config.caisson.modules.${class}; in - if classConfig.export.enabled then classConfig.exported classModules else { } + if !(defined classOptions.export.enabled) && !(defined classOptions.exported) then + localModules class classModules + else if classConfig.export.enabled then + classConfig.exported classModules + else + { } ) registeredModules; in { options.caisson.modules = lib.genAttrs classes (class: { export.enabled = lib.mkOption { - type = lib.types.bool; + type = types.enabled; default = true; description = "Whether to export the modules of the class `${class}`."; }; exported = lib.mkOption { - type = lib.types.functionTo (lib.types.attrsOf lib.types.deferredModule); + type = types.exported; default = localModules class; defaultText = lib.literalMD "the modules of the class this composition registers itself"; description = '' diff --git a/modules/hjem/darwin-parent/default.nix b/modules/hjem/darwin-parent/default.nix index f4306004..caad88fc 100644 --- a/modules/hjem/darwin-parent/default.nix +++ b/modules/hjem/darwin-parent/default.nix @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT # -# An hjem module for an hjem configuration declared inside a darwin +# A hjem module for a hjem configuration declared inside a darwin # configuration. It sets the home directory of the user from the # account the machine declares, as hjem's nix-darwin module does for # the users it embeds, and refuses a user the machine has no account @@ -9,12 +9,12 @@ # caisson does not use hjem's nix-darwin module. A darwin configuration # adds this module to the modules that the hjem configurations declared # inside it import by default; caisson's `default` darwin module does -# that, through `caisson.forChildren`. An hjem configuration that passes +# that, through `caisson.forChildren`. A hjem configuration that passes # `moduleImports` selects its modules itself, and gets this module only # if it lists it. # # The module reads the machine through the manifest of the hjem -# configuration, so it can only be imported by an hjem configuration +# configuration, so it can only be imported by a hjem configuration # that has a darwin configuration above it. { ... }: # `lib` is the library of the hjem configuration, and `config` is its diff --git a/modules/hjem/nixos-parent/default.nix b/modules/hjem/nixos-parent/default.nix index 18235e8a..e1e2294c 100644 --- a/modules/hjem/nixos-parent/default.nix +++ b/modules/hjem/nixos-parent/default.nix @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT # -# An hjem module for an hjem configuration declared inside a NixOS +# A hjem module for a hjem configuration declared inside a NixOS # configuration. It sets the home directory of the user from the # account the machine declares, as hjem's NixOS module does for the # users it embeds, and refuses a user the machine has no account for. @@ -8,12 +8,12 @@ # caisson does not use hjem's NixOS module. A NixOS configuration adds # this module to the modules that the hjem configurations declared # inside it import by default; caisson's `default` NixOS module does -# that, through `caisson.forChildren`. An hjem configuration that +# that, through `caisson.forChildren`. A hjem configuration that # passes `moduleImports` selects its modules itself, and gets this # module only if it lists it. # # The module reads the machine through the manifest of the hjem -# configuration, so it can only be imported by an hjem configuration +# configuration, so it can only be imported by a hjem configuration # that has a NixOS configuration above it. { ... }: # `lib` is the library of the hjem configuration, and `config` is its diff --git a/modules/nixos/default/caisson/forChildren.nix b/modules/nixos/default/caisson/forChildren.nix index 3e50c996..2b5e74b1 100644 --- a/modules/nixos/default/caisson/forChildren.nix +++ b/modules/nixos/default/caisson/forChildren.nix @@ -6,7 +6,7 @@ # it through the options under `caisson.forChildren`. This file adds # two home-manager modules, `nixos-parent` and `nixos-source-marker`, # to the modules that a home declared inside this NixOS configuration -# imports by default, and the hjem module `nixos-parent` to those of an +# imports by default, and the hjem module `nixos-parent` to those of a # hjem configuration declared inside it. # # `nixos-source-marker` is the module caisson registers as @@ -38,7 +38,7 @@ closure-lib.caisson.modules.homeManager.nixos-source-marker ]; - # The hjem module `nixos-parent` sets the home directory of an hjem + # The hjem module `nixos-parent` sets the home directory of a hjem # configuration declared inside this machine from the account of its # user, and refuses a user the machine has no account for. caisson.forChildren.extraDefaultModuleImports.hjem = _lib: [ diff --git a/modules/nixos/hjem-activation/default.nix b/modules/nixos/hjem-activation/default.nix index 0eaea2f1..af0f9b43 100644 --- a/modules/nixos/hjem-activation/default.nix +++ b/modules/nixos/hjem-activation/default.nix @@ -3,12 +3,12 @@ # A NixOS module with which a machine activates the hjem configurations # declared inside it. # -# Activating an hjem configuration means applying its manifest with +# Activating a hjem configuration means applying its manifest with # hjem's CLI: `hjem internal activate` links the user's files and # replaces those of the previous generation, whose manifest is kept in # `/var/lib/hjem/manifest-.json`. hjem has a NixOS module that # does this for the users it embeds. caisson does not use that NixOS -# module. An hjem configuration declared inside a NixOS configuration +# module. A hjem configuration declared inside a NixOS configuration # is a caisson configuration, and this module writes the units hjem's # NixOS module writes, under the same names. Each template unit has an # instance for each user: diff --git a/modules/systemManager/core b/modules/systemManager/core new file mode 120000 index 00000000..3715354a --- /dev/null +++ b/modules/systemManager/core @@ -0,0 +1 @@ +../generic/core \ No newline at end of file diff --git a/modules/terranix/core b/modules/terranix/core new file mode 120000 index 00000000..3715354a --- /dev/null +++ b/modules/terranix/core @@ -0,0 +1 @@ +../generic/core \ No newline at end of file diff --git a/tests/argument-errors/check.sh b/tests/argument-errors/check.sh index 1a0be246..e6278da7 100755 --- a/tests/argument-errors/check.sh +++ b/tests/argument-errors/check.sh @@ -20,14 +20,10 @@ lib_expr="$1" shift flags=("$@") -# Every entry point takes `configModule`. The structural, caisson-colmena, -# caisson-disko, disko, flake-parts, nixos, nixos-minimal, darwin, hjem, -# home-manager and home-manager-minimal entry points require nothing: -# each finds the configuration registered under the name of the -# configuration. The others require `configModule`, and take `pkgSets`, -# which the nixos, darwin, hjem and home-manager entry points do not: a -# NixOS configuration, a darwin configuration, an hjem configuration and -# a home take their package sets from the composition. +# Every entry point takes `configModule` and requires nothing: each +# finds the configuration registered under the name of the +# configuration. None takes `pkgSets`; the configurations take their +# package sets from the composition. namespaces=( nixos nixos-minimal @@ -87,31 +83,14 @@ check() { fi } -missing="function '[^']*%s' called without required argument '(configModule|pkgSets)'" +unexpected="function '[^']*%s' called with unexpected argument 'bogus'" # The arguments a call passes beside the wrong argument. -given_for() { - case "$1" in - nixos | nixos-minimal | darwin | hjem | home-manager | home-manager-minimal) printf 'configModule = { };' ;; - *) printf 'configModule = { }; pkgSets.pkgs = { };' ;; - esac -} -requires_nothing() { - case "$1" in - structural | caisson-colmena | caisson-disko | disko | flake-parts | nixos | nixos-minimal | darwin | hjem | home-manager | home-manager-minimal) return 0 ;; - *) return 1 ;; - esac -} -unexpected="function '[^']*%s' called with unexpected argument 'bogus'" +given='configModule = { };' for ns in "${namespaces[@]}"; do - given=$(given_for "$ns") for fn in mkConfiguration mkConfigurationWithEcosystemArgs; do ep="lib.caisson.$ns.$fn" - if ! requires_nothing "$ns"; then - # shellcheck disable=SC2059 - check "$ns.$fn { }" "$ep { }" "$(printf "$missing" "$fn")" - fi # shellcheck disable=SC2059 check "$ns.$fn { bogus, ... }" \ "$ep { bogus = 1; $given }" "$(printf "$unexpected" "$fn")" @@ -138,6 +117,11 @@ check "nixos.mkConfiguration { pkgSets, ... }" \ check "home-manager.mkConfiguration { pkgSets, ... }" \ "lib.caisson.home-manager.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }" \ "function '[^']*mkConfiguration' called with unexpected argument 'pkgSets'" +for ns in terranix system-manager; do + check "$ns.mkConfiguration { pkgSets, ... }" \ + "lib.caisson.$ns.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }" \ + "function '[^']*mkConfiguration' called with unexpected argument 'pkgSets'" +done # The upstream-compatible colmena entry points take the hive in place of # `configModule`, and require it. diff --git a/tests/darwin-system/default.nix b/tests/darwin-system/default.nix index 56372d7a..616a44b8 100644 --- a/tests/darwin-system/default.nix +++ b/tests/darwin-system/default.nix @@ -9,7 +9,7 @@ # there is nixpkgs at aarch64-darwin, and with nixpkgs, nix-darwin, # home-manager and hjem declared as its ecosystems. A structural top declares the machine # `probe` from `machine`, and the machine `family`, which is `machine` -# with a home and an hjem configuration for the user `probe` declared +# with a home and a hjem configuration for the user `probe` declared # inside it. Evaluating them needs no Mac: the package set # and the derivations are evaluated for aarch64-darwin wherever the # evaluation runs. Building the system needs a darwin builder. @@ -69,7 +69,7 @@ let }; }; - # A home and an hjem configuration for the user `probe`. + # A home and a hjem configuration for the user `probe`. home = { home.stateVersion = "25.05"; home.file.".probe".text = "home"; diff --git a/tests/hjem-system/default.nix b/tests/hjem-system/default.nix index 1194fa41..7b987192 100644 --- a/tests/hjem-system/default.nix +++ b/tests/hjem-system/default.nix @@ -7,11 +7,11 @@ # # The composition is at x86_64-linux, with a package config whose set # there is nixpkgs, and with nixpkgs and hjem declared as its -# ecosystems. `user` is an hjem module with a file and a package. -# `standalone` is an hjem configuration that is a top, for the user +# ecosystems. `user` is a hjem module with a file and a package. +# `standalone` is a hjem configuration that is a top, for the user # `probe` with the home directory it sets itself. `top` is a structural # top holding a NixOS machine `probe-machine`, which declares the -# account of `probe` and an hjem configuration `probe` from +# account of `probe` and a hjem configuration `probe` from # `machineUser`, which is `user` with a systemd user unit. { inputs }: let diff --git a/tests/pinned-world/default.nix b/tests/pinned-world/default.nix index b1d6d6b9..b039ce86 100644 --- a/tests/pinned-world/default.nix +++ b/tests/pinned-world/default.nix @@ -604,18 +604,18 @@ let }; }; }).colmenaHive - && throws ( - composed.lib.caisson.terranix.mkConfiguration { - ecosystemSrc = { }; - configModule = { }; - } - ) - && throws ( - composed.lib.caisson.system-manager.mkConfiguration { - ecosystemSrc = { }; - configModule = { }; - } - ); + && + throws + (hiveLib.caisson.terranix.mkTopConfiguration { + ecosystemSrc = { }; + configModule = { }; + }).drvPath + && + throws + (hiveLib.caisson.system-manager.mkTopConfiguration { + ecosystemSrc = { }; + configModule = { }; + }).drvPath; homeConfigurationEvaluatesEndToEnd = let @@ -1411,7 +1411,7 @@ let && probe.configuration.config.nixpkgs.hostPlatform.system == "aarch64-darwin" && probe.configuration.pkgs.hello.drvPath == probe.pkgs.hello.drvPath; - # A home and an hjem configuration inside a darwin machine take the + # A home and a hjem configuration inside a darwin machine take the # home directory and the user ID from the account the machine # declares, and the machine activates both. Its `postActivation` # step runs the activation of the home as the user, as @@ -1457,7 +1457,7 @@ let && builtins.attrNames probe.top.homeConfigurations == [ "probe@family" ] && builtins.attrNames probe.top.hjemConfigurations == [ "probe" ]; - # An hjem configuration that is a top gives what hjem's standalone + # A hjem configuration that is a top gives what hjem's standalone # CLI reads from a file: the version 3 manifest of the user's files, # with targets in the user's home directory, and the packages. The # manifest of the evaluation sits under `caisson.manifest`, behind an @@ -1479,7 +1479,7 @@ let && builtins.toJSON top.caisson == builtins.toJSON "caisson manifest: read caisson.manifest" && top.caisson.manifest.type == "hjem"; - # An hjem configuration inside a NixOS machine takes the home + # A hjem configuration inside a NixOS machine takes the home # directory of the account the machine declares, and its manifest is # the one hjem's own NixOS module makes from the same user module, # the systemd user unit of the user included. The machine activates @@ -1526,29 +1526,99 @@ let ) && builtins.attrNames probe.top.hjemConfigurations == [ "probe" ]; + # A terranix configuration that is a top is its `config.tf.json` + # file, built from the package set of the composition with the + # evaluated configuration in its `passthru`, and its modules get the + # composed library. Declared under a structural top, it is published + # as `terranixConfigurations.`. terranixConfigurationEvaluatesEndToEnd = let - terraform = composed.lib.caisson.terranix.mkConfiguration { - ecosystemSrc = inputs.terranix; - pkgSets.pkgs = pkgs; - configModule = { + module = + { lib, ... }: + { config.terraform.required_version = ">= 1.0"; + config.output.composed.value = lib ? caisson-core; }; + terraform = hiveLib.caisson.terranix.mkTopConfiguration { + ecosystemSrc = inputs.terranix; + configModule = module; + }; + published = hiveLib.caisson.structural.mkTopConfiguration { + moduleImports = _modules: [ ]; + configModule = + { lib, ... }: + { + caisson.terranix.configurations.infra = lib.caisson.terranix.mkConfiguration { + ecosystemSrc = inputs.terranix; + configModule = module; + }; + }; }; in - builtins.isString terraform.drvPath; - + builtins.isString terraform.drvPath + && terraform.name == "config.tf.json" + && terraform.passthru.config.terraform.required_version == ">= 1.0" + && terraform.passthru.config.output.composed.value + && published.terranixConfigurations.infra.drvPath == terraform.drvPath; + + # A system-manager configuration that is a top is its system, built + # at the system of the evaluation, and its modules get the composed + # library. It runs on the package set caisson selects, the set named + # `default`, and its userborn is built on that set. A configuration + # that sets `nixpkgs.overlays`, which that set leaves without effect, + # is refused. Declared under a structural top under the name + # `default`, it is published as `systemConfigs.default`, which + # `system-manager switch --flake` reads when no name is given. systemManagerConfigEvaluatesEndToEnd = let - config = composed.lib.caisson.system-manager.mkConfiguration { + module = + { lib, ... }: + { + config.system-manager.allowAnyDistro = true; + config.environment.etc.composed.text = builtins.toJSON (lib ? caisson-core); + }; + system = hiveLib.caisson.system-manager.mkTopConfiguration { + ecosystemSrc = inputs.system-manager; + configModule = module; + }; + withOverlay = hiveLib.caisson.system-manager.mkTopConfiguration { ecosystemSrc = inputs.system-manager; - pkgSets.pkgs = pkgs; configModule = { - config.system-manager.allowAnyDistro = true; + imports = [ module ]; + nixpkgs.overlays = [ (_final: _prev: { }) ]; }; }; + runsOn = system.config.nixpkgs.pkgs; + userborn = + (import "${inputs.system-manager.inputs.userborn}/nix/packages" { inherit pkgs; }).userborn; + published = hiveLib.caisson.structural.mkTopConfiguration { + moduleImports = _modules: [ ]; + configModule = + { lib, ... }: + { + caisson.system-manager.configurations.default = lib.caisson.system-manager.mkConfiguration { + ecosystemSrc = inputs.system-manager; + configModule = module; + }; + }; + }; in - builtins.isString config.drvPath || builtins.isString (config.build.toplevel.drvPath or null); + builtins.isString system.drvPath + && ( + let + platform = system.config.nixpkgs.hostPlatform; + in + (if builtins.isString platform then platform else platform.system) == "x86_64-linux" + ) + && system.config.environment.etc.composed.text == "true" + # The pins make the nixpkgs of system-manager follow the nixpkgs + # of this suite, so a set system-manager built itself would have + # the same path and packages. Only the set the composition + # declares has the probe. + && (runsOn.pinnedWorldProbe or null) == "the set the composition declares" + && system.config.services.userborn.package.drvPath == userborn.drvPath + && !(builtins.tryEval withOverlay.drvPath).success + && published.systemConfigs.default.drvPath == system.drvPath; # The ecosystem-args twins take the evaluator's arguments in # `ecosystemArgs`, applied last. @@ -1570,16 +1640,17 @@ let ) ) ); - terraform = composed.lib.caisson.terranix.mkConfigurationWithEcosystemArgs { - ecosystemSrc = inputs.terranix; - configModule = { - config.terraform.required_version = ">= 1.0"; - }; - ecosystemArgs = { - inherit pkgs; - strip_nulls = false; - }; - }; + terraform = hiveLib.caisson.integrations.topValue ( + hiveLib.caisson-core.finalizeTop ( + hiveLib.caisson.terranix.mkConfigurationWithEcosystemArgs { + ecosystemSrc = inputs.terranix; + configModule = { + config.terraform.required_version = ">= 1.0"; + }; + ecosystemArgs.strip_nulls = false; + } + ) + ); home = hiveLib.caisson.integrations.topValue ( hiveLib.caisson-core.finalizeTop ( hiveLib.caisson.home-manager.mkConfigurationWithEcosystemArgs { diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index f54bf934..8199fae8 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -1716,12 +1716,15 @@ in # Test compositions register caisson's real terranix integration # (built from its source file, like the flake-parts # registration) alongside the harness's flake-parts - # registration. + # registration. A terranix configuration is evaluated at each + # system the composition declares, here one, so a top is the + # value of that evaluation: what the stub returns. mkResolutionLib = extra: caisson.mkLib ( { sources = mockSources; + systems = [ "x86_64-linux" ]; libOverlays = _lib: { terranix = mkLibOverlay (inputs.parent.outPath + "/lib-overlays/terranix"); }; @@ -1735,9 +1738,8 @@ in let myLib = mkResolutionLib { defaultEcosystemSrc.terranix = terranixStub "declared"; }; in - (myLib.caisson.terranix.mkConfiguration { + (myLib.caisson.terranix.mkTopConfiguration { configModule = { }; - pkgSets.pkgs = { }; }).stubbed; expected = "declared"; }; @@ -1747,10 +1749,9 @@ in let myLib = mkResolutionLib { defaultEcosystemSrc.terranix = terranixStub "declared"; }; in - (myLib.caisson.terranix.mkConfiguration { + (myLib.caisson.terranix.mkTopConfiguration { ecosystemSrc = terranixStub "explicit"; configModule = { }; - pkgSets.pkgs = { }; }).stubbed; expected = "explicit"; }; @@ -1764,9 +1765,8 @@ in }; }; in - (myLib.caisson.terranix.mkConfiguration { + (myLib.caisson.terranix.mkTopConfiguration { configModule = { }; - pkgSets.pkgs = { }; }).stubbed; expected = "input"; }; @@ -1781,9 +1781,8 @@ in defaultEcosystemSrc.terranix = terranixStub "declared"; }; in - (myLib.caisson.terranix.mkConfiguration { + (myLib.caisson.terranix.mkTopConfiguration { configModule = { }; - pkgSets.pkgs = { }; }).stubbed; expected = "declared"; }; @@ -1791,9 +1790,8 @@ in "test: a full miss throws at the adapter" = { expr = builtins.tryEval - ((mkResolutionLib { }).caisson.terranix.mkConfiguration { + ((mkResolutionLib { }).caisson.terranix.mkTopConfiguration { configModule = { }; - pkgSets.pkgs = { }; }).stubbed; expected = { success = false; @@ -5292,11 +5290,12 @@ in # The entry points whose constructor returns a configuration take # `configModule` as optional: each finds the configuration - # registered under the name of the configuration. The nixos and - # home-manager entry points take no `pkgSets`: a NixOS - # configuration and a home take their package sets from the - # composition. - "test: every entry point takes configModule, optional where it is found by name, and pkgSets except on nixos and home-manager" = + # registered under the name of the configuration. The entry points + # of the configurations evaluated at a system take no `pkgSets`: + # those configurations take their package sets from the + # composition. The structural, colmena, disko and flake-parts + # entry points take `pkgSets` and hand it to their modules. + "test: every entry point takes configModule, optional where it is found by name, and pkgSets where its configurations do not take sets from the composition" = { expr = builtins.listToAttrs ( builtins.map (name: { @@ -5316,6 +5315,8 @@ in "hjem" "home-manager" "home-manager-minimal" + "terranix" + "system-manager" ]; setsFromTheComposition = builtins.elem name [ "nixos" @@ -5324,6 +5325,8 @@ in "hjem" "home-manager" "home-manager-minimal" + "terranix" + "system-manager" ]; in signature ? configModule