fake.modules transposition for aspect-oriented Dendritic Nix. with cross-aspect dependencies. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic nix aspect oriented
at doc 279 lines 10 kB view raw view rendered
1<!-- Badges --> 2 3<p align="right"> 4 <a href="https://github.com/sponsors/vic"><img src="https://img.shields.io/badge/sponsor-vic-white?logo=githubsponsors&logoColor=white&labelColor=%23FF0000" alt="Sponsor Vic"/> 5 </a> 6 <a href="https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries"> <img src="https://img.shields.io/badge/Dendritic-Nix-informational?logo=nixos&logoColor=white" alt="Dendritic Nix"/> </a> 7 <a href="https://github.com/vic/flake-aspects/actions"> 8 <img src="https://github.com/vic/flake-aspects/actions/workflows/test.yml/badge.svg" alt="CI Status"/> </a> 9 <a href="LICENSE"> <img src="https://img.shields.io/github/license/vic/flake-aspects" alt="License"/> </a> 10</p> 11 12# `<aspect>.<class>` Transposition for Dendritic Nix 13 14> `flake-aspects` and [vic](https://bsky.app/profile/oeiuwq.bsky.social)'s [dendritic libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries) made for you with Love++ and AI--. If you like my work, consider [sponsoring](https://github.com/sponsors/vic) 15 16In [aspect-oriented](https://vic.github.io/dendrix/Dendritic.html) [Dendritic](https://github.com/mightyiam/dendritic) setups, it is common to expose modules using the structure `flake.modules.<class>.<aspect>`. 17 18However, for many users, a transposed attribute set, `<aspect>.<class>`, can be more intuitive. It often feels more natural to nest classes within aspects rather than the other way around. 19 20This project provides a small, dependency-free [`transpose`](nix/default.nix) primitive that is powerful enough to implement [cross-aspect dependencies](nix/aspects.nix) for any Nix configuration class. It also includes a [flake-parts module](nix/flakeModule.nix) that transforms `flake.aspects` into `flake.modules`. 21 22<table> 23<tr> 24<td> 25<b><code>flake.aspects</code></b> 26 27```nix 28{ 29 vim-btw = { 30 nixos = ...; 31 darwin = ...; 32 homeManager = ...; 33 nixvim = ...; 34 }; 35 tiling-desktop = { 36 nixos = ...; 37 darwin = ...; 38 }; 39 macos-develop = { 40 darwin = ...; 41 hjem = ...; 42 }; 43} 44``` 45 46</td> 47<td> 48<img width="400" height="400" alt="image" src="https://github.com/user-attachments/assets/dd28ce8d-f727-4e31-a192-d3002ee8984e" /> 49</td> 50<td> 51<code>flake.modules</code> 52 53```nix 54{ 55 nixos = { 56 vim-btw = ...; 57 tiling-desktop = ...; 58 }; 59 darwin = { 60 vim-btw = ...; 61 tiling-desktop = ...; 62 macos-develop = ...; 63 }; 64 homeManager = { 65 vim-btw = ...; 66 }; 67 hjem = { 68 macos-develop = ...; 69 }; 70 nixvim = { 71 vim-btw = ...; 72 }; 73} 74``` 75 76</td> 77</tr> 78</table> 79 80Unlike `flake.modules.<class>.<aspect>` which is _flat_, aspects can be nested forming a _tree_ by using the `provides` (short alias: `_`) attribute. Each aspect can also specify a list of `includes` of other aspects, forming a _graph_ of dependencies. 81 82```nix 83{ 84 flake.aspects = { 85 gaming = { 86 nixos = {}; 87 darwin = {}; 88 89 _.emulation = { aspect, ... }: { 90 nixos = {}; 91 92 _.nes.nixos = {}; 93 _.gba.nixos = {}; 94 95 includes = with aspect._; [ nes gba ]; 96 }; 97 }; 98 }; 99} 100``` 101 102## Usage 103 104The library can be used in two ways: as a flakes-independent dependency-free utility or as a `flake-parts` module. 105 106### As a Dependency-Free Library (`./nix/default.nix`) 107 108The core of this project is the [`transpose`](nix/default.nix) function, which is powerful enough to implement cross-aspect dependencies for any Nix configuration class. It accepts an optional `emit` function that can be used to ignore items, modify them, or generate multiple items from a single input. 109 110```nix 111let transpose = import ./nix/default.nix { lib = pkgs.lib; }; in 112transpose { a.b.c = 1; } # => { b.a.c = 1; } 113``` 114 115This `emit` function is utilized by the [`aspects`](nix/aspects.nix) library to manage module dependencies between different aspects of the same class. Both `transpose` and `aspects` are independent of flakes. 116 117#### Use aspects without flakes. 118 119It is possible to use the aspects system as a library, [without flakes](https://github.com/vic/flake-aspects/blob/b94d806/checkmate.nix#L76). This can be used, for example, to avoid poluting flake-parts' `flake.modules` or by libraries that want to create own isolated aspects scope. For examples of this, see our own [flake-parts integration](nix/flakeModule.nix), and how [`den`](https://github.com/vic/den) creates its own [`den.aspects` scope](https://github.com/vic/den/blob/main/nix/scope.nix) independent of `flakes.aspects`/`flake.modules`. 120 121### As a Dendritic Flake-Parts Module (`flake.aspects` option) 122 123When used as a `flake-parts` module, the `flake.aspects` option is automatically transposed into `flake.modules`, making the modules available to consumers of your flake. 124 125```nix 126# The code in this example can (and should) be split into different Dendritic modules. 127{ inputs, ... }: { 128 imports = [ inputs.flake-aspects.flakeModule ]; 129 flake.aspects = { 130 131 sliding-desktop = { 132 description = "Next-generation tiling windowing"; 133 nixos = { }; # Configure Niri on Linux 134 darwin = { }; # Configure Paneru on macOS 135 }; 136 137 awesome-cli = { 138 description = "Enhances the environment with the best of CLI and TUI"; 139 nixos = { }; # OS services 140 darwin = { }; # Apps like ghostty, iTerm2 141 homeManager = { }; # Fish aliases, TUIs, etc. 142 nixvim = { }; # Plugins 143 }; 144 145 work-network = { 146 description = "Work VPN and SSH access"; 147 nixos = {}; # Enable OpenSSH 148 darwin = {}; # Enable macOS SSH server 149 terranix = {}; # Provision VPN 150 hjem = {}; # Home: link .ssh keys and configs 151 }; 152 153 }; 154} 155``` 156 157#### Declaring Cross-Aspect Dependencies 158 159Aspects can declare dependencies on other aspects using the `includes` attribute. This allows you to compose configurations in a modular way. 160 161Dependencies are defined at the aspect level, not within individual modules. When a module from an aspect is evaluated (e.g., `flake.modules.nixos.development-server`), the library resolves all dependencies for the `nixos` class and imports the corresponding modules if they exist. 162 163In the example below, the `development-server` aspect includes the `alice` and `bob` aspects. This demonstrates how to create a consistent development environment across different operating systems and user configurations. 164 165```nix 166{ 167 flake.aspects = { aspects, ... }: { 168 development-server = { 169 # This aspect now includes modules from 'alice' and 'bob'. 170 includes = with aspects; [ alice bob ]; 171 172 # Without flake-aspects, you would have to do this manually for each class. 173 # nixos.imports = [ inputs.self.modules.nixos.alice ]; 174 # darwin.imports = [ inputs.self.modules.darwin.bob ]; 175 }; 176 177 alice = { 178 nixos = {}; 179 homeManager = {}; 180 }; 181 182 bob = { 183 darwin = {}; 184 hjem = {}; 185 }; 186 }; 187} 188``` 189 190Creating the final OS configurations is outside the scope of this library—for that, see [`vic/den`](https://github.com/vic/den). However, exposing them would look like this: 191 192```nix 193{ inputs, ... }: 194{ 195 flake.nixosConfigurations.fooHost = inputs.nixpkgs.lib.nixosSystem { 196 system = "x86_64-linux"; 197 modules = [ inputs.self.modules.nixos.development-server ]; 198 }; 199 200 flake.darwinConfigurations.fooHost = inputs.darwin.lib.darwinSystem { 201 system = "aarch64-darwin"; 202 modules = [ inputs.self.modules.darwin.development-server ]; 203 }; 204} 205``` 206 207### Advanced Aspect Dependencies: Providers 208 209Dependencies are managed through a powerful abstraction called **providers**. A provider is a value that returns an aspect object, which can then supply modules to the aspect that includes it. 210 211A provider can be either a static aspect object or a function that dynamically returns one. This mechanism enables sophisticated dependency chains, conditional logic, and parameterization. 212 213#### Default Provider (`__functor`) 214 215Each aspect is itself a provider via its hidden option `__functor` (see `nix/types.nix`). You can include aspects directly. 216 217```nix 218# A 'foo' aspect that depends on 'bar' and 'baz' aspects. 219flake.aspects = { aspects, ... }: { 220 foo.includes = with aspects; [ bar baz ]; 221} 222``` 223 224#### Custom Providers 225 226You can define custom providers to implement more complex logic. A provider function receives the current `class` (e.g., `"nixos"`) and the `aspect-chain` (the list of aspects that led to the call). This allows a provider to act as a conditional proxy or router for dependencies. 227 228In this example, the `kde-desktop` aspect defines a custom `karousel` provider that only returns a module if certain conditions are met: 229 230```nix 231flake.aspects.kde-desktop._.karousel = { aspect-chain, class }: 232 if someCondition aspect-chain && class == "nixos" then { nixos = { ... }; } else { }; 233``` 234 235The `karousel` provider can then be included in another aspect: 236 237```nix 238flake.aspects = { aspects, ... }: { 239 home-server.includes = [ aspects.kde-desktop._.karousel ]; 240} 241``` 242 243This pattern allows an included aspect to determine which configuration its caller should use, enabling a tree of dependencies where each node can be either static or parametric. 244 245#### Parameterized Providers 246 247Providers can be implemented as curried functions, allowing you to create parameterized modules. This is useful for creating reusable configurations that can be customized at the inclusion site. 248 249For real-world examples, see how `vic/den` defines [auto-imports](https://github.com/vic/den/blob/main/modules/aspects/batteries/import-tree.nix) and [home-managed](https://github.com/vic/den/blob/main/modules/aspects/batteries/home-managed.nix) parametric aspects. 250 251```nix 252flake.aspects = { aspects, ... }: { 253 system = { 254 nixos.system.stateVersion = "25.11"; 255 _.user = userName: { 256 darwin.system.primaryUser = userName; 257 nixos.users.${userName}.isNormalUser = true; 258 }; 259 }; 260 261 home-server.includes = [ 262 aspects.system 263 (aspects.system._.user "bob") 264 ]; 265} 266``` 267 268See the `aspects."test provides"` and `aspects."test provides using fixpoints"` sections in the [checkmate tests](checkmate.nix) for more examples of chained providers. 269 270#### The `_` Alias for `provides` 271 272For convenience, `_` is an alias for `provides`. This allows for more concise chaining of providers. For example, `foo.provides.bar.provides.baz` can be written as `foo._.bar._.baz`. 273 274## Testing 275 276```shell 277nix run github:vic/checkmate#fmt --override-input target . 278nix flake check github:vic/checkmate --override-input target . -L 279```