├── .envrc ├── .gitignore ├── README.md ├── default.nix ├── lib.nix ├── nix ├── nixdoc-to-github.nix ├── nixdoc.nix ├── npins.nix └── sources │ ├── default.nix │ └── sources.json ├── shell.nix └── test.nix /.envrc: -------------------------------------------------------------------------------- 1 | use_nix 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `lazy-drv` 2 | Build executables from Nix derivations on demand. 3 | 4 | ## Motivation 5 | 6 | Nix does not allow on-demand [realisation](https://nix.dev/manual/nix/2.19/glossary#gloss-realise) of store paths. 7 | But sometimes it would be nice to have a large closure only realised when it's actually accessed, for example when a rarely-used helper command is run. 8 | 9 | This tool is inspired by [TVL's `lazy-deps`](https://cs.tvl.fyi/depot@0c0edd5928d48c9673dd185cd332f921e64135e7/-/blob/nix/lazy-deps/default.nix). 10 | 11 | It trades saving initial build time against adding a startup time overhead. 12 | And it meshes well with [`attr-cmd`](https://github.com/fricklerhandwerk/attr-cmd), a library for producing command line programs from attribute sets. 13 | 14 | ## Installation 15 | 16 | ```shell-session 17 | nix-shell -p npins 18 | npins init 19 | npins add github fricklerhandwerk lazy-drv -b main 20 | ``` 21 | 22 | ```nix 23 | ## default.nix 24 | let 25 | sources = import ./npins; 26 | in 27 | { 28 | pkgs ? import sources.nixpkgs { inherit system; config = { }; overlays = [ ]; }, 29 | lazy-drv ? import sources.lazy-drv { inherit pkgs system; }, 30 | system ? builtins.currentSystem, 31 | }: 32 | let 33 | lib = pkgs.lib // lazy-drv.lib; 34 | in 35 | pkgs.callPackage ./example.nix { inherit lib; } 36 | ``` 37 | ## Future work 38 | 39 | Obviously this is just a cheap trick that can't do more than run selected commands from derivations. 40 | 41 | More fancy things, such as lazily exposing `man` pages or other auxiliary data from a package, would probably require integration into a configuration management framework like NixOS, since every tool in question would have to play along. 42 | 43 | This could indeed be quite powerful: 44 | Imagine wiring up `man` to accept an additional option `--nixpkgs`. 45 | It would then first inspect `$MANPATH`, and on failure leverage [`nix-index`](https://github.com/nix-community/nix-index) to realise the appropriate derivation on the fly. 46 | 47 | One current limitation is that the Nix expression underlying the lazy derivation still needs to evaluated. 48 | This can become costly for large expressions. 49 | Another layer of indirection, which also defers evaluation, could be added to avoid that. 50 | 51 | ## `lib.lazy-drv.lazy-build` 52 | 53 | Replace derivations in an attribute set with calls to `nix-build` on these derivations. 54 | 55 | Input attributes are the union of the second argument to [`lazify`](#liblazy-drvlazify) and [`nix-build`](#liblazy-drvnix-build). 56 | 57 | > **Example** 58 | > 59 | > ### Make derivations in an attribute set build lazily 60 | > 61 | > ```nix 62 | > ### example.nix 63 | > { pkgs, lib }: 64 | > let 65 | > example = pkgs.writeText "example-output" "Built on demand!"; 66 | > 67 | > lazy = lib.lazy-drv.lazy-build { 68 | > source = "${with lib.fileset; toSource { 69 | > root = ./.; 70 | > fileset = unions [ ./example.nix ]; 71 | > }}"; 72 | > attrs = { inherit example; }; 73 | > }; 74 | > in 75 | > { 76 | > inherit example; 77 | > shell = pkgs.mkShellNoCC { 78 | > packages = with lib; collect isDerivation lazy; 79 | > }; 80 | > } 81 | > ``` 82 | > 83 | > ```console 84 | > $ nix-shell -A shell -I lazy-drv=https://github.com/fricklerhandwerk/lazy-drv/tarball/master 85 | > [nix-shell:~]$ example-output 86 | > this derivation will be built: 87 | > /nix/store/...-example-output.drv 88 | > building '/nix/store/...-example-output.drv'... 89 | > [nix-shell:~]$ cat result 90 | > Built on demand! 91 | > ``` 92 | 93 | ## `lib.lazy-drv.lazy-run` 94 | 95 | Replace derivations in an attribute set with calls to the executable specified in each derivation's `meta.mainProgram`. 96 | 97 | Input attributes are the union of the second argument to [`lazify`](#liblazy-drvlazify) (except `predicate`, since one can only run the executable from a single derivation) and [`nix-build`](#liblazy-drvnix-build) (where `nix-build-args` defaults to `[ "--no-out-link" ]`, since one usually doesn't want the `result` symlink). 98 | 99 | > **Example** 100 | > 101 | > ### Build a derivation on demand and run its main executable 102 | > 103 | > ```nix 104 | > ### example.nix 105 | > { pkgs, lib }: 106 | > let 107 | > example = pkgs.writeShellScriptBin "example-command" "echo I am lazy"; 108 | > 109 | > lazy = lib.lazy-drv.lazy-run { 110 | > source = "${with lib.fileset; toSource { 111 | > root = ./.; 112 | > fileset = unions [ ./example.nix ]; 113 | > }}"; 114 | > attrs = { inherit example; }; 115 | > }; 116 | > in 117 | > { 118 | > inherit example; 119 | > shell = pkgs.mkShellNoCC { 120 | > packages = with lib; collect isDerivation lazy; 121 | > }; 122 | > } 123 | > ``` 124 | > 125 | > ```console 126 | > $ nix-shell -A shell -I lazy-drv=https://github.com/fricklerhandwerk/lazy-drv/tarball/master 127 | > [nix-shell:~]$ example-command 128 | > this derivation will be built: 129 | > /nix/store/...-example-command.drv 130 | > building '/nix/store/...-example-command.drv'... 131 | > I am lazy 132 | > ``` 133 | 134 | ## `lib.lazy-drv.nix-build` 135 | 136 | Make a command line that calls `nix-build` on an `attrpath` in a `source` file. 137 | 138 | ### Arguments 139 | 140 | - `source` (path or string) 141 | 142 | Path to the Nix file declaring the derivation to realise. 143 | If the expression in the file is a function, it must take an attribute set where all values have defaults. 144 | 145 | If the path points to a directory, the complete directory this file lives in will be copied into the store. 146 | The Nix expression must not access files outside of this directory. 147 | 148 | If the path points to a single file, the Nix experession in the file must not refer to other files. 149 | 150 | The most robust and efficient approach is using a file from a [file set](https://nixos.org/manual/nixpkgs/stable/#sec-functions-library-fileset). 151 | 152 | - `attrpath` (list of strings) 153 | 154 | The [attribute path](https://nix.dev/manual/nix/2.19/language/operators#attribute-selection) to the derivation in the expression at `source` to wrap, denoted as a list of path components. 155 | 156 | - `nix` (derivation or string, optional) 157 | 158 | Path to a Nix package that has the executable `nix-build`. 159 | If not set, the command name `nix-build` is used. 160 | 161 | - `nix-build-args` (list of strings, optional) 162 | 163 | Default: `[ ]` 164 | 165 | [Command-line arguments](https://nix.dev/manual/nix/2.19/command-ref/nix-build#options) to pass to `nix-build` for the on-demand realisation. 166 | 167 | - `nix-build-env` (attribute set, optional) 168 | 169 | Default: `{ }` 170 | 171 | [Environment variables](https://nix.dev/manual/nix/2.19/command-ref/nix-build#common-environment-variables) to set on the invocation of `nix-build` for the on-demand realisation. 172 | 173 | > **Example** 174 | > 175 | > ### Generate a command line 176 | > 177 | > ```nix 178 | > ### example.nix 179 | > { pkgs, lib }: 180 | > lib.lazy-drv.nix-build { 181 | > source = with lib.fileset; toSource { 182 | > root = ./.; 183 | > fileset = unions [ ./default.nix ./npins ]; 184 | > }}; 185 | > attrpath = [ "foo" "bar" ]; 186 | > nix-build-env = { NIX_PATH=""; }; 187 | > nix-build-args = [ "--no-out-link" ]; 188 | > } 189 | > ``` 190 | > 191 | > ```console 192 | > $ nix-instantiate --eval 193 | > "NIX_PATH= nix-build /nix/store/...-source -A foo.bar --no-out-link" 194 | > ``` 195 | 196 | ## `lib.lazy-drv.lazify` 197 | 198 | Apply a function to each leaf attribute in a nested attribute set, which must come from a Nix file that is accessible to `nix-build`. 199 | 200 | ### Arguments 201 | 202 | 1. `lazifier` (`[String] -> a -> b`) 203 | 204 | This function is given the attribute path in the nested attribute set being processed, and the value it's supposed to operate on. 205 | It is ensured that the attribute path exists in the given `source` file, and that this `source` file can be processed by `nix-build`. 206 | 207 | The function can return anything. 208 | In practice it would usually produce a derivation with a shell script that runs `nix-build` on the attribute path in the source file, and processes the build result. 209 | 210 | 2. An attribute set: 211 | 212 | - `source` (path or string) 213 | 214 | Path to the Nix file declaring the attribute set `attrs`. 215 | The Nix file must be accessible to `nix-build`: 216 | - The path must exist in the file system 217 | - If it leads to a directory instead of a Nix file, that directory must contain `default.nix` 218 | - If the Nix file contains a function, all its arguments must have default values 219 | 220 | - `attrs` (attribute set) 221 | 222 | Nested attribute set of derivations. 223 | It must correspond to a top-level attribute set in the expression at `source`. 224 | 225 | - `predicate` (`a -> bool`, optional) 226 | 227 | A function to determine what's a leaf attribute. 228 | Since the intended use case is to create a `nix-build` command line, one meaningful alternative to the default value is [`isAttrsetOfDerivations`](#liblazy-drvisBuildable). 229 | 230 | Default: [`lib.isDerivation`](https://nixos.org/manual/nixpkgs/stable/#lib.attrsets.isDerivation) 231 | 232 | ## `lib.lazy-drv.isBuildable` 233 | 234 | Check if the given value is a derivation or an attribute set of derivations. 235 | 236 | This emulates what `nix-build` expects as the contents of `default.nix`. 237 | 238 | ## `lib.lazy-drv.mapAttrsRecursiveCond'` 239 | 240 | Apply function `f` to all nested attributes in attribute set `attrs` which satisfy predicate `pred`. 241 | 242 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | sources ? import ./nix/sources, 3 | system ? builtins.currentSystem, 4 | pkgs ? import sources.nixpkgs { inherit system; config = { }; overlays = [ ]; }, 5 | nixdoc-to-github ? pkgs.callPackage sources.nixdoc-to-github { }, 6 | git-hooks ? pkgs.callPackage sources.git-hooks { }, 7 | }: 8 | let 9 | lib = { 10 | inherit (git-hooks.lib) git-hooks; 11 | inherit (nixdoc-to-github.lib) nixdoc-to-github; 12 | }; 13 | update-readme = lib.nixdoc-to-github.run { 14 | category = "lazy-drv"; 15 | description = "\\`lazy-drv\\`"; 16 | file = "${toString ./lib.nix}"; 17 | output = "${toString ./README.md}"; 18 | }; 19 | # wrapper to account for the custom lockfile location 20 | npins = pkgs.callPackage ./nix/npins.nix { }; 21 | in 22 | { 23 | lib.lazy-drv = pkgs.callPackage ./lib.nix { }; 24 | 25 | shell = pkgs.mkShellNoCC { 26 | packages = [ 27 | npins 28 | ]; 29 | shellHook = '' 30 | ${with lib.git-hooks; pre-commit (wrap.abort-on-change update-readme)} 31 | ''; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /lib.nix: -------------------------------------------------------------------------------- 1 | /** 2 | Build executables from Nix derivations on demand. 3 | 4 | # Motivation 5 | 6 | Nix does not allow on-demand [realisation](https://nix.dev/manual/nix/2.19/glossary#gloss-realise) of store paths. 7 | But sometimes it would be nice to have a large closure only realised when it's actually accessed, for example when a rarely-used helper command is run. 8 | 9 | This tool is inspired by [TVL's `lazy-deps`](https://cs.tvl.fyi/depot@0c0edd5928d48c9673dd185cd332f921e64135e7/-/blob/nix/lazy-deps/default.nix). 10 | 11 | It trades saving initial build time against adding a startup time overhead. 12 | And it meshes well with [`attr-cmd`](https://github.com/fricklerhandwerk/attr-cmd), a library for producing command line programs from attribute sets. 13 | 14 | # Installation 15 | 16 | ```shell-session 17 | nix-shell -p npins 18 | npins init 19 | npins add github fricklerhandwerk lazy-drv -b main 20 | ``` 21 | 22 | ```nix 23 | # default.nix 24 | let 25 | sources = import ./npins; 26 | in 27 | { 28 | pkgs ? import sources.nixpkgs { inherit system; config = { }; overlays = [ ]; }, 29 | lazy-drv ? import sources.lazy-drv { inherit pkgs system; }, 30 | system ? builtins.currentSystem, 31 | }: 32 | let 33 | lib = pkgs.lib // lazy-drv.lib; 34 | in 35 | pkgs.callPackage ./example.nix { inherit lib; } 36 | ``` 37 | # Future work 38 | 39 | Obviously this is just a cheap trick that can't do more than run selected commands from derivations. 40 | 41 | More fancy things, such as lazily exposing `man` pages or other auxiliary data from a package, would probably require integration into a configuration management framework like NixOS, since every tool in question would have to play along. 42 | 43 | This could indeed be quite powerful: 44 | Imagine wiring up `man` to accept an additional option `--nixpkgs`. 45 | It would then first inspect `$MANPATH`, and on failure leverage [`nix-index`](https://github.com/nix-community/nix-index) to realise the appropriate derivation on the fly. 46 | 47 | One current limitation is that the Nix expression underlying the lazy derivation still needs to evaluated. 48 | This can become costly for large expressions. 49 | Another layer of indirection, which also defers evaluation, could be added to avoid that. 50 | */ 51 | { lib, symlinkJoin, writeShellApplication }: 52 | rec { 53 | /** 54 | Replace derivations in an attribute set with calls to `nix-build` on these derivations. 55 | 56 | Input attributes are the union of the second argument to [`lazify`](#function-library-lib.lazy-drv.lazify) and [`nix-build`](#function-library-lib.lazy-drv.nix-build). 57 | 58 | :::{.example} 59 | 60 | # Make derivations in an attribute set build lazily 61 | 62 | ```nix 63 | # example.nix 64 | { pkgs, lib }: 65 | let 66 | example = pkgs.writeText "example-output" "Built on demand!"; 67 | 68 | lazy = lib.lazy-drv.lazy-build { 69 | source = "${with lib.fileset; toSource { 70 | root = ./.; 71 | fileset = unions [ ./example.nix ]; 72 | }}"; 73 | attrs = { inherit example; }; 74 | }; 75 | in 76 | { 77 | inherit example; 78 | shell = pkgs.mkShellNoCC { 79 | packages = with lib; collect isDerivation lazy; 80 | }; 81 | } 82 | ``` 83 | 84 | ```console 85 | $ nix-shell -A shell -I lazy-drv=https://github.com/fricklerhandwerk/lazy-drv/tarball/master 86 | [nix-shell:~]$ example-output 87 | this derivation will be built: 88 | /nix/store/...-example-output.drv 89 | building '/nix/store/...-example-output.drv'... 90 | [nix-shell:~]$ cat result 91 | Built on demand! 92 | ``` 93 | ::: 94 | */ 95 | lazy-build = 96 | { source 97 | , attrs 98 | , predicate ? lib.isDerivation 99 | , nix ? null 100 | , nix-build-args ? [ ] 101 | , nix-build-env ? { } 102 | }: 103 | let 104 | build = attrpath: drv: 105 | let 106 | result = nix-build { inherit source attrpath nix nix-build-args nix-build-env; }; 107 | in 108 | writeShellApplication { inherit (drv) name; text = ''exec ${result} "$@"''; }; 109 | in 110 | lazify build { inherit source attrs predicate; }; 111 | 112 | /** 113 | Replace derivations in an attribute set with calls to the executable specified in each derivation's `meta.mainProgram`. 114 | 115 | Input attributes are the union of the second argument to [`lazify`](#function-library-lib.lazy-drv.lazify) (except `predicate`, since one can only run the executable from a single derivation) and [`nix-build`](#function-library-lib.lazy-drv.nix-build) (where `nix-build-args` defaults to `[ "--no-out-link" ]`, since one usually doesn't want the `result` symlink). 116 | 117 | :::{.example} 118 | 119 | # Build a derivation on demand and run its main executable 120 | 121 | ```nix 122 | # example.nix 123 | { pkgs, lib }: 124 | let 125 | example = pkgs.writeShellScriptBin "example-command" "echo I am lazy"; 126 | 127 | lazy = lib.lazy-drv.lazy-run { 128 | source = "${with lib.fileset; toSource { 129 | root = ./.; 130 | fileset = unions [ ./example.nix ]; 131 | }}"; 132 | attrs = { inherit example; }; 133 | }; 134 | in 135 | { 136 | inherit example; 137 | shell = pkgs.mkShellNoCC { 138 | packages = with lib; collect isDerivation lazy; 139 | }; 140 | } 141 | ``` 142 | 143 | ```console 144 | $ nix-shell -A shell -I lazy-drv=https://github.com/fricklerhandwerk/lazy-drv/tarball/master 145 | [nix-shell:~]$ example-command 146 | this derivation will be built: 147 | /nix/store/...-example-command.drv 148 | building '/nix/store/...-example-command.drv'... 149 | I am lazy 150 | ``` 151 | ::: 152 | */ 153 | lazy-run = 154 | { source 155 | , attrs 156 | , nix ? null 157 | , nix-build-args ? [ "--no-out-link" ] 158 | , nix-build-env ? {} 159 | }: 160 | let 161 | run = attrpath: drv: 162 | let 163 | result = nix-build { inherit source attrpath nix nix-build-args nix-build-env; }; 164 | in 165 | writeShellApplication { 166 | name = drv.meta.mainProgram; 167 | text = ''exec "$(${result})"/bin/${drv.meta.mainProgram} "$@"''; 168 | }; 169 | in 170 | lazify run { inherit source attrs; }; 171 | 172 | /** 173 | Make a command line that calls `nix-build` on an `attrpath` in a `source` file. 174 | 175 | # Arguments 176 | 177 | - `source` (path or string) 178 | 179 | Path to the Nix file declaring the derivation to realise. 180 | If the expression in the file is a function, it must take an attribute set where all values have defaults. 181 | 182 | If the path points to a directory, the complete directory this file lives in will be copied into the store. 183 | The Nix expression must not access files outside of this directory. 184 | 185 | If the path points to a single file, the Nix experession in the file must not refer to other files. 186 | 187 | The most robust and efficient approach is using a file from a [file set](https://nixos.org/manual/nixpkgs/stable/#sec-functions-library-fileset). 188 | 189 | - `attrpath` (list of strings) 190 | 191 | The [attribute path](https://nix.dev/manual/nix/2.19/language/operators#attribute-selection) to the derivation in the expression at `source` to wrap, denoted as a list of path components. 192 | 193 | - `nix` (derivation or string, optional) 194 | 195 | Path to a Nix package that has the executable `nix-build`. 196 | If not set, the command name `nix-build` is used. 197 | 198 | - `nix-build-args` (list of strings, optional) 199 | 200 | Default: `[ ]` 201 | 202 | [Command-line arguments](https://nix.dev/manual/nix/2.19/command-ref/nix-build#options) to pass to `nix-build` for the on-demand realisation. 203 | 204 | - `nix-build-env` (attribute set, optional) 205 | 206 | Default: `{ }` 207 | 208 | [Environment variables](https://nix.dev/manual/nix/2.19/command-ref/nix-build#common-environment-variables) to set on the invocation of `nix-build` for the on-demand realisation. 209 | 210 | :::{.example} 211 | 212 | # Generate a command line 213 | 214 | ```nix 215 | # example.nix 216 | { pkgs, lib }: 217 | lib.lazy-drv.nix-build { 218 | source = with lib.fileset; toSource { 219 | root = ./.; 220 | fileset = unions [ ./default.nix ./npins ]; 221 | }}; 222 | attrpath = [ "foo" "bar" ]; 223 | nix-build-env = { NIX_PATH=""; }; 224 | nix-build-args = [ "--no-out-link" ]; 225 | } 226 | ``` 227 | 228 | ```console 229 | $ nix-instantiate --eval 230 | "NIX_PATH= nix-build /nix/store/...-source -A foo.bar --no-out-link" 231 | ``` 232 | ::: 233 | */ 234 | nix-build = 235 | { source 236 | , attrpath 237 | , nix ? null 238 | , nix-build-args ? [ ] 239 | , nix-build-env ? { } 240 | }: 241 | with lib; 242 | join " " (filter (x: x != "") [ 243 | (join " " (mapAttrsToList (k: v: "${k}=${toString v}") nix-build-env)) 244 | "${optionalString (!isNull nix) "${nix}/bin/"}nix-build ${source} -A ${join "." attrpath}" 245 | (join " " nix-build-args) 246 | ]); 247 | 248 | /** 249 | Apply a function to each leaf attribute in a nested attribute set, which must come from a Nix file that is accessible to `nix-build`. 250 | 251 | # Arguments 252 | 253 | 1. `lazifier` (`[String] -> a -> b`) 254 | 255 | This function is given the attribute path in the nested attribute set being processed, and the value it's supposed to operate on. 256 | It is ensured that the attribute path exists in the given `source` file, and that this `source` file can be processed by `nix-build`. 257 | 258 | The function can return anything. 259 | In practice it would usually produce a derivation with a shell script that runs `nix-build` on the attribute path in the source file, and processes the build result. 260 | 261 | 2. An attribute set: 262 | 263 | - `source` (path or string) 264 | 265 | Path to the Nix file declaring the attribute set `attrs`. 266 | The Nix file must be accessible to `nix-build`: 267 | - The path must exist in the file system 268 | - If it leads to a directory instead of a Nix file, that directory must contain `default.nix` 269 | - If the Nix file contains a function, all its arguments must have default values 270 | 271 | - `attrs` (attribute set) 272 | 273 | Nested attribute set of derivations. 274 | It must correspond to a top-level attribute set in the expression at `source`. 275 | 276 | - `predicate` (`a -> bool`, optional) 277 | 278 | A function to determine what's a leaf attribute. 279 | Since the intended use case is to create a `nix-build` command line, one meaningful alternative to the default value is [`isAttrsetOfDerivations`](#function-library-lib.lazy-drv.isBuildable). 280 | 281 | Default: [`lib.isDerivation`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.isDerivation) 282 | */ 283 | lazify = lazifier: 284 | { source 285 | , attrs 286 | , predicate ? lib.isDerivation 287 | }: 288 | let 289 | lazificator = attrpath: value: 290 | assert attrpath-exists "lazify" source attrpath; 291 | lazifier attrpath value; 292 | in 293 | assert file-is-autocallable "lazify" source; 294 | mapAttrsRecursiveCond' predicate lazificator attrs; 295 | 296 | /** 297 | Check if the given value is a derivation or an attribute set of derivations. 298 | 299 | This emulates what `nix-build` expects as the contents of `default.nix`. 300 | */ 301 | isBuildable = value: 302 | with lib; isAttrs value && (value.type or null == "derivation" || all isDerivation (attrValues value)); 303 | 304 | /** 305 | Apply function `f` to all nested attributes in attribute set `attrs` which satisfy predicate `pred`. 306 | */ 307 | mapAttrsRecursiveCond' = pred: f: attrs: 308 | with lib; 309 | mapAttrsRecursiveCond 310 | (value: !pred value) 311 | (attrpath: value: if pred value then f attrpath value else value) 312 | attrs; 313 | 314 | file-is-autocallable = context: path: 315 | with lib; 316 | if !pathExists path then 317 | throw "${context}: source file '${toString path}' does not exist" 318 | else if pathIsDirectory path && !pathExists "${path}/default.nix" then 319 | throw "${context}: source file '${toString path}/default.nix' does not exist" 320 | else if isFunction (import path) then 321 | all id 322 | (mapAttrsToList 323 | (name: value: 324 | if value then value else 325 | throw "${context}: function argument '${name}' in '${toString path}' must have a default value" 326 | ) 327 | (__functionArgs (import path))) 328 | else true; 329 | 330 | attrpath-exists = context: path: attrpath: 331 | let 332 | imported = import path; 333 | source = if lib.isFunction imported then imported { } else imported; 334 | in 335 | if lib.hasAttrByPath attrpath source then true else 336 | throw "${context}: attribute path '${join "." attrpath}' does not exist in '${toString path}'"; 337 | 338 | join = lib.concatStringsSep; 339 | 340 | # these are from earlier attempts that involved parsing string or list representations of attribute paths. 341 | # it's not a great idea to do that in my opinion, but if you really need to do it, you may find this helpful: 342 | matchAttrName = string: lib.strings.match "^([a-zA-Z_][a-zA-Z0-9_'-]*|\"[^\"]*\")$" string; 343 | isAttrPathString = string: 344 | with lib; 345 | join "." (concatMap matchAttrName (splitString "." string)) == string; 346 | isAttrPathList = list: 347 | with lib; all (x: x != null) (map matchAttrName list); 348 | } 349 | -------------------------------------------------------------------------------- /nix/nixdoc-to-github.nix: -------------------------------------------------------------------------------- 1 | { writeShellApplication, git, nixdoc, busybox, perl }: 2 | { category, description, file, output }: 3 | writeShellApplication { 4 | name = "nixdoc-to-github"; 5 | runtimeInputs = [ nixdoc busybox perl ]; 6 | # nixdoc makes a few assumptions that are specific to the Nixpkgs manual. 7 | # Those need to be adapated to GitHub Markdown: 8 | # - Turn `:::{.example}` blocks into block quotes 9 | # - Remove section anchors 10 | # - GitHub produces its own anchors, change URL fragments accordingly 11 | text = '' 12 | nixdoc --category "${category}" --description "${description}" --file "${file}" | awk ' 13 | BEGIN { p=0; } 14 | /^\:\:\:\{\.example\}/ { print "> **Example**"; p=1; next; } 15 | /^\:\:\:/ { p=0; next; } 16 | p { print "> " $0; next; } 17 | { print } 18 | ' | sed 's/[[:space:]]*$//' \ 19 | | sed 's/ {#[^}]*}//g' \ 20 | | sed "s/\`\`\` /\`\`\`/g" \ 21 | | sed 's/function-library-//g' | perl -pe 's/\(#([^)]+)\)/"(#" . $1 =~ s|\.||gr . ")" /eg' \ 22 | > "${output}" 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /nix/nixdoc.nix: -------------------------------------------------------------------------------- 1 | { sources, lib, rustPlatform }: 2 | let 3 | src = sources.nixdoc; 4 | package = (lib.importTOML "${src}/Cargo.toml").package; 5 | in 6 | rustPlatform.buildRustPackage { 7 | pname = package.name; 8 | version = package.version; 9 | inherit src; 10 | cargoLock.lockFile = "${src}/Cargo.lock"; 11 | } 12 | -------------------------------------------------------------------------------- /nix/npins.nix: -------------------------------------------------------------------------------- 1 | { lib, runCommand, makeWrapper, npins }: 2 | runCommand "npins" { buildInputs = [ makeWrapper ]; } '' 3 | mkdir -p $out/bin 4 | cp ${npins}/bin/* $out/bin/ 5 | wrapProgram $out/bin/npins --add-flags "-d ${toString ./sources}" 6 | '' 7 | -------------------------------------------------------------------------------- /nix/sources/default.nix: -------------------------------------------------------------------------------- 1 | # Generated by npins. Do not modify; will be overwritten regularly 2 | let 3 | data = builtins.fromJSON (builtins.readFile ./sources.json); 4 | version = data.version; 5 | 6 | mkSource = spec: 7 | assert spec ? type; let 8 | path = 9 | if spec.type == "Git" then mkGitSource spec 10 | else if spec.type == "GitRelease" then mkGitSource spec 11 | else if spec.type == "PyPi" then mkPyPiSource spec 12 | else if spec.type == "Channel" then mkChannelSource spec 13 | else builtins.throw "Unknown source type ${spec.type}"; 14 | in 15 | spec // { outPath = path; }; 16 | 17 | mkGitSource = { repository, revision, url ? null, hash, ... }: 18 | assert repository ? type; 19 | # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository 20 | # In the latter case, there we will always be an url to the tarball 21 | if url != null then 22 | (builtins.fetchTarball { 23 | inherit url; 24 | sha256 = hash; # FIXME: check nix version & use SRI hashes 25 | }) 26 | else assert repository.type == "Git"; builtins.fetchGit { 27 | url = repository.url; 28 | rev = revision; 29 | # hash = hash; 30 | }; 31 | 32 | mkPyPiSource = { url, hash, ... }: 33 | builtins.fetchurl { 34 | inherit url; 35 | sha256 = hash; 36 | }; 37 | 38 | mkChannelSource = { url, hash, ... }: 39 | builtins.fetchTarball { 40 | inherit url; 41 | sha256 = hash; 42 | }; 43 | in 44 | if version == 3 then 45 | builtins.mapAttrs (_: mkSource) data.pins 46 | else 47 | throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" 48 | -------------------------------------------------------------------------------- /nix/sources/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "pins": { 3 | "git-hooks": { 4 | "type": "Git", 5 | "repository": { 6 | "type": "GitHub", 7 | "owner": "fricklerhandwerk", 8 | "repo": "git-hooks" 9 | }, 10 | "branch": "main", 11 | "revision": "aecdc1339e1322385e120aa1a257840839e91928", 12 | "url": "https://github.com/fricklerhandwerk/git-hooks/archive/aecdc1339e1322385e120aa1a257840839e91928.tar.gz", 13 | "hash": "07h6ig6gm24pj2naf9j5vap3j8snxszardjfg8jvbrpprxfk8cim" 14 | }, 15 | "nixdoc-to-github": { 16 | "type": "Git", 17 | "repository": { 18 | "type": "GitHub", 19 | "owner": "fricklerhandwerk", 20 | "repo": "nixdoc-to-github" 21 | }, 22 | "branch": "main", 23 | "revision": "2a5826e460df7deb8618084fa3df1d1882e352d3", 24 | "url": "https://github.com/fricklerhandwerk/nixdoc-to-github/archive/2a5826e460df7deb8618084fa3df1d1882e352d3.tar.gz", 25 | "hash": "18dnhcdx661fs2x9gf9dwvsvyw7j2gximgikw3325g8v8cjrkgs0" 26 | }, 27 | "nixpkgs": { 28 | "type": "Git", 29 | "repository": { 30 | "type": "GitHub", 31 | "owner": "nixos", 32 | "repo": "nixpkgs" 33 | }, 34 | "branch": "nixpkgs-unstable", 35 | "revision": "abd6d48f8c77bea7dc51beb2adfa6ed3950d2585", 36 | "url": "https://github.com/nixos/nixpkgs/archive/abd6d48f8c77bea7dc51beb2adfa6ed3950d2585.tar.gz", 37 | "hash": "0lisfh5b3dgcjb083nsxmffvzxzs6ir1jfsxfaf29gjpjnv7sm7f" 38 | } 39 | }, 40 | "version": 3 41 | } -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ./. { }).shell 2 | -------------------------------------------------------------------------------- /test.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./npins 2 | , system ? builtins.currentSystem 3 | }: 4 | let 5 | pkgs = import sources.nixpkgs { 6 | inherit system; 7 | config = { }; 8 | overlays = [ ]; 9 | }; 10 | inherit (pkgs.callPackage ./lib.nix { }) lazy-run; 11 | scripts = { 12 | foo = pkgs.writeShellScriptBin "foo-executable" "echo this is lazy $@"; 13 | bar = pkgs.writeShellScriptBin "bar-executable" "echo very lazy"; 14 | }; 15 | lazy = with pkgs.lib; lazy-run { 16 | source = "${with fileset; toSource { 17 | root = ./.; 18 | fileset = unions [ ./test.nix ./npins ./lib.nix ]; 19 | }}/test.nix"; 20 | attrs = { inherit scripts; }; 21 | }; 22 | in 23 | { 24 | inherit scripts; 25 | shell = with pkgs; mkShellNoCC { 26 | packages = (with lib; collect isDerivation lazy) ++ [ 27 | npins 28 | ]; 29 | }; 30 | } 31 | --------------------------------------------------------------------------------