├── .github ├── dependabot.yml └── workflows │ └── nix.yml ├── .gitignore ├── LICENSE ├── README.md ├── allSystems.nix ├── check-utils.nix ├── default.nix ├── examples ├── check-utils │ ├── flake.lock │ └── flake.nix ├── each-system │ └── flake.nix └── simple-flake │ ├── flake.nix │ ├── overlay.nix │ └── shell.nix ├── filterPackages.nix ├── flake-utils.svg ├── flake.lock ├── flake.nix ├── flattenTree.nix ├── lib.nix └── simpleFlake.nix /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | on: [ push ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: cachix/install-nix-action@v26 10 | - run: nix flake check 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix 2 | result 3 | result-* 4 | 5 | # Don't keep the example lockfile around 6 | /example/flake.lock 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zimbatm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # flake-utils 4 | 5 | 6 | 7 | **Pure Nix flake utility functions.** 8 | 9 | *A numtide project.* 10 | 11 |

12 | 13 | 14 |

15 | 16 |
17 | 18 | The goal of this project is to build a collection of pure Nix functions that don't 19 | depend on nixpkgs, and that are useful in the context of writing other Nix 20 | flakes. 21 | 22 | ## Usage 23 | 24 | ### `system :: { system = system, ... }` 25 | 26 | A map from system to system built from `allSystems`: 27 | ```nix 28 | system = { 29 | x86_64-linux = "x86_64-linux"; 30 | x86_64-darwin = "x86_64-darwin"; 31 | ... 32 | } 33 | ``` 34 | It's mainly useful to 35 | detect typos and auto-complete if you use [rnix-lsp](https://github.com/nix-community/rnix-lsp). 36 | 37 | Eg: instead of typing `"x86_64-linux"`, use `system.x86_64-linux`. 38 | 39 | 40 | ### `allSystems :: []` 41 | 42 | A list of all systems defined in nixpkgs. For a smaller list see `defaultSystems`. 43 | 44 | ### `defaultSystems :: []` 45 | 46 | The list of systems to use in `eachDefaultSystem` and `simpleFlake`. 47 | 48 | The default values are `["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]`. 49 | 50 | It's possible to override and control that list by changing the `systems` input of this flake. 51 | 52 | Eg (in your `flake.nix`): 53 | 54 | ```nix 55 | { 56 | # 1. Defined a "systems" inputs that maps to only ["x86_64-linux"] 57 | inputs.systems.url = "github:nix-systems/x86_64-linux"; 58 | 59 | inputs.flake-utils.url = "github:numtide/flake-utils"; 60 | # 2. Override the flake-utils default to your version 61 | inputs.flake-utils.inputs.systems.follows = "systems"; 62 | 63 | outputs = { self, flake-utils, ... }: 64 | # Now eachDefaultSystem is only using ["x86_64-linux"], but this list can also 65 | # further be changed by users of your flake. 66 | flake-utils.lib.eachDefaultSystem (system: { 67 | # ... 68 | }); 69 | } 70 | ``` 71 | 72 | For more details in this pattern, see: . 73 | 74 | ### `eachSystem :: [] -> ( -> attrs)` 75 | 76 | A common case is to build the same structure for each system. Instead of 77 | building the hierarchy manually or per prefix, iterate over each systems and 78 | then re-build the hierarchy. 79 | 80 | Eg: 81 | 82 | ```nix 83 | eachSystem [ system.x86_64-linux ] (system: { hello = 42; }) 84 | # => { hello = { x86_64-linux = 42; }; } 85 | eachSystem allSystems (system: { hello = 42; }) 86 | # => { 87 | hello.aarch64-darwin = 42, 88 | hello.aarch64-genode = 42, 89 | hello.aarch64-linux = 42, 90 | ... 91 | hello.x86_64-redox = 42, 92 | hello.x86_64-solaris = 42, 93 | hello.x86_64-windows = 42 94 | } 95 | ``` 96 | 97 | ### `eachSystemPassThrough :: [] -> ( -> attrs)` 98 | 99 | Unlike `eachSystem`, this function does not inject the `${system}` key by merely 100 | providing the system argument to the function. 101 | 102 | ### `eachDefaultSystem :: ( -> attrs)` 103 | 104 | `eachSystem` pre-populated with `defaultSystems`. 105 | 106 | #### Example 107 | 108 | [$ examples/each-system/flake.nix](examples/each-system/flake.nix) as nix 109 | ```nix 110 | { 111 | description = "Flake utils demo"; 112 | 113 | inputs.flake-utils.url = "github:numtide/flake-utils"; 114 | 115 | outputs = { self, nixpkgs, flake-utils }: 116 | flake-utils.lib.eachDefaultSystem (system: 117 | let pkgs = nixpkgs.legacyPackages.${system}; in 118 | { 119 | packages = rec { 120 | hello = pkgs.hello; 121 | default = hello; 122 | }; 123 | apps = rec { 124 | hello = flake-utils.lib.mkApp { drv = self.packages.${system}.hello; }; 125 | default = hello; 126 | }; 127 | } 128 | ); 129 | } 130 | ``` 131 | 132 | ### `eachDefaultSystemPassThrough :: ( -> attrs)` 133 | 134 | `eachSystemPassThrough` pre-populated with `defaultSystems`. 135 | 136 | #### Example 137 | 138 | ```nix 139 | inputs.flake-utils.lib.eachDefaultSystem (system: { 140 | checks./*.*/"" = /* ... */; 141 | devShells./*.*/"" = /* ... */; 142 | packages./*.*/"" = /* ... */; 143 | }) 144 | // inputs.flake-utils.lib.eachDefaultSystemPassThrough (system: { 145 | homeConfigurations."" = /* ... */; 146 | nixosConfigurations."" = /* ... */; 147 | }) 148 | ``` 149 | 150 | ### `meld :: attrs -> [ path ] -> attrs` 151 | 152 | Meld merges subflakes using common inputs. Useful when you want to 153 | split up a large flake with many different components into more 154 | manageable parts. 155 | 156 | ### `mkApp { drv, name ? drv.pname or drv.name, exePath ? drv.passthru.exePath or "/bin/${name}"` 157 | 158 | A small utility that builds the structure expected by the special `apps` and `defaultApp` prefixes. 159 | 160 | 161 | ### `flattenTree :: attrs -> attrs` 162 | 163 | Nix flakes insists on having a flat attribute set of derivations in 164 | various places like the `packages` and `checks` attributes. 165 | 166 | This function traverses a tree of attributes (by respecting 167 | [recurseIntoAttrs](https://noogle.dev/f/lib/recurseIntoAttrs)) and only returns their derivations, with a flattened 168 | key-space. 169 | 170 | Eg: 171 | ```nix 172 | flattenTree { hello = pkgs.hello; gitAndTools = pkgs.gitAndTools } 173 | ``` 174 | Returns: 175 | 176 | ```nix 177 | { 178 | hello = «derivation»; 179 | "gitAndTools/git" = «derivation»; 180 | "gitAndTools/hub" = «derivation»; 181 | # ... 182 | } 183 | ``` 184 | 185 | ### `simpleFlake :: attrs -> attrs` 186 | 187 | This function should be useful for most common use-cases where you have a 188 | simple flake that builds a package. It takes nixpkgs and a bunch of other 189 | parameters and outputs a value that is compatible as a flake output. 190 | 191 | Input: 192 | ```nix 193 | { 194 | # pass an instance of self 195 | self 196 | , # pass an instance of the nixpkgs flake 197 | nixpkgs 198 | , # we assume that the name maps to the project name, and also that the 199 | # overlay has an attribute with the `name` prefix that contains all of the 200 | # project's packages. 201 | name 202 | , # nixpkgs config 203 | config ? { } 204 | , # pass either a function or a file 205 | overlay ? null 206 | , # use this to load other flakes overlays to supplement nixpkgs 207 | preOverlays ? [ ] 208 | , # maps to the devShell output. Pass in a shell.nix file or function. 209 | shell ? null 210 | , # pass the list of supported systems 211 | systems ? [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ] 212 | }: null 213 | ``` 214 | 215 | #### Example 216 | 217 | Here is how it looks like in practice: 218 | 219 | [$ examples/simple-flake/flake.nix](examples/simple-flake/flake.nix) as nix 220 | ```nix 221 | { 222 | description = "Flake utils demo"; 223 | 224 | inputs.flake-utils.url = "github:numtide/flake-utils"; 225 | 226 | outputs = { self, nixpkgs, flake-utils }: 227 | flake-utils.lib.simpleFlake { 228 | inherit self nixpkgs; 229 | name = "simple-flake"; 230 | overlay = ./overlay.nix; 231 | shell = ./shell.nix; 232 | }; 233 | } 234 | ``` 235 | 236 | ## Commercial support 237 | 238 | Looking for help or customization? 239 | 240 | Get in touch with Numtide to get a quote. We make it easy for companies to 241 | work with Open Source projects: 242 | -------------------------------------------------------------------------------- /allSystems.nix: -------------------------------------------------------------------------------- 1 | [ 2 | "aarch64-darwin" 3 | "aarch64-genode" 4 | "aarch64-linux" 5 | "aarch64-netbsd" 6 | "aarch64-none" 7 | "aarch64_be-none" 8 | "arm-none" 9 | "armv5tel-linux" 10 | "armv6l-linux" 11 | "armv6l-netbsd" 12 | "armv6l-none" 13 | "armv7a-darwin" 14 | "armv7a-linux" 15 | "armv7a-netbsd" 16 | "armv7l-linux" 17 | "armv7l-netbsd" 18 | "avr-none" 19 | "i686-cygwin" 20 | "i686-darwin" 21 | "i686-freebsd13" 22 | "i686-genode" 23 | "i686-linux" 24 | "i686-netbsd" 25 | "i686-none" 26 | "i686-openbsd" 27 | "i686-windows" 28 | "javascript-ghcjs" 29 | "m68k-linux" 30 | "m68k-netbsd" 31 | "m68k-none" 32 | "microblaze-linux" 33 | "microblaze-none" 34 | "microblazeel-linux" 35 | "microblazeel-none" 36 | "mips64el-linux" 37 | "mipsel-linux" 38 | "mipsel-netbsd" 39 | "mmix-mmixware" 40 | "msp430-none" 41 | "or1k-none" 42 | "powerpc-netbsd" 43 | "powerpc-none" 44 | "powerpc64-linux" 45 | "powerpc64le-linux" 46 | "powerpcle-none" 47 | "riscv32-linux" 48 | "riscv32-netbsd" 49 | "riscv32-none" 50 | "riscv64-linux" 51 | "riscv64-netbsd" 52 | "riscv64-none" 53 | "rx-none" 54 | "s390-linux" 55 | "s390-none" 56 | "s390x-linux" 57 | "s390x-none" 58 | "vc4-none" 59 | "wasm32-wasi" 60 | "wasm64-wasi" 61 | "x86_64-cygwin" 62 | "x86_64-darwin" 63 | "x86_64-freebsd13" 64 | "x86_64-genode" 65 | "x86_64-linux" 66 | "x86_64-netbsd" 67 | "x86_64-none" 68 | "x86_64-openbsd" 69 | "x86_64-redox" 70 | "x86_64-solaris" 71 | "x86_64-windows" 72 | ] 73 | -------------------------------------------------------------------------------- /check-utils.nix: -------------------------------------------------------------------------------- 1 | systemOrPkgs: 2 | let 3 | inherit (builtins) foldl' unsafeDiscardStringContext elemAt match split concatStringsSep isList substring stringLength length attrNames; 4 | system = systemOrPkgs.system or systemOrPkgs; 5 | pipe = val: functions: foldl' (x: f: f x) val functions; 6 | max = x: y: if x > y then x else y; 7 | 8 | # Minimized copy-paste https://github.com/NixOS/nixpkgs/blob/master/lib/strings.nix#L746-L762 9 | sanitizeDerivationName = string: pipe (toString string) [ 10 | # Get rid of string context. This is safe under the assumption that the 11 | # resulting string is only used as a derivation name 12 | unsafeDiscardStringContext 13 | # Strip all leading "." 14 | (x: elemAt (match "\\.*(.*)" x) 0) 15 | # Split out all invalid characters 16 | # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 17 | # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 18 | (split "[^[:alnum:]+._?=-]+") 19 | # Replace invalid character ranges with a "-" 20 | (map (s: if isList s then "-" else s)) 21 | (concatStringsSep "") 22 | # Limit to 211 characters (minus 4 chars for ".drv") 23 | (x: substring (max (stringLength x - 207) 0) (-1) x) 24 | # If the result is empty, replace it with "?EMPTY?" 25 | (x: if stringLength x == 0 then "?EMPTY?" else x) 26 | ]; 27 | 28 | # Minimized version of 'sanitizeDerivationName' function 29 | str = it: if it == null then "null" else (sanitizeDerivationName it); 30 | 31 | test = name: command: derivation { 32 | inherit system; 33 | name = str name; 34 | builder = "/bin/sh"; 35 | args = [ "-c" command ]; 36 | }; 37 | 38 | success = test "SUCCESS" "echo success > $out"; 39 | in 40 | { 41 | 42 | isEqual = a: b: 43 | if a == b 44 | then success 45 | else test "FAILURE__${str a}__NOT_EQUAL__${str b}" "exit 1"; 46 | 47 | hasKey = attrset: key: 48 | if attrset ? ${str key} 49 | then success 50 | else test "FAILURE__${str key}__DOES_NOT_EXISTS_IN_ATTRSET_SIZE_${str(length (attrNames attrset))}" "exit 1"; 51 | } 52 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Used for back-compat 2 | import ./lib.nix { } 3 | -------------------------------------------------------------------------------- /examples/check-utils/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 0, 9 | "narHash": "sha256-omjHh3LT883xERMxVEXH/oeAFI2pAAy30mhZb0eN5G4=", 10 | "path": "../..", 11 | "type": "path" 12 | }, 13 | "original": { 14 | "path": "../..", 15 | "type": "path" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1685498995, 21 | "narHash": "sha256-rdyjnkq87tJp+T2Bm1OD/9NXKSsh/vLlPeqCc/mm7qs=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "9cfaa8a1a00830d17487cb60a19bb86f96f09b27", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "id": "nixpkgs", 29 | "type": "indirect" 30 | } 31 | }, 32 | "root": { 33 | "inputs": { 34 | "flake-utils": "flake-utils", 35 | "nixpkgs": "nixpkgs" 36 | } 37 | }, 38 | "systems": { 39 | "locked": { 40 | "lastModified": 1681028828, 41 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 42 | "owner": "nix-systems", 43 | "repo": "default", 44 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "nix-systems", 49 | "repo": "default", 50 | "type": "github" 51 | } 52 | } 53 | }, 54 | "root": "root", 55 | "version": 7 56 | } 57 | -------------------------------------------------------------------------------- /examples/check-utils/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Flake utils demo"; 3 | 4 | inputs.flake-utils.url = "path:../.."; 5 | 6 | outputs = { self, nixpkgs, flake-utils }: 7 | flake-utils.lib.eachDefaultSystem (system: 8 | let 9 | inherit (flake-utils.lib.check-utils system) isEqual hasKey; 10 | testDataset = { key1 = "value1"; key2 = 123; key3 = "some>value with^invalid&characters"; }; 11 | mkHydraJobs = system: { 12 | toplevel = derivation { name = "toplevel"; builder = "mybuilder"; inherit system; }; 13 | nested = { 14 | attribute = derivation { name = "nested-attribute"; builder = "mybuilder"; inherit system; }; 15 | }; 16 | }; 17 | in 18 | rec { 19 | hydraJobs = mkHydraJobs system; 20 | checks = { 21 | # Successful cases 22 | success_isEqual = isEqual testDataset.key1 "value1"; 23 | success_hasKey = hasKey testDataset "key2"; 24 | # ensure no special handling of hydraJobs 25 | success_hydraJobs = isEqual self.hydraJobs (flake-utils.lib.eachDefaultSystemMap mkHydraJobs); 26 | 27 | # Failing cases 28 | failure_isEqual = isEqual testDataset.key1 "failing-data"; 29 | failure_hasKey = hasKey testDataset "failing-data"; 30 | 31 | # Formatting 32 | formatting_number = isEqual testDataset.key2 123; 33 | formatting_null = isEqual null null; 34 | formatting_invalid_chars = isEqual testDataset.key3 "some>value with^invalid&characters"; 35 | 36 | }; 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /examples/each-system/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Flake utils demo"; 3 | 4 | inputs.flake-utils.url = "github:numtide/flake-utils"; 5 | 6 | outputs = { self, nixpkgs, flake-utils }: 7 | flake-utils.lib.eachDefaultSystem (system: 8 | let pkgs = nixpkgs.legacyPackages.${system}; in 9 | { 10 | packages = rec { 11 | hello = pkgs.hello; 12 | default = hello; 13 | }; 14 | apps = rec { 15 | hello = flake-utils.lib.mkApp { drv = self.packages.${system}.hello; }; 16 | default = hello; 17 | }; 18 | } 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/simple-flake/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Flake utils demo"; 3 | 4 | inputs.flake-utils.url = "github:numtide/flake-utils"; 5 | 6 | outputs = { self, nixpkgs, flake-utils }: 7 | flake-utils.lib.simpleFlake { 8 | inherit self nixpkgs; 9 | name = "simple-flake"; 10 | overlay = ./overlay.nix; 11 | shell = ./shell.nix; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /examples/simple-flake/overlay.nix: -------------------------------------------------------------------------------- 1 | final: prev: 2 | { 3 | # this key should be the same as the simpleFlake name attribute. 4 | simple-flake = { 5 | # assuming that hello is a project-specific package; 6 | hello = prev.hello; 7 | 8 | # demonstrating recursive packages 9 | terraform-providers = prev.terraform-providers; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /examples/simple-flake/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import }: 2 | pkgs.mkShell { 3 | buildInputs = [ pkgs.jq ]; 4 | } 5 | -------------------------------------------------------------------------------- /filterPackages.nix: -------------------------------------------------------------------------------- 1 | { allSystems }: 2 | system: packages: 3 | let 4 | # Adopted from nixpkgs.lib 5 | inherit (builtins) listToAttrs concatMap attrNames; 6 | nameValuePair = name: value: { inherit name value; }; 7 | filterAttrs = pred: set: 8 | listToAttrs ( 9 | concatMap 10 | (name: 11 | let v = set.${name}; in 12 | if pred name v then [ (nameValuePair name v) ] else [ ] 13 | ) 14 | (attrNames set) 15 | ); 16 | 17 | # Everything that nix flake check requires for the packages output 18 | sieve = n: v: 19 | with v; 20 | let 21 | inherit (builtins) isAttrs; 22 | isDerivation = x: isAttrs x && x ? type && x.type == "derivation"; 23 | isBroken = meta.broken or false; 24 | platforms = meta.platforms or allSystems; 25 | badPlatforms = meta.badPlatforms or [ ]; 26 | in 27 | # check for isDerivation, so this is independently useful of 28 | # flattenTree, which also does filter on derivations 29 | isDerivation v && !isBroken && (builtins.elem system platforms) && 30 | !(builtins.elem system badPlatforms) 31 | ; 32 | in 33 | filterAttrs sieve packages 34 | -------------------------------------------------------------------------------- /flake-utils.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 229 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "root": { 4 | "inputs": { 5 | "systems": "systems" 6 | } 7 | }, 8 | "systems": { 9 | "locked": { 10 | "lastModified": 1681028828, 11 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 12 | "owner": "nix-systems", 13 | "repo": "default", 14 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "nix-systems", 19 | "repo": "default", 20 | "type": "github" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Pure Nix flake utility functions"; 3 | 4 | # Externally extensible flake systems. See . 5 | inputs.systems.url = "github:nix-systems/default"; 6 | 7 | outputs = { self, systems }: { 8 | lib = import ./lib.nix { 9 | defaultSystems = import systems; 10 | }; 11 | templates = { 12 | default = self.templates.each-system; 13 | simple-flake = { 14 | path = ./examples/simple-flake; 15 | description = "A flake using flake-utils.lib.simpleFlake"; 16 | }; 17 | each-system = { 18 | path = ./examples/each-system; 19 | description = "A flake using flake-utils.lib.eachDefaultSystem"; 20 | }; 21 | check-utils = { 22 | path = ./examples/check-utils; 23 | description = "A flake with tests"; 24 | }; 25 | }; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /flattenTree.nix: -------------------------------------------------------------------------------- 1 | tree: 2 | let 3 | op = sum: path: val: 4 | let 5 | pathStr = builtins.concatStringsSep "/" path; 6 | in 7 | if (builtins.typeOf val) != "set" then 8 | # ignore that value 9 | # builtins.trace "${pathStr} is not of type set" 10 | sum 11 | else if val ? type && val.type == "derivation" then 12 | # builtins.trace "${pathStr} is a derivation" 13 | # we used to use the derivation outPath as the key, but that crashes Nix 14 | # so fallback on constructing a static key 15 | (sum // { 16 | "${pathStr}" = val; 17 | }) 18 | else if val ? recurseForDerivations && val.recurseForDerivations == true then 19 | # builtins.trace "${pathStr} is a recursive" 20 | # recurse into that attribute set 21 | (recurse sum path val) 22 | else 23 | # ignore that value 24 | # builtins.trace "${pathStr} is something else" 25 | sum 26 | ; 27 | 28 | recurse = sum: path: val: 29 | builtins.foldl' 30 | (sum: key: op sum (path ++ [ key ]) val.${key}) 31 | sum 32 | (builtins.attrNames val) 33 | ; 34 | in 35 | recurse { } [ ] tree 36 | -------------------------------------------------------------------------------- /lib.nix: -------------------------------------------------------------------------------- 1 | { 2 | # The list of systems supported by nixpkgs and hydra 3 | defaultSystems ? [ 4 | "aarch64-linux" 5 | "aarch64-darwin" 6 | "x86_64-darwin" 7 | "x86_64-linux" 8 | ] 9 | }: 10 | let 11 | inherit defaultSystems; 12 | 13 | # List of all systems defined in nixpkgs 14 | # Keep in sync with nixpkgs wit the following command: 15 | # $ nix-instantiate --json --eval --expr "with import {}; lib.platforms.all" | jq 'sort' | sed 's!,!!' > allSystems.nix 16 | allSystems = import ./allSystems.nix; 17 | 18 | # A map from system to system. It's useful to detect typos. 19 | # 20 | # Instead of typing `"x86_64-linux"`, type `flake-utils.lib.system.x86_64-linux` 21 | # and get an error back if you used a dash instead of an underscore. 22 | system = 23 | builtins.listToAttrs 24 | (map (system: { name = system; value = system; }) allSystems); 25 | 26 | # eachSystem using defaultSystems 27 | eachDefaultSystem = eachSystem defaultSystems; 28 | 29 | # eachSystemPassThrough using defaultSystems 30 | eachDefaultSystemPassThrough = eachSystemPassThrough defaultSystems; 31 | 32 | # Builds a map from =value to .=value for each system. 33 | eachSystem = eachSystemOp ( 34 | # Merge outputs for each system. 35 | f: attrs: system: 36 | let 37 | ret = f system; 38 | in 39 | builtins.foldl' ( 40 | attrs: key: 41 | attrs 42 | // { 43 | ${key} = (attrs.${key} or { }) // { 44 | ${system} = ret.${key}; 45 | }; 46 | } 47 | ) attrs (builtins.attrNames ret) 48 | ); 49 | 50 | # Applies a merge operation accross systems. 51 | eachSystemOp = 52 | op: systems: f: 53 | builtins.foldl' (op f) { } ( 54 | if 55 | !builtins ? currentSystem || builtins.elem builtins.currentSystem systems 56 | then 57 | systems 58 | else 59 | # Add the current system if the --impure flag is used. 60 | systems ++ [ builtins.currentSystem ] 61 | ); 62 | 63 | # Merely provides the system argument to the function. 64 | # 65 | # Unlike eachSystem, this function does not inject the `${system}` key. 66 | eachSystemPassThrough = eachSystemOp ( 67 | f: attrs: system: 68 | attrs // (f system) 69 | ); 70 | 71 | # eachSystemMap using defaultSystems 72 | eachDefaultSystemMap = eachSystemMap defaultSystems; 73 | 74 | # Builds a map from =value to . = value. 75 | eachSystemMap = systems: f: builtins.listToAttrs (builtins.map (system: { name = system; value = f system; }) systems); 76 | 77 | # Nix flakes insists on having a flat attribute set of derivations in 78 | # various places like the `packages` and `checks` attributes. 79 | # 80 | # This function traverses a tree of attributes (by respecting 81 | # recurseIntoAttrs) and only returns their derivations, with a flattened 82 | # key-space. 83 | # 84 | # Eg: 85 | # 86 | # flattenTree { hello = pkgs.hello; gitAndTools = pkgs.gitAndTools }; 87 | # 88 | # Returns: 89 | # 90 | # { 91 | # hello = «derivation»; 92 | # "gitAndTools/git" = «derivation»; 93 | # "gitAndTools/hub" = «derivation»; 94 | # # ... 95 | # } 96 | flattenTree = tree: import ./flattenTree.nix tree; 97 | 98 | # Nix check functionality validates packages for various conditions, like if 99 | # they build for any given platform or if they are marked broken. 100 | # 101 | # This function filters a flattend package set for conditinos that 102 | # would *trivially* break `nix flake check`. It does not flatten a tree and it 103 | # does not implement advanced package validation checks. 104 | # 105 | # Eg: 106 | # 107 | # filterPackages "x86_64-linux" { 108 | # hello = pkgs.hello; 109 | # "gitAndTools/git" = pkgs.gitAndTools // {meta.broken = true;}; 110 | # }; 111 | # 112 | # Returns: 113 | # 114 | # { 115 | # hello = «derivation»; 116 | # } 117 | filterPackages = import ./filterPackages.nix { inherit allSystems; }; 118 | 119 | # Meld merges subflakes using common inputs. Useful when you want 120 | # to split up a large flake with many different components into more 121 | # manageable parts. 122 | # 123 | # For example: 124 | # 125 | # { 126 | # inputs = { 127 | # flutils.url = "github:numtide/flake-utils"; 128 | # nixpkgs.url = "github:nixos/nixpkgs"; 129 | # }; 130 | # outputs = inputs@{ flutils, ... }: flutils.lib.meld inputs [ 131 | # ./nix/packages 132 | # ./nix/hardware 133 | # ./nix/overlays 134 | # # ... 135 | # ]; 136 | # } 137 | # 138 | # Where ./nix/packages/default.nix looks like just the output 139 | # portion of a flake. 140 | # 141 | # { flutils, nixpkgs, ... }: flutils.lib.eachDefaultSystem (system: 142 | # let pkgs = import nixpkgs { inherit system; }; in 143 | # { 144 | # packages = { 145 | # foo = ...; 146 | # bar = ...; 147 | # # ... 148 | # }; 149 | # } 150 | # ) 151 | # 152 | # You can also use meld within the subflakes to further subdivide 153 | # your flake into a tree like structure. For example, 154 | # ./nix/hardware/default.nix might look like: 155 | # 156 | # inputs@{ flutils, ... }: flutils.lib.meld inputs [ 157 | # ./foobox.nix 158 | # ./barbox.nix 159 | # ] 160 | meld = let 161 | # Pulled from nixpkgs.lib 162 | recursiveUpdateUntil = 163 | # Predicate, taking the path to the current attribute as a list of strings for attribute names, and the two values at that path from the original arguments. 164 | pred: 165 | # Left attribute set of the merge. 166 | lhs: 167 | # Right attribute set of the merge. 168 | rhs: 169 | let 170 | f = attrPath: 171 | builtins.zipAttrsWith (n: values: 172 | let here = attrPath ++ [ n ]; 173 | in if builtins.length values == 1 174 | || pred here (builtins.elemAt values 1) (builtins.head values) then 175 | builtins.head values 176 | else 177 | f here values); 178 | in f [ ] [ rhs lhs ]; 179 | 180 | # Pulled from nixpkgs.lib 181 | recursiveUpdate = 182 | # Left attribute set of the merge. 183 | lhs: 184 | # Right attribute set of the merge. 185 | rhs: 186 | recursiveUpdateUntil (path: lhs: rhs: !(builtins.isAttrs lhs && builtins.isAttrs rhs)) lhs 187 | rhs; 188 | in inputs: 189 | builtins.foldl' (output: subflake: 190 | recursiveUpdate output (import subflake inputs)) { }; 191 | 192 | # Returns the structure used by `nix app` 193 | mkApp = 194 | { drv 195 | , name ? drv.pname or drv.name 196 | , exePath ? drv.passthru.exePath or "/bin/${name}" 197 | }: 198 | { 199 | type = "app"; 200 | program = "${drv}${exePath}"; 201 | }; 202 | 203 | # This function tries to capture a common flake pattern. 204 | simpleFlake = import ./simpleFlake.nix { inherit lib defaultSystems; }; 205 | 206 | # Helper functions for Nix evaluation 207 | check-utils = import ./check-utils.nix; 208 | 209 | lib = { 210 | inherit 211 | allSystems 212 | check-utils 213 | defaultSystems 214 | eachDefaultSystem 215 | eachDefaultSystemMap 216 | eachDefaultSystemPassThrough 217 | eachSystem 218 | eachSystemMap 219 | eachSystemPassThrough 220 | filterPackages 221 | flattenTree 222 | meld 223 | mkApp 224 | simpleFlake 225 | system 226 | ; 227 | }; 228 | in 229 | lib 230 | -------------------------------------------------------------------------------- /simpleFlake.nix: -------------------------------------------------------------------------------- 1 | { lib 2 | , defaultSystems ? [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ] 3 | }: 4 | # This function returns a flake outputs-compatible schema. 5 | { 6 | # pass an instance of self 7 | self 8 | , # pass an instance of the nixpkgs flake 9 | nixpkgs 10 | , # we assume that the name maps to the project name, and also that the 11 | # overlay has an attribute with the `name` prefix that contains all of the 12 | # project's packages. 13 | name 14 | , # nixpkgs config 15 | config ? { } 16 | , # pass either a function or a file 17 | overlay ? null 18 | , # use this to load other flakes overlays to supplement nixpkgs 19 | preOverlays ? [ ] 20 | , # maps to the devShell output. Pass in a shell.nix file or function. 21 | shell ? null 22 | , # pass the list of supported systems 23 | systems ? defaultSystems 24 | }: 25 | let 26 | loadOverlay = obj: 27 | if obj == null then 28 | [ ] 29 | else 30 | [ (maybeImport obj) ] 31 | ; 32 | 33 | maybeImport = obj: 34 | if (builtins.typeOf obj == "path") || (builtins.typeOf obj == "string") then 35 | import obj 36 | else 37 | obj 38 | ; 39 | 40 | overlays = preOverlays ++ (loadOverlay overlay); 41 | 42 | shell_ = maybeImport shell; 43 | 44 | outputs = lib.eachSystem systems (system: 45 | let 46 | pkgs = import nixpkgs { 47 | inherit 48 | config 49 | overlays 50 | system 51 | ; 52 | }; 53 | 54 | packages = pkgs.${name} or { }; 55 | in 56 | { 57 | # Use the legacy packages since it's more forgiving. 58 | legacyPackages = packages; 59 | } 60 | // 61 | ( 62 | if packages ? defaultPackage then { 63 | defaultPackage = packages.defaultPackage; 64 | } else { } 65 | ) 66 | // 67 | ( 68 | if packages ? checks then { 69 | checks = packages.checks; 70 | } else { } 71 | ) 72 | // 73 | ( 74 | if shell != null then { 75 | devShell = shell_ { inherit pkgs; }; 76 | } else if packages ? devShell then { 77 | devShell = packages.devShell; 78 | } else { } 79 | ) 80 | ); 81 | in 82 | outputs 83 | --------------------------------------------------------------------------------