├── .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 |
--------------------------------------------------------------------------------