From 403304578200457addf6b4a5da926f94a11b3bfc Mon Sep 17 00:00:00 2001 From: Noam Yorav-Raphael Date: Tue, 9 Apr 2024 02:37:11 +0300 Subject: [PATCH] Convert from docbook to mdbook (#233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Transform elements to include content * Remove 3 files which were not referenced The files were probably included in previous versions and weren't removed. * Automatic conversion using pandoc I just run `pandoc {fn} -f docbook -t markdown --wrap=none -s -o {fn.with_suffix(".md")}` over all .xml files in pills/, and on /book.xml I manually created SUMMARY.md based on the list in book.xml. * Remove front matter from 00-preface.md * Support "note" sections, fix inter-links * Fix code sections with highlighting, which pandoc missed * Replace `\'` with `'`. * Replace `\"` with `"`. * Replace `\...` with `...` * Format markdown with Prettier * Convert code section to fenced, with the appropriate syntax * Build the book with Nix * Add highlight.js which supports nix syntax highlighting * Add redirects from original paths to new paths * make prompts unselectable * README: Remove DocBook-specific instructions (cherry picked from commit 66936f15a573e36e7138fc93cecb8d2845296ace) * Re-add heading id It was removed in pandoc step for some reason. (cherry picked from commit cf39aa5ad95cf3d704ecd2aa4055e4c23e74fb6b) * Fix footnote rendering with mdbook-epub mdbook-epub 0.4.37 will collapse the second footnote into the first one. This does not happen with HTML output from plain mdbook. (cherry picked from commit bc9bd1384427a443dd12674c5ae2ae0c36ffc7ee) * Manually added backticks for tags in original * Put «» around what was originally * epub: Remove extra page breaks before headings The sections are pretty short, leading to unnecessary pagination being required. --------- Co-authored-by: Noam Yorav-Raphael Co-authored-by: Jan Tojnar --- .gitignore | 1 + README.md | 41 -- book.toml | 40 ++ book.xml | 64 --- custom.css | 28 + default.nix | 145 ++--- pills/00-preface.md | 15 + pills/01-why-you-should-give-it-a-try.md | 98 ++++ pills/01-why-you-should-give-it-try.xml | 284 ---------- pills/01/which-bash.txt | 2 - pills/02-install-on-your-running-system.md | 155 ++++++ pills/02-install-on-your-running.xml | 325 ------------ pills/02/ldd-bash.txt | 3 - pills/02/nixpkgs-expressions.txt | 4 - pills/02/profile.xml | 6 - pills/02/user-environment.xml | 4 - pills/03-enter-environment.md | 234 ++++++++ pills/03-enter-environment.xml | 406 -------------- pills/03/channel-list.txt | 2 - pills/03/generation-3.txt | 2 - pills/03/install-hello.txt | 5 - pills/03/install.txt | 1 - pills/03/list-generations.txt | 3 - pills/03/list-installed-derivations.txt | 3 - pills/03/ls-nix-profile.txt | 4 - pills/03/ls-profile-bin.txt | 8 - pills/03/nix-env-rollback.txt | 1 - pills/03/nix-store-tree.txt | 2 - pills/03/nix-store.txt | 2 - pills/03/references.txt | 3 - pills/03/referrers.txt | 6 - pills/03/rollback.txt | 2 - pills/03/source-nix.txt | 1 - pills/03/uninstall-all.txt | 4 - pills/04-basics-of-language.md | 272 ++++++++++ pills/04-basics-of-language.xml | 363 ------------- pills/04/basics.txt | 8 - pills/04/dash.txt | 4 - pills/04/division.txt | 5 - pills/04/double-quotes.txt | 4 - pills/04/escaping.txt | 4 - pills/04/if.txt | 4 - pills/04/interpolate.txt | 7 - pills/04/lazy.txt | 2 - pills/04/let-basic.txt | 2 - pills/04/let-multiple-assign.txt | 4 - pills/04/let-multiple.txt | 2 - pills/04/let-nested.txt | 2 - pills/04/let-reference.txt | 2 - pills/04/let-scope.txt | 2 - pills/04/lists.txt | 2 - pills/04/relative-path.txt | 2 - pills/04/set-access.txt | 4 - pills/04/set-basics.txt | 3 - pills/04/set-failed.txt | 2 - pills/04/set-recursive.txt | 2 - pills/04/strings-basic.txt | 4 - pills/04/with-basic.txt | 5 - pills/04/with-scope.txt | 4 - pills/05-functions-and-imports.md | 222 ++++++++ pills/05-functions-and-imports.xml | 339 ------------ pills/05/a-nix.txt | 1 - pills/05/anon-function.txt | 2 - pills/05/argument-set-error.txt | 5 - pills/05/b-nix.txt | 1 - pills/05/default-values.txt | 5 - pills/05/import.txt | 5 - pills/05/mul-nix.txt | 1 - pills/05/multi-argument-function.txt | 7 - pills/05/named-function.txt | 5 - pills/05/named-set-argument.txt | 3 - pills/05/no-parenthesis.txt | 9 - pills/05/partial-application.txt | 5 - pills/05/set-argument.txt | 6 - pills/05/test-import-2.txt | 3 - pills/05/test-import.txt | 2 - pills/05/test-nix-2.txt | 4 - pills/05/test-nix.txt | 1 - pills/05/variadic-arguments.txt | 2 - pills/06-our-first-derivation.md | 314 +++++++++++ pills/06-our-first-derivation.xml | 499 ------------------ pills/06/build-derivation.txt | 7 - pills/06/check-drvattrs.txt | 2 - pills/06/coreutils.txt | 6 - pills/06/current-system.txt | 2 - pills/06/drvattrs.txt | 2 - pills/06/examine-build.xml | 25 - pills/06/fake-system.txt | 3 - pills/06/fix-attribute.txt | 4 - pills/06/inspect-values.txt | 5 - pills/06/interpolate.txt | 4 - pills/06/list-coreutils.txt | 2 - pills/06/outpath.txt | 4 - pills/06/realise-derivation.txt | 1 - pills/06/reference.txt | 2 - pills/06/show-derivation.xml | 21 - pills/06/test-build.txt | 5 - pills/06/tostring.txt | 4 - pills/06/troll.png | Bin 70746 -> 0 bytes pills/06/troll.svg | 16 - pills/06/type-derivation.txt | 2 - pills/07-working-derivation.md | 243 +++++++++ pills/07-working-derivation.xml | 341 ------------ pills/07/bash.xml | 5 - pills/07/builder.sh.txt | 2 - pills/07/c-program-derivation.xml | 7 - pills/07/foo.drv.xml | 29 - pills/07/read-log.xml | 18 - pills/07/simple-derivation.xml | 6 - pills/07/simple.c.txt | 3 - pills/07/simple.txt | 12 - pills/07/simple_builder.sh.txt | 3 - pills/07/simple_inherit.txt | 11 - pills/08-generic-builders.md | 272 ++++++++++ pills/08-generic-builders.xml | 333 ------------ pills/08/autotools-nix.txt | 21 - pills/08/generic-builder.txt | 18 - pills/08/hello-builder.txt | 6 - pills/08/hello-nix-darwin.txt | 21 - pills/08/hello-nix-rev-1.txt | 21 - pills/08/hello-nix-rev-2.txt | 8 - pills/08/hello-nix.txt | 21 - pills/08/set-union.txt | 4 - pills/08/to-string-nixpkgs.txt | 6 - pills/08/to-string.txt | 4 - pills/09-automatic-runtime-dependencies.md | 138 +++++ pills/09-automatic-runtime.xml | 281 ---------- pills/09/build-hello-nix.txt | 5 - pills/09/find.txt | 1 - pills/09/instantiate-hello.txt | 8 - pills/09/instantiate.txt | 15 - pills/09/ldd-hello.txt | 4 - pills/09/strings.txt | 2 - pills/10-developing-with-nix-shell.md | 172 ++++++ pills/10-developing-with-nix-shell.xml | 234 -------- pills/10/autotools-nix.txt | 24 - pills/10/builder-sh.txt | 3 - pills/10/hello-nix.txt | 8 - pills/10/nix-shell-hello.txt | 5 - pills/10/nix-shell-source.txt | 3 - pills/10/setup-sh.txt | 39 -- pills/10/source-builder.txt | 2 - pills/11-garbage-collector.md | 135 +++++ pills/11-garbage-collector.xml | 295 ----------- pills/11/channel-update.txt | 4 - pills/11/install-bsd-games.txt | 8 - pills/11/ls-gcroots-auto.txt | 5 - pills/11/nix-collect-garbage.txt | 6 - pills/11/remove-bsd-games.txt | 6 - pills/11/remove-gen-9.txt | 9 - pills/12-inputs-design-pattern.md | 255 +++++++++ pills/12-inputs-design-pattern.xml | 361 ------------- pills/12/graphviz-derivation.txt | 8 - pills/12/graphviz-gd-derivation.txt | 13 - pills/12/graphviz-mkderivation.txt | 14 - pills/12/nix-env-install-graphviz.txt | 3 - pills/12/repository-mkderivation.txt | 25 - pills/12/repository-test-nix-build.txt | 4 - pills/12/repository-test-nix-repl.txt | 7 - pills/12/repository.txt | 4 - pills/12/setup-sh.txt | 8 - pills/12/simple-png.txt | 2 - pills/13-callpackage-design-pattern.md | 172 ++++++ pills/13-callpackage-design-pattern.xml | 267 ---------- pills/13/callpackage-function-call.txt | 4 - pills/13/callpackage-function-overrides.txt | 5 - pills/13/callpackage-function.txt | 5 - pills/13/callpackage-usage.txt | 17 - pills/13/get-args-function.txt | 3 - pills/13/intersect-attr-values.txt | 5 - pills/13/package-derivation.txt | 2 - pills/13/repository-derivation.txt | 4 - pills/14-override-design-pattern.md | 163 ++++++ pills/14-override-design-pattern.xml | 195 ------- pills/14/make-overridable-lib.txt | 8 - pills/14/mygraphviz.txt | 11 - pills/14/nix-repl-make-overridable-test.txt | 11 - pills/14/nix-repl-make-overridable-twice.txt | 9 - pills/14/rec-make-overridable.txt | 8 - pills/15-nix-search-paths.md | 138 +++++ pills/15-nix-search-paths.xml | 146 ----- pills/15/mypkgs-graphviz-multiple.txt | 4 - pills/15/mypkgs-install-attr-graphviz.txt | 3 - pills/15/mypkgs-path.txt | 4 - pills/15/mypkgs-query-all.txt | 4 - pills/15/nix-instantiate-ping.txt | 6 - pills/15/nixpkgs-path.txt | 4 - pills/15/ping-custom-path.txt | 4 - pills/16-nixpkgs-parameters.md | 108 ++++ pills/16-nixpkgs-parameters.xml | 126 ----- pills/16/config-foo-bar.txt | 7 - pills/16/myrelease-nix.txt | 4 - pills/16/pkgs-psmisc.txt | 3 - pills/17-nixpkgs-overriding-packages.md | 141 +++++ pills/17-nixpkgs-overriding-packages.xml | 141 ----- pills/17/build-asciidoc-graphviz-override.txt | 2 - pills/17/config-nix.txt | 8 - pills/17/fix-function.txt | 9 - pills/17/fix-pkgs-function.txt | 4 - pills/17/graphviz-override.txt | 4 - pills/17/newpkgs-override-set.txt | 5 - pills/17/p-graphviz-override.txt | 3 - pills/18-nix-store-paths.md | 184 +++++++ pills/18-nix-store-paths.xml | 143 ----- pills/18/bar-derivation.xml | 12 - pills/18/derivation-simple-content.xml | 23 - pills/18/derivation-simple.txt | 3 - .../mycontent-string-representation-hash.txt | 3 - pills/18/mycontent-string-representation.txt | 4 - pills/18/mycontent.txt | 5 - pills/18/myfile-final-hash.txt | 2 - pills/18/myfile-hash-alternate.txt | 2 - pills/18/myfile-hash.txt | 2 - pills/18/myfile-string-hash.txt | 3 - pills/18/myout-drv-hash.txt | 5 - pills/18/output-path-replace-empty.txt | 2 - pills/19-fundamentals-of-stdenv.md | 214 ++++++++ pills/19-fundamentals-of-stdenv.xml | 187 ------- pills/19/default-builder.txt | 2 - pills/19/hello-derivation.xml | 50 -- pills/19/stdenv-derivation.txt | 8 - pills/19/stdenv-hello-build.txt | 5 - pills/19/stdenv-hello.txt | 5 - pills/19/stdenv-mkderivation.txt | 11 - pills/19/stdenv-references.txt | 6 - pills/19/stdenv-setup-fake-builder.txt | 9 - pills/19/stdenv-setup-head.txt | 5 - pills/20-basic-dependencies-and-hooks.md | 279 ++++++++++ pills/20-basic-dependencies-and-hooks.xml | 180 ------- pills/20/build-inputs-0.bash | 4 - pills/20/build-inputs-1.bash | 14 - pills/20/build-inputs-2.bash | 3 - pills/20/build-inputs-3.bash | 9 - pills/20/build-inputs-4.bash | 1 - pills/20/env-hooks-0.bash | 10 - pills/20/env-hooks-1.bash | 7 - pills/20/propagated-build-inputs-0.bash | 12 - pills/20/propagated-build-inputs-1.bash | 11 - pills/20/propagated-build-inputs-2.bash | 4 - pills/20/setup-hooks-0.bash | 12 - pills/20/three-hellos.nix | 46 -- pills/20/two-hellos.nix | 35 -- pills/SUMMARY.md | 24 + release.nix | 6 +- style.css | 268 ---------- theme/highlight.js | 384 ++++++++++++++ 246 files changed, 4453 insertions(+), 7226 deletions(-) create mode 100644 book.toml delete mode 100644 book.xml create mode 100644 custom.css create mode 100644 pills/00-preface.md create mode 100644 pills/01-why-you-should-give-it-a-try.md delete mode 100644 pills/01-why-you-should-give-it-try.xml delete mode 100644 pills/01/which-bash.txt create mode 100644 pills/02-install-on-your-running-system.md delete mode 100644 pills/02-install-on-your-running.xml delete mode 100644 pills/02/ldd-bash.txt delete mode 100644 pills/02/nixpkgs-expressions.txt delete mode 100644 pills/02/profile.xml delete mode 100644 pills/02/user-environment.xml create mode 100644 pills/03-enter-environment.md delete mode 100644 pills/03-enter-environment.xml delete mode 100644 pills/03/channel-list.txt delete mode 100644 pills/03/generation-3.txt delete mode 100644 pills/03/install-hello.txt delete mode 100644 pills/03/install.txt delete mode 100644 pills/03/list-generations.txt delete mode 100644 pills/03/list-installed-derivations.txt delete mode 100644 pills/03/ls-nix-profile.txt delete mode 100644 pills/03/ls-profile-bin.txt delete mode 100644 pills/03/nix-env-rollback.txt delete mode 100644 pills/03/nix-store-tree.txt delete mode 100644 pills/03/nix-store.txt delete mode 100644 pills/03/references.txt delete mode 100644 pills/03/referrers.txt delete mode 100644 pills/03/rollback.txt delete mode 100644 pills/03/source-nix.txt delete mode 100644 pills/03/uninstall-all.txt create mode 100644 pills/04-basics-of-language.md delete mode 100644 pills/04-basics-of-language.xml delete mode 100644 pills/04/basics.txt delete mode 100644 pills/04/dash.txt delete mode 100644 pills/04/division.txt delete mode 100644 pills/04/double-quotes.txt delete mode 100644 pills/04/escaping.txt delete mode 100644 pills/04/if.txt delete mode 100644 pills/04/interpolate.txt delete mode 100644 pills/04/lazy.txt delete mode 100644 pills/04/let-basic.txt delete mode 100644 pills/04/let-multiple-assign.txt delete mode 100644 pills/04/let-multiple.txt delete mode 100644 pills/04/let-nested.txt delete mode 100644 pills/04/let-reference.txt delete mode 100644 pills/04/let-scope.txt delete mode 100644 pills/04/lists.txt delete mode 100644 pills/04/relative-path.txt delete mode 100644 pills/04/set-access.txt delete mode 100644 pills/04/set-basics.txt delete mode 100644 pills/04/set-failed.txt delete mode 100644 pills/04/set-recursive.txt delete mode 100644 pills/04/strings-basic.txt delete mode 100644 pills/04/with-basic.txt delete mode 100644 pills/04/with-scope.txt create mode 100644 pills/05-functions-and-imports.md delete mode 100644 pills/05-functions-and-imports.xml delete mode 100644 pills/05/a-nix.txt delete mode 100644 pills/05/anon-function.txt delete mode 100644 pills/05/argument-set-error.txt delete mode 100644 pills/05/b-nix.txt delete mode 100644 pills/05/default-values.txt delete mode 100644 pills/05/import.txt delete mode 100644 pills/05/mul-nix.txt delete mode 100644 pills/05/multi-argument-function.txt delete mode 100644 pills/05/named-function.txt delete mode 100644 pills/05/named-set-argument.txt delete mode 100644 pills/05/no-parenthesis.txt delete mode 100644 pills/05/partial-application.txt delete mode 100644 pills/05/set-argument.txt delete mode 100644 pills/05/test-import-2.txt delete mode 100644 pills/05/test-import.txt delete mode 100644 pills/05/test-nix-2.txt delete mode 100644 pills/05/test-nix.txt delete mode 100644 pills/05/variadic-arguments.txt create mode 100644 pills/06-our-first-derivation.md delete mode 100644 pills/06-our-first-derivation.xml delete mode 100644 pills/06/build-derivation.txt delete mode 100644 pills/06/check-drvattrs.txt delete mode 100644 pills/06/coreutils.txt delete mode 100644 pills/06/current-system.txt delete mode 100644 pills/06/drvattrs.txt delete mode 100644 pills/06/examine-build.xml delete mode 100644 pills/06/fake-system.txt delete mode 100644 pills/06/fix-attribute.txt delete mode 100644 pills/06/inspect-values.txt delete mode 100644 pills/06/interpolate.txt delete mode 100644 pills/06/list-coreutils.txt delete mode 100644 pills/06/outpath.txt delete mode 100644 pills/06/realise-derivation.txt delete mode 100644 pills/06/reference.txt delete mode 100644 pills/06/show-derivation.xml delete mode 100644 pills/06/test-build.txt delete mode 100644 pills/06/tostring.txt delete mode 100644 pills/06/troll.png delete mode 100644 pills/06/troll.svg delete mode 100644 pills/06/type-derivation.txt create mode 100644 pills/07-working-derivation.md delete mode 100644 pills/07-working-derivation.xml delete mode 100644 pills/07/bash.xml delete mode 100644 pills/07/builder.sh.txt delete mode 100644 pills/07/c-program-derivation.xml delete mode 100644 pills/07/foo.drv.xml delete mode 100644 pills/07/read-log.xml delete mode 100644 pills/07/simple-derivation.xml delete mode 100644 pills/07/simple.c.txt delete mode 100644 pills/07/simple.txt delete mode 100644 pills/07/simple_builder.sh.txt delete mode 100644 pills/07/simple_inherit.txt create mode 100644 pills/08-generic-builders.md delete mode 100644 pills/08-generic-builders.xml delete mode 100644 pills/08/autotools-nix.txt delete mode 100644 pills/08/generic-builder.txt delete mode 100644 pills/08/hello-builder.txt delete mode 100644 pills/08/hello-nix-darwin.txt delete mode 100644 pills/08/hello-nix-rev-1.txt delete mode 100644 pills/08/hello-nix-rev-2.txt delete mode 100644 pills/08/hello-nix.txt delete mode 100644 pills/08/set-union.txt delete mode 100644 pills/08/to-string-nixpkgs.txt delete mode 100644 pills/08/to-string.txt create mode 100644 pills/09-automatic-runtime-dependencies.md delete mode 100644 pills/09-automatic-runtime.xml delete mode 100644 pills/09/build-hello-nix.txt delete mode 100644 pills/09/find.txt delete mode 100644 pills/09/instantiate-hello.txt delete mode 100644 pills/09/instantiate.txt delete mode 100644 pills/09/ldd-hello.txt delete mode 100644 pills/09/strings.txt create mode 100644 pills/10-developing-with-nix-shell.md delete mode 100644 pills/10-developing-with-nix-shell.xml delete mode 100644 pills/10/autotools-nix.txt delete mode 100644 pills/10/builder-sh.txt delete mode 100644 pills/10/hello-nix.txt delete mode 100644 pills/10/nix-shell-hello.txt delete mode 100644 pills/10/nix-shell-source.txt delete mode 100644 pills/10/setup-sh.txt delete mode 100644 pills/10/source-builder.txt create mode 100644 pills/11-garbage-collector.md delete mode 100644 pills/11-garbage-collector.xml delete mode 100644 pills/11/channel-update.txt delete mode 100644 pills/11/install-bsd-games.txt delete mode 100644 pills/11/ls-gcroots-auto.txt delete mode 100644 pills/11/nix-collect-garbage.txt delete mode 100644 pills/11/remove-bsd-games.txt delete mode 100644 pills/11/remove-gen-9.txt create mode 100644 pills/12-inputs-design-pattern.md delete mode 100644 pills/12-inputs-design-pattern.xml delete mode 100644 pills/12/graphviz-derivation.txt delete mode 100644 pills/12/graphviz-gd-derivation.txt delete mode 100644 pills/12/graphviz-mkderivation.txt delete mode 100644 pills/12/nix-env-install-graphviz.txt delete mode 100644 pills/12/repository-mkderivation.txt delete mode 100644 pills/12/repository-test-nix-build.txt delete mode 100644 pills/12/repository-test-nix-repl.txt delete mode 100644 pills/12/repository.txt delete mode 100644 pills/12/setup-sh.txt delete mode 100644 pills/12/simple-png.txt create mode 100644 pills/13-callpackage-design-pattern.md delete mode 100644 pills/13-callpackage-design-pattern.xml delete mode 100644 pills/13/callpackage-function-call.txt delete mode 100644 pills/13/callpackage-function-overrides.txt delete mode 100644 pills/13/callpackage-function.txt delete mode 100644 pills/13/callpackage-usage.txt delete mode 100644 pills/13/get-args-function.txt delete mode 100644 pills/13/intersect-attr-values.txt delete mode 100644 pills/13/package-derivation.txt delete mode 100644 pills/13/repository-derivation.txt create mode 100644 pills/14-override-design-pattern.md delete mode 100644 pills/14-override-design-pattern.xml delete mode 100644 pills/14/make-overridable-lib.txt delete mode 100644 pills/14/mygraphviz.txt delete mode 100644 pills/14/nix-repl-make-overridable-test.txt delete mode 100644 pills/14/nix-repl-make-overridable-twice.txt delete mode 100644 pills/14/rec-make-overridable.txt create mode 100644 pills/15-nix-search-paths.md delete mode 100644 pills/15-nix-search-paths.xml delete mode 100644 pills/15/mypkgs-graphviz-multiple.txt delete mode 100644 pills/15/mypkgs-install-attr-graphviz.txt delete mode 100644 pills/15/mypkgs-path.txt delete mode 100644 pills/15/mypkgs-query-all.txt delete mode 100644 pills/15/nix-instantiate-ping.txt delete mode 100644 pills/15/nixpkgs-path.txt delete mode 100644 pills/15/ping-custom-path.txt create mode 100644 pills/16-nixpkgs-parameters.md delete mode 100644 pills/16-nixpkgs-parameters.xml delete mode 100644 pills/16/config-foo-bar.txt delete mode 100644 pills/16/myrelease-nix.txt delete mode 100644 pills/16/pkgs-psmisc.txt create mode 100644 pills/17-nixpkgs-overriding-packages.md delete mode 100644 pills/17-nixpkgs-overriding-packages.xml delete mode 100644 pills/17/build-asciidoc-graphviz-override.txt delete mode 100644 pills/17/config-nix.txt delete mode 100644 pills/17/fix-function.txt delete mode 100644 pills/17/fix-pkgs-function.txt delete mode 100644 pills/17/graphviz-override.txt delete mode 100644 pills/17/newpkgs-override-set.txt delete mode 100644 pills/17/p-graphviz-override.txt create mode 100644 pills/18-nix-store-paths.md delete mode 100644 pills/18-nix-store-paths.xml delete mode 100644 pills/18/bar-derivation.xml delete mode 100644 pills/18/derivation-simple-content.xml delete mode 100644 pills/18/derivation-simple.txt delete mode 100644 pills/18/mycontent-string-representation-hash.txt delete mode 100644 pills/18/mycontent-string-representation.txt delete mode 100644 pills/18/mycontent.txt delete mode 100644 pills/18/myfile-final-hash.txt delete mode 100644 pills/18/myfile-hash-alternate.txt delete mode 100644 pills/18/myfile-hash.txt delete mode 100644 pills/18/myfile-string-hash.txt delete mode 100644 pills/18/myout-drv-hash.txt delete mode 100644 pills/18/output-path-replace-empty.txt create mode 100644 pills/19-fundamentals-of-stdenv.md delete mode 100644 pills/19-fundamentals-of-stdenv.xml delete mode 100644 pills/19/default-builder.txt delete mode 100644 pills/19/hello-derivation.xml delete mode 100644 pills/19/stdenv-derivation.txt delete mode 100644 pills/19/stdenv-hello-build.txt delete mode 100644 pills/19/stdenv-hello.txt delete mode 100644 pills/19/stdenv-mkderivation.txt delete mode 100644 pills/19/stdenv-references.txt delete mode 100644 pills/19/stdenv-setup-fake-builder.txt delete mode 100644 pills/19/stdenv-setup-head.txt create mode 100644 pills/20-basic-dependencies-and-hooks.md delete mode 100644 pills/20-basic-dependencies-and-hooks.xml delete mode 100644 pills/20/build-inputs-0.bash delete mode 100644 pills/20/build-inputs-1.bash delete mode 100644 pills/20/build-inputs-2.bash delete mode 100644 pills/20/build-inputs-3.bash delete mode 100644 pills/20/build-inputs-4.bash delete mode 100644 pills/20/env-hooks-0.bash delete mode 100644 pills/20/env-hooks-1.bash delete mode 100644 pills/20/propagated-build-inputs-0.bash delete mode 100644 pills/20/propagated-build-inputs-1.bash delete mode 100644 pills/20/propagated-build-inputs-2.bash delete mode 100644 pills/20/setup-hooks-0.bash delete mode 100644 pills/20/three-hellos.nix delete mode 100644 pills/20/two-hellos.nix create mode 100644 pills/SUMMARY.md delete mode 100644 style.css create mode 100644 theme/highlight.js diff --git a/.gitignore b/.gitignore index 750baeb..707ee7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ result result-* +/book* diff --git a/README.md b/README.md index fac364e..84713da 100644 --- a/README.md +++ b/README.md @@ -9,44 +9,3 @@ You can also build them locally: Similarly, for an [EPUB](https://www.w3.org/publishing/epub32/) version, run: nix-build release.nix -A epub && foliate result/share/doc/nix-pills/nix-pills.epub - -## Development - - - [List of DocBook Elements](https://tdg.docbook.org/tdg/5.2/part2.html) - -Emacs config for a nice DocBook experience: - - ```nix - let - pkgs = import {}; - inherit (pkgs) emacsPackagesNg docbook5 writeText; - - schemas = writeText "schemas.xml" '' - - - - - - - - ''; - -in emacsPackagesNg.emacsWithPackages (epkgs: [ - (emacsPackagesNg.trivialBuild { - pname = "nix-docbook-mode"; - version = "1970-01-01"; - src = writeText "default.el" '' - (eval-after-load 'rng-loc - '(add-to-list 'rng-schema-locating-files "${schemas}")) - (global-set-key (kbd "") 'nxml-complete) - ''; - }) -]) -``` - -Then you can use the keys: - - - - `C-c C-b` to finish & close a tag - - `C-c C-f` to close a tag - - `C-return` to auto-complete a tag or attribute. diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..357fe66 --- /dev/null +++ b/book.toml @@ -0,0 +1,40 @@ +[book] +title = "Nix Pills" +authors = ["Luca Bruno"] +language = "en" +multilingual = false +src = "pills" + +[output.html] +additional-css = ["custom.css"] +edit-url-template = "https://github.com/NixOS/nix-pills/tree/master/{path}" +git-repository-url = "https://github.com/NixOS/nix-pills" + +[output.html.redirect] +"pr01.html" = "00-preface.html" +"why-you-should-give-it-a-try.html" = "01-why-you-should-give-it-a-try.html" +"install-on-your-running-system.html" = "02-install-on-your-running-system.html" +"enter-environment.html" = "03-enter-environment.html" +"basics-of-language.html" = "04-basics-of-language.html" +"functions-and-imports.html" = "05-functions-and-imports.html" +"our-first-derivation.html" = "06-our-first-derivation.html" +"working-derivation.html" = "07-working-derivation.html" +"generic-builders.html" = "08-generic-builders.html" +"automatic-runtime-dependencies.html" = "09-automatic-runtime-dependencies.html" +"developing-with-nix-shell.html" = "10-developing-with-nix-shell.html" +"garbage-collector.html" = "11-garbage-collector.html" +"inputs-design-pattern.html" = "12-inputs-design-pattern.html" +"callpackage-design-pattern.html" = "13-callpackage-design-pattern.html" +"override-design-pattern.html" = "14-override-design-pattern.html" +"nix-search-paths.html" = "15-nix-search-paths.html" +"nixpkgs-parameters.html" = "16-nixpkgs-parameters.html" +"nixpkgs-overriding-packages.html" = "17-nixpkgs-overriding-packages.html" +"nix-store-paths.html" = "18-nix-store-paths.html" +"fundamentals-of-stdenv.html" = "19-fundamentals-of-stdenv.html" +"basic-dependencies-and-hooks.html" = "20-basic-dependencies-and-hooks.html" + +[output.linkcheck] +follow-web-links = true + +# To build the epub version, run: +# mdbook-epub --standalone true diff --git a/book.xml b/book.xml deleted file mode 100644 index 2a7a245..0000000 --- a/book.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - Nix Pills - Version - - - - Preface - - - This is a ported version of the Nix - Pills, a series of blog posts written by Luca Bruno (aka Lethalman) and - originally published in 2014 and 2015. It provides a tutorial - introduction into the Nix package manager and Nixpkgs package - collection, in the form of short chapters called 'pills'. - - - - Since the Nix Pills are considered a classic introduction to - Nix, an effort to port them to the current format was led by - Graham Christensen (aka grahamc / gchristensen) and other - contributors in 2017. - - - - For an up-to-date version, please visit . An EPUB version is also available. - - - If you encounter problems, please report them on the - nixos/nix-pills - issue tracker. - - Commands prefixed with # have to be run as - root, either requiring to login as root user or temporarily switching - to it using sudo for example. - - - - - - - - - - - - - - - - - - - - - - - diff --git a/custom.css b/custom.css new file mode 100644 index 0000000..7d19409 --- /dev/null +++ b/custom.css @@ -0,0 +1,28 @@ +.info { + margin: 20px; + padding: 0 20px; + border-inline-start: 2px solid var(--links); +} + +.info:before { + position: absolute; + width: 3rem; + height: 3rem; + margin-inline-start: calc(-1.5rem - 21px); + content: "ⓘ"; + text-align: center; + background-color: var(--bg); + color: var(--links); + font-weight: bold; + font-size: 2rem; +} + +/* + Make prompts unselectable in console sections. + This relies on highlight.js applying certain classes on the prompts. + For more details, see https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html#stylable-scopes +*/ +.hljs-meta.prompt_ { + user-select: none; + -webkit-user-select: none; +} diff --git a/default.nix b/default.nix index aa4aae2..c03eab1 100644 --- a/default.nix +++ b/default.nix @@ -1,87 +1,32 @@ -{ pkgs ? import {}, revCount, shortRev }: -let - lib = pkgs.lib; - - sources = let - - # We want nix examples, but not the top level nix to build things - noTopLevelNix = path: type: let - relPath = lib.removePrefix (toString ./. + "/") (toString path); - in builtins.match "[^/]*\.nix" relPath == null; - - extensions = [ ".xml" ".txt" ".nix" ".bash" ]; - - in lib.cleanSourceWith { - filter = noTopLevelNix; - src = lib.sourceFilesBySuffices ./. extensions; - }; - - combined = pkgs.runCommand "nix-pills-combined" - { - buildInputs = [ pkgs.libxml2 ]; - meta.description = "Nix Pills with as a single docbook file"; - inherit revCount shortRev; - } - '' - cp -r ${sources} ./sources - chmod -R u+w ./sources - - cd sources - - printf "%s-%s" "$revCount" "$shortRev" > version - xmllint --xinclude --output "$out" ./book.xml - ''; - - toc = builtins.toFile "toc.xml" - '' - - - - - ''; +{ pkgs ? import {} }: - manualXsltprocOptions = toString [ - "--param section.autolabel 1" - "--param section.label.includes.component.label 1" - "--stringparam html.stylesheet style.css" - "--param xref.with.number.and.title 1" - "--param toc.section.depth 3" - "--stringparam admon.style ''" - "--stringparam callout.graphics.extension .svg" - "--stringparam current.docid nix-pills" - "--param chunk.section.depth 0" - "--param chunk.first.sections 1" - "--param use.id.as.filename 1" - "--stringparam generate.toc 'book toc appendix toc'" - "--stringparam chunk.toc '${toc}'" - ]; - -in { - html-split = pkgs.stdenv.mkDerivation { +{ + html-split = pkgs.stdenvNoCC.mkDerivation { name = "nix-pills"; + src = ./.; - src = sources; - - buildInputs = with pkgs; [ - libxslt + nativeBuildInputs = with pkgs; [ + mdbook + mdbook-linkcheck ]; + buildPhase = '' + runHook preBuild + + # We can't check external links inside the sandbox, but it's good to check them outside the sandbox. + substituteInPlace book.toml --replace-fail 'follow-web-links = true' 'follow-web-links = false' + mdbook build + + runHook postBuild + ''; + installPhase = '' runHook preInstall - # Generate the HTML manual. + # The nix pills were originally built into this directory, and consumers of the nix pills expect to find it there. Do not change unless you also change other code that depends on this directory structure. dst=$out/share/doc/nix-pills mkdir -p "$dst" - xsltproc \ - ${manualXsltprocOptions} \ - --nonet --output "$dst/" \ - "${pkgs.docbook-xsl-ns}/xml/xsl/docbook/xhtml/chunk.xsl" \ - "${combined}" - - mkdir -p "$dst/images/callouts" - cp -r "${pkgs.docbook-xsl-ns}/xml/xsl/docbook/images/callouts"/*.svg "$dst/images/callouts" - - cp "${./style.css}" "$dst/style.css" + mv book/html/* "$dst"/ mkdir -p "$out/nix-support" echo "nix-build out $out" >> "$out/nix-support/hydra-build-products" @@ -91,13 +36,13 @@ in { ''; }; - epub = pkgs.stdenv.mkDerivation { + epub = pkgs.stdenvNoCC.mkDerivation { name = "nix-pills-epub"; + src = ./.; - src = sources; - - buildInputs = with pkgs; [ - libxslt + nativeBuildInputs = with pkgs; [ + mdbook-epub + unzip zip ]; @@ -107,29 +52,39 @@ in { doInstallCheck = true; + buildPhase = '' + runHook preBuild + + mdbook-epub --standalone${pkgs.lib.optionalString (pkgs.mdbook-epub.version != "unstable-2022-12-25") " true"} + + # Work around bugs in mdbook-epub. + mkdir nix-pills.epub-fix + ( cd nix-pills.epub-fix + unzip -q "../book/epub/Nix Pills.epub" + # Fix invalid ids. + sed -Ei 's/(id(ref)?=")([0-9])/\1p\3/g' OEBPS/content.opf + sed -Ei 's/(id="|href="#)([0-9])/\1fn\2/g' OEBPS/20-basic-dependencies-and-hooks.html + # Fix anchors. + sed -Ei 's~(.+) \{#([^\}]+)\}()~\1 id="\3"\2\4~g' OEBPS/*.html + # Fix broken links in body. + sed -Ei 's/("[0-9a-z-]+\.)md(["#])/\1html\2/g' OEBPS/*.html + # Remove unnecessary page breaks, the sections are short. + substituteInPlace OEBPS/stylesheet.css --replace-fail "page-break-before: always;" "" + zip -q "../book/epub/Nix Pills.epub" **/* + ) + + runHook postBuild + ''; + installPhase = '' runHook preInstall - # Generate the EPUB manual. + # The nix pills were originally built into this directory, and consumers of the nix pills expect to find it there. Do not change unless you also change other code that depends on this directory structure. dst=$out/share/doc/nix-pills mkdir -p "$dst" - xsltproc \ - ${manualXsltprocOptions} \ - --nonet --output "$dst/epub/" \ - "${pkgs.docbook-xsl-ns}/xml/xsl/docbook/epub3/chunk.xsl" \ - "${combined}" - - mkdir -p "$dst/epub/OEBPS/images/callouts" - cp -r "${pkgs.docbook-xsl-ns}/xml/xsl/docbook/images/callouts"/*.svg "$dst/epub/OEBPS/images/callouts" - cp "${./style.css}" "$dst/epub/OEBPS/style.css" - echo "application/epub+zip" > mimetype manual="$dst/nix-pills.epub" - zip -0Xq "$manual" mimetype - pushd "$dst/epub" && zip -Xr9D "$manual" * - popd - - rm -rf "$dst/epub" + mv "book/epub/Nix Pills.epub" "$manual" mkdir -p "$out/nix-support" echo "nix-build out $out" >> "$out/nix-support/hydra-build-products" diff --git a/pills/00-preface.md b/pills/00-preface.md new file mode 100644 index 0000000..967da61 --- /dev/null +++ b/pills/00-preface.md @@ -0,0 +1,15 @@ +# Preface + +This is a ported version of the **Nix Pills**, a series of blog posts written by **Luca Bruno** (aka Lethalman) and originally published in 2014 and 2015. It provides a tutorial introduction into the Nix package manager and Nixpkgs package collection, in the form of short chapters called 'pills'. + +Since the Nix Pills are considered a classic introduction to Nix, an effort to port them to the current format was led by Graham Christensen (aka grahamc / gchristensen) and other contributors in 2017. + +For an up-to-date version, please visit . An [EPUB version](https://nixos.org/guides/nix-pills/nix-pills.epub) is also available. + +If you encounter problems, please report them on the [nixos/nix-pills](https://github.com/NixOS/nix-pills/issues) issue tracker. + +
+ +Note: Commands prefixed with `#` have to be run as root, either requiring to login as root user or temporarily switching to it using `sudo` for example. + +
diff --git a/pills/01-why-you-should-give-it-a-try.md b/pills/01-why-you-should-give-it-a-try.md new file mode 100644 index 0000000..3138fa3 --- /dev/null +++ b/pills/01-why-you-should-give-it-a-try.md @@ -0,0 +1,98 @@ +# Why You Should Give it a Try + +## Introduction + +Welcome to the first post of the "[Nix](https://nixos.org/nix) in pills" series. Nix is a purely functional package manager and deployment system for POSIX. + +There's a lot of documentation that describes what Nix, [NixOS](https://nixos.org/nixos) and related projects are. But the purpose of this post is to convince you to give Nix a try. Installing NixOS is not required, but sometimes I may refer to NixOS as a real world example of Nix usage for building a whole operating system. + +## Rationale for this series + +The [Nix](https://nixos.org/manual/nix), [Nixpkgs](https://nixos.org/manual/nixpkgs/), and [NixOS](https://nixos.org/manual/nixos/) manuals along with [the wiki](https://nixos.wiki/) are excellent resources for explaining how Nix/NixOS works, how you can use it, and how cool things are being done with it. However, at the beginning you may feel that some of the magic which happens behind the scenes is hard to grasp. + +This series aims to complement the existing explanations from the more formal documents. + +The following is a description of Nix. Just as with pills, I'll try to be as short as possible. + +## Not being purely functional + +Most, if not all, widely used package managers ([dpkg](https://wiki.debian.org/dpkg), [rpm](http://www.rpm.org/), ...) mutate the global state of the system. If a package `foo-1.0` installs a program to `/usr/bin/foo`, you cannot install `foo-1.1` as well, unless you change the installation paths or the binary name. But changing the binary names means breaking users of that binary. + +There are some attempts to mitigate this problem. Debian, for example, partially solves the problem with the [alternatives](https://wiki.debian.org/DebianAlternatives) system. + +So while in theory it's possible with some current systems to install multiple versions of the same package, in practice it's very painful. + +Let's say you need an nginx service and also an nginx-openresty service. You have to create a new package that changes all the paths to have, for example, an `-openresty` suffix. + +Or suppose that you want to run two different instances of mysql: 5.2 and 5.5. The same thing applies, plus you have to also make sure the two mysqlclient libraries do not collide. + +This is not impossible but it _is_ very inconvenient. If you want to install two whole stacks of software like GNOME 3.10 and GNOME 3.12, you can imagine the amount of work. + +From an administrator's point of view: you can use containers. The typical solution nowadays is to create a container per service, especially when different versions are needed. That somewhat solves the problem, but at a different level and with other drawbacks. For example, needing orchestration tools, setting up a shared cache of packages, and new machines to monitor rather than simple services. + +From a developer's point of view: you can use virtualenv for python, or jhbuild for gnome, or whatever else. But then how do you mix the two stacks? How do you avoid recompiling the same thing when it could instead be shared? Also you need to set up your development tools to point to the different directories where libraries are installed. Not only that, there's the risk that some of the software incorrectly uses system libraries. + +And so on. Nix solves all this at the packaging level and solves it well. A single tool to rule them all. + +## Being purely functional + +Nix makes no assumptions about the global state of the system. This has many advantages, but also some drawbacks of course. The core of a Nix system is the Nix store, usually installed under `/nix/store`, and some tools to manipulate the store. In Nix there is the notion of a _derivation_ rather than a package. The difference can be subtle at the beginning, so I will often use the words interchangeably. + +Derivations/packages are stored in the Nix store as follows: `/nix/store/«hash-name»`, where the hash uniquely identifies the derivation (this isn't quite true, it's a little more complex), and the name is the name of the derivation. + +Let's take a bash derivation as an example: `/nix/store/s4zia7hhqkin1di0f187b79sa2srhv6k-bash-4.2-p45/`. This is a directory in the Nix store which contains `bin/bash`. + +What that means is that there's no `/bin/bash`, there's only that self-contained build output in the store. The same goes for coreutils and everything else. To make them convenient to use from the shell, Nix will arrange for binaries to appear in your `PATH` as appropriate. + +What we have is basically a store of all packages (with different versions occupying different locations), and everything in the Nix store is immutable. + +In fact, there's no ldconfig cache either. So where does bash find libc? + +```console +$ ldd `which bash` +libc.so.6 => /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/libc.so.6 (0x00007f0248cce000) +``` + +It turns out that when bash was built, it was built against that specific version of glibc in the Nix store, and at runtime it will require exactly that glibc version. + +Don't be confused by the version in the derivation name: it's only a name for us humans. You may end up having two derivations with the same name but different hashes: it's the hash that really matters. + +What does all this mean? It means that you could run mysql 5.2 with glibc-2.18, and mysql 5.5 with glibc-2.19. You could use your python module with python 2.7 compiled with gcc 4.6 and the same python module with python 3 compiled with gcc 4.8, all in the same system. + +In other words: no dependency hell, not even a dependency resolution algorithm. Straight dependencies from derivations to other derivations. + +From an administrator's point of view: if you want an old PHP version for one application, but want to upgrade the rest of the system, that's not painful any more. + +From a developer's point of view: if you want to develop webkit with llvm 3.4 and 3.3, that's not painful any more. + +## Mutable vs. immutable + +When upgrading a library, most package managers replace it in-place. All new applications run afterwards with the new library without being recompiled. After all, they all refer dynamically to `libc6.so`. + +Since Nix derivations are immutable, upgrading a library like glibc means recompiling all applications, because the glibc path to the Nix store has been hardcoded. + +So how do we deal with security updates? In Nix we have some tricks (still pure) to solve this problem, but that's another story. + +Another problem is that unless software has in mind a pure functional model, or can be adapted to it, it can be hard to compose applications at runtime. + +Let's take Firefox for example. On most systems, you install flash, and it starts working in Firefox because Firefox looks in a global path for plugins. + +In Nix, there's no such global path for plugins. Firefox therefore must know explicitly about the path to flash. The way we handle this problem is to wrap the Firefox binary so that we can setup the necessary environment to make it find flash in the nix store. That will produce a new Firefox derivation: be aware that it takes a few seconds, and it makes composition harder at runtime. + +There are no upgrade/downgrade scripts for your data. It doesn't make sense with this approach, because there's no real derivation to be upgraded. With Nix you switch to using other software with its own stack of dependencies, but there's no formal notion of upgrade or downgrade when doing so. + +If there is a data format change, then migrating to the new data format remains your own responsibility. + +## Conclusion + +Nix lets you compose software at build time with maximum flexibility, and with builds being as reproducible as possible. Not only that, due to its nature deploying systems in the cloud is so easy, consistent, and reliable that in the Nix world all existing self-containment and orchestration tools are deprecated by [NixOps](http://nixos.org/nixops/). + +It however _currently_ falls short when working with dynamic composition at runtime or replacing low level libraries, due to the need to rebuild dependencies. + +That may sound scary, however after running NixOS on both a server and a laptop desktop, I'm very satisfied so far. Some of the architectural problems just need some man-power, other design problems still need to be solved as a community. + +Considering [Nixpkgs](https://nixos.org/nixpkgs/) ([github link](https://github.com/NixOS/nixpkgs)) is a completely new repository of all the existing software, with a completely fresh concept, and with few core developers but overall year-over-year increasing contributions, the current state is more than acceptable and beyond the experimental stage. In other words, it's worth your investment. + +## Next pill... + +...we will install Nix on top of your current system (I assume GNU/Linux, but we also have OSX users) and start inspecting the installed software. diff --git a/pills/01-why-you-should-give-it-try.xml b/pills/01-why-you-should-give-it-try.xml deleted file mode 100644 index 84bbff8..0000000 --- a/pills/01-why-you-should-give-it-try.xml +++ /dev/null @@ -1,284 +0,0 @@ - - - Why You Should Give it a Try - -
- Introduction - - - Welcome to the first post of the "Nix in pills" series. - Nix is a purely functional package manager and deployment - system for POSIX. - - - - There's a lot of documentation that describes what Nix, NixOS and related - projects are. - But the purpose of this post is to convince you to give Nix a try. - Installing NixOS is not required, but sometimes I may refer to - NixOS as a real world example of Nix usage for building a whole - operating system. - -
- -
- Rationale for this series - - The Nix, - Nixpkgs, and - NixOS manuals - along with the wiki are - excellent resources for explaining how Nix/NixOS works, how - you can use it, and how cool things are being done with it. - However, at the beginning you may feel that some of the magic - which happens behind the scenes is hard to grasp. - - - - This series aims to complement the existing explanations from the - more formal documents. - - - - The following is a description of Nix. Just as with pills, I'll try to be as - short as possible. - -
- -
- Not being purely functional - - - Most, if not all, widely used package managers (dpkg, rpm, ...) mutate the - global state of the system. If a package - foo-1.0 installs a program to - /usr/bin/foo, you cannot install - foo-1.1 as well, unless you change the - installation paths or the binary name. - But changing the binary names means breaking users of - that binary. - - - - There are some attempts to mitigate this problem. - Debian, for example, partially solves the problem with the - alternatives - system. - - - - So while in theory it's possible with some current systems to install - multiple versions of the same package, in practice it's very - painful. - - - Let's say you need an nginx service and also an nginx-openresty - service. You have to create a new package that changes all the - paths to have, for example, an -openresty suffix. - - - Or suppose that you want to run two different instances of mysql: 5.2 and - 5.5. The same thing applies, plus you have to also make sure the two - mysqlclient libraries do not collide. - - - This is not impossible but it is very inconvenient. - If you want to install two whole stacks of software like GNOME 3.10 and GNOME - 3.12, you can imagine the amount of work. - - - From an administrator's point of view: you can use containers. The - typical solution nowadays is to create a container per service, - especially when different versions are needed. That somewhat - solves the problem, but at a different level and with other - drawbacks. For example, needing orchestration tools, setting up a shared - cache of packages, and new machines to monitor rather than - simple services. - - - From a developer's point of view: you can use virtualenv for python, or jhbuild - for gnome, or whatever else. But then how do you mix the two stacks? - How do you avoid recompiling the same thing when it could - instead be shared? Also you need to set up your development - tools to point to the different directories where libraries are - installed. Not only that, there's the risk that some of the software - incorrectly uses system libraries. - - - And so on. Nix solves all this at the packaging level and - solves it well. A single tool to rule them all. - -
- -
- Being purely functional - - - Nix makes no assumptions about the global state of the system. - This has many advantages, but also some drawbacks of course. - The core of a Nix system is the Nix store, usually - installed under /nix/store, and some tools to manipulate the - store. In Nix there is the notion of a derivation rather than a - package. The difference can be subtle at the beginning, so I - will often use the words interchangeably. - - - Derivations/packages are stored in the Nix store as follows: - /nix/store/hash-name, - where the hash uniquely identifies the derivation (this isn't quite true, - it's a little more complex), and the name is the name of - the derivation. - - - Let's take a bash derivation as an example: - /nix/store/s4zia7hhqkin1di0f187b79sa2srhv6k-bash-4.2-p45/. - This is a directory in the Nix store which contains bin/bash. - - - What that means is that there's no /bin/bash, there's only that - self-contained build output in the store. The same goes for - coreutils and everything else. To make them convenient - to use from the shell, Nix will arrange for binaries to appear in - your PATH as appropriate. - - - What we have is basically a store of all packages (with different versions - occupying different locations), and everything in the Nix store is immutable. - - - In fact, there's no ldconfig cache either. So where does bash find libc? - - - - It turns out that when bash was built, it was built against that specific - version of glibc in the Nix store, and at runtime it will require exactly that - glibc version. - - - Don't be confused by the version in the derivation name: - it's only a name for us humans. You may end up having two derivations with - the same name but different hashes: it's the hash that really matters. - - - What does all this mean? It means that you could run mysql 5.2 with glibc-2.18, - and mysql 5.5 with glibc-2.19. You could use your python module - with python 2.7 compiled with gcc 4.6 and the same python - module with python 3 compiled with gcc 4.8, all in the same - system. - - - In other words: no dependency hell, not even a dependency - resolution algorithm. Straight dependencies from derivations to - other derivations. - - - From an administrator's point of view: if you want an old PHP version for - one application, but want to upgrade the rest of the system, that's not painful any more. - - - From a developer's point of view: if you want to develop webkit with llvm - 3.4 and 3.3, that's not painful any more. - -
- -
- Mutable vs. immutable - - - When upgrading a library, most package managers replace it in-place. - All new applications run afterwards with the new library - without being recompiled. After all, they all refer dynamically - to libc6.so. - - - Since Nix derivations are immutable, upgrading a library like glibc - means recompiling all applications, because the glibc path to - the Nix store has been hardcoded. - - - So how do we deal with security updates? In Nix we have some - tricks (still pure) to solve this problem, but that's another - story. - - - Another problem is that unless software has in mind a pure - functional model, or can be adapted to it, it can be hard to compose - applications at runtime. - - - Let's take Firefox for example. On most systems, you install flash, - and it starts working in Firefox because Firefox looks in a global path for plugins. - - - In Nix, there's no such global path for plugins. Firefox - therefore must know explicitly about the path to flash. The way - we handle this problem is to wrap - the Firefox binary so that we can setup the necessary environment to make - it find flash in the nix store. That will produce a new Firefox - derivation: be aware that it takes a few seconds, and it makes - composition harder at runtime. - - - There are no upgrade/downgrade scripts for your data. It doesn't make - sense with this approach, because there's no real derivation to - be upgraded. With Nix you switch to using other software with - its own stack of dependencies, but there's no formal notion of - upgrade or downgrade when doing so. - - - If there is a data format change, then migrating to the new data format remains - your own responsibility. - -
-
- Conclusion - - Nix lets you compose software at build time with maximum - flexibility, and with builds being as reproducible as possible. - Not only that, due to its nature deploying systems in the cloud is - so easy, consistent, and reliable that in the Nix world all - existing self-containment and orchestration tools are - deprecated by NixOps. - - - It however currently falls short when - working with dynamic composition at runtime or replacing low - level libraries, due to the need to rebuild dependencies. - - - That may sound scary, however after running NixOS on both a - server and a laptop desktop, I'm very satisfied so far. Some of - the architectural problems just need some man-power, other - design problems still need to be solved as a community. - - - Considering Nixpkgs (github - link) is a completely new repository of all the existing - software, with a completely fresh concept, and with few core - developers but overall year-over-year increasing contributions, - the current state is more than acceptable and beyond the - experimental stage. In other words, it's worth your investment. - -
- -
- Next pill... - - - ...we will install Nix on top of your current system (I assume - GNU/Linux, but we also have OSX users) and start inspecting the - installed software. - -
-
diff --git a/pills/01/which-bash.txt b/pills/01/which-bash.txt deleted file mode 100644 index aee9e1f..0000000 --- a/pills/01/which-bash.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ ldd `which bash` -libc.so.6 => /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/libc.so.6 (0x00007f0248cce000) diff --git a/pills/02-install-on-your-running-system.md b/pills/02-install-on-your-running-system.md new file mode 100644 index 0000000..b186692 --- /dev/null +++ b/pills/02-install-on-your-running-system.md @@ -0,0 +1,155 @@ +# Install on Your Running System + +Welcome to the second Nix pill. In the [first](01-why-you-should-give-it-a-try.md) pill we briefly described Nix. + +Now we'll install Nix on our running system and understand what changed in our system after the installation. **If you're using NixOS, Nix is already installed; you can skip to the [next](03-enter-environment.md) pill.** + +For installation instructions, please refer to the Nix Reference Manual on [ Installing Nix](https://nixos.org/manual/nix/stable/installation/installing-binary.html). + +## Installation + +These articles are not a tutorial on _using_ Nix. Instead, we're going to walk through the Nix system to understand the fundamentals. + +The first thing to note: derivations in the Nix store refer to other derivations which are themselves in the Nix store. They don't use `libc` from our system or anywhere else. It's a self-contained store of all the software we need to bootstrap up to any particular package. + +
+ +Note: In a multi-user installation, such as the one used in NixOS, the store is owned by root and multiple users can install and build software through a Nix daemon. You can read more about multi-user installations here: . + +
+ +## The beginnings of the Nix store + +Start looking at the output of the install command: + +``` +copying Nix to /nix/store.......................... +``` + +That's the `/nix/store` we were talking about in the first article. We're copying in the necessary software to bootstrap a Nix system. You can see bash, coreutils, the C compiler toolchain, perl libraries, sqlite and Nix itself with its own tools and libnix. + +You may have noticed that `/nix/store` can contain not only directories, but also files, still always in the form «hash-name». + +## The Nix database + +Right after copying the store, the installation process initializes a database: + +``` +initialising Nix database... +``` + +Yes, Nix also has a database. It's stored under `/nix/var/nix/db`. It is a sqlite database that keeps track of the dependencies between derivations. + +The schema is very simple: there's a table of valid paths, mapping from an auto increment integer to a store path. + +Then there's a dependency relation from path to paths upon which they depend. + +You can inspect the database by installing sqlite (`nix-env -iA sqlite -f ''`) and then running `sqlite3 /nix/var/nix/db/db.sqlite`. + +
+ +Note: If this is the first time you're using Nix after the initial installation, remember you must close and open your terminals first, so that your shell environment will be updated. + +
+ +
+ +Important: Never change `/nix/store` manually. If you do, then it will no longer be in sync with the sqlite db, unless you _really_ know what you are doing. + +
+ +## The first profile + +Next in the installation, we encounter the concept of the [profile](https://nixos.org/manual/nix/stable/package-management/profiles.html): + +
creating /home/nix/.nix-profile
+installing 'nix-2.1.3'
+building path(s) `/nix/store/a7p1w3z2h8pl00ywvw6icr3g5l9vm5r7-user-environment'
+created 7 symlinks in user environment
+
+ +A profile in Nix is a general and convenient concept for realizing rollbacks. Profiles are used to compose components that are spread among multiple paths under a new unified path. Not only that, but profiles are made up of multiple "generations": they are versioned. Whenever you change a profile, a new generation is created. + +Generations can be switched and rolled back atomically, which makes them convenient for managing changes to your system. + +Let's take a closer look at our profile: + +
$ ls -l ~/.nix-profile/
+bin -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin
+[...]
+manifest.nix -> /nix/store/q8b5238akq07lj9gfb3qb5ycq4dxxiwm-env-manifest.nix
+[...]
+share -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/share
+
+ +That `nix-2.1.3` derivation in the Nix store is Nix itself, with binaries and libraries. The process of "installing" the derivation in the profile basically reproduces the hierarchy of the `nix-2.1.3` store derivation in the profile by means of symbolic links. + +The contents of this profile are special, because only one program has been installed in our profile, therefore e.g. the `bin` directory points to the only program which has been installed (Nix itself). + +But that's only the contents of the latest generation of our profile. In fact, `~/.nix-profile` itself is a symbolic link to `/nix/var/nix/profiles/default`. + +In turn, that's a symlink to `default-1-link` in the same directory. Yes, that means it's the first generation of the `default` profile. + +Finally, `default-1-link` is a symlink to the nix store "user-environment" derivation that you saw printed during the installation process. + +We'll talk about `manifest.nix` more in the next article. + +## Nixpkgs expressions + +More output from the installer: + +``` +downloading Nix expressions from `http://releases.nixos.org/nixpkgs/nixpkgs-14.10pre46060.a1a2851/nixexprs.tar.xz'... +unpacking channels... +created 2 symlinks in user environment +modifying /home/nix/.profile... +``` + +Nix expressions are written in the [Nix language](https://nix.dev/tutorials/nix-language) and used to describe packages and how to build them. [Nixpkgs](https://nixos.org/nixpkgs/) is the repository containing all of the expressions: . + +The installer downloaded the package descriptions from commit `a1a2851`. + +The second profile we discover is the channels profile. `~/.nix-defexpr/channels` points to `/nix/var/nix/profiles/per-user/nix/channels` which points to `channels-1-link` which points to a Nix store directory containing the downloaded Nix expressions. + +Channels are a set of packages and expressions available for download. Similar to Debian stable and unstable, there's a stable and unstable channel. In this installation, we're tracking `nixpkgs-unstable`. + +Don't worry about Nix expressions yet, we'll get to them later. + +Finally, for your convenience, the installer modified `~/.profile` to automatically enter the Nix environment. What `~/.nix-profile/etc/profile.d/nix.sh` really does is simply to add `~/.nix-profile/bin` to `PATH` and `~/.nix-defexpr/channels/nixpkgs` to `NIX_PATH`. We'll discuss `NIX_PATH` later. + +Read `nix.sh`, it's short. + +## FAQ: Can I change /nix to something else? + +You can, but there's a good reason to keep using `/nix` instead of a different directory. All the derivations depend on other derivations by using absolute paths. We saw in the first article that bash referenced a `glibc` under a specific absolute path in `/nix/store`. + +You can see for yourself, don't worry if you see multiple bash derivations: + +```console +$ ldd /nix/store/*bash*/bin/bash +[...] +``` + +Keeping the store in `/nix` means we can grab the binary cache from nixos.org (just like you grab packages from debian mirrors) otherwise: + +- `glibc` would be installed under `/foo/store` + +- Thus bash would need to point to `glibc` under `/foo/store`, instead of under `/nix/store` + +- So the binary cache can't help, because we need a _different_ bash, and so we'd have to recompile everything ourselves. + +After all `/nix` is a sensible place for the store. + +## Conclusion + +We've installed Nix on our system, fully isolated and owned by the `nix` user as we're still coming to terms with this new system. + +We learned some new concepts like profiles and channels. In particular, with profiles we're able to manage multiple generations of a composition of packages, while with channels we're able to download binaries from `nixos.org`. + +The installation put everything under `/nix`, and some symlinks in the Nix user home. That's because every user is able to install and use software in her own environment. + +I hope I left nothing uncovered so that you think there's some kind of magic going on behind the scenes. It's all about putting components in the store and symlinking these components together. + +## Next pill... + +...we will enter the Nix environment and learn how to interact with the store. diff --git a/pills/02-install-on-your-running.xml b/pills/02-install-on-your-running.xml deleted file mode 100644 index ab4679e..0000000 --- a/pills/02-install-on-your-running.xml +++ /dev/null @@ -1,325 +0,0 @@ - - - Install on Your Running System - - - Welcome to the second Nix pill. In the first pill we - briefly described Nix. - - - - Now we'll install Nix on our running system and understand what - changed in our system after the installation. - If you're using NixOS, Nix is already installed; - you can skip to the next pill. - - - - For installation instructions, please refer to the Nix Reference Manual on - - Installing Nix. - - -
- Installation - - - These articles are not a tutorial on using Nix. - Instead, we're going to walk through the Nix system to understand the fundamentals. - - - - The first thing to note: derivations in the Nix store refer to other - derivations which are themselves in the Nix store. They don't use libc - from our system or anywhere else. It's a self-contained store of all the software we need to bootstrap up - to any particular package. - - - - In a multi-user installation, such as the one used in NixOS, - the store is owned by root and multiple users can install and build - software through a Nix daemon. You can read more about multi-user - installations here: https://nixos.org/manual/nix/stable/installation/installing-binary.html#multi-user-installation. - -
- -
- The beginnings of the Nix store - - - Start looking at the output of the install command: - - - copying Nix to /nix/store.......................... - - - That's the /nix/store we - were talking about in the first article. We're copying in the - necessary software to bootstrap a Nix system. You can see bash, - coreutils, the C compiler toolchain, perl libraries, sqlite and Nix itself - with its own tools and libnix. - - - - You may have noticed that /nix/store can contain - not only directories, but also files, still always in the form - hash-name. - -
- -
- The Nix database - - - Right after copying the store, the installation process - initializes a database: - - - initialising Nix database... - - - Yes, Nix also has a database. It's stored under - /nix/var/nix/db. It is a sqlite database - that keeps track of the dependencies between derivations. - - - - The schema is very simple: there's a table of valid paths, - mapping from an auto increment integer to a store path. - - - - Then there's a dependency relation from path to paths upon which they depend. - - - - You can inspect the database by installing sqlite - (nix-env -iA sqlite -f '<nixpkgs>') and then running - sqlite3 /nix/var/nix/db/db.sqlite. - - - If this is the first time you're using Nix after the - initial installation, remember you must close and open your - terminals first, so that your shell environment will be updated. - - - Never change /nix/store manually. If you do, then it will - no longer be in sync with the sqlite db, unless you really - know what you are doing. - -
- -
- The first profile - - - Next in the installation, we encounter the concept of the profile: - - - - - - A profile in Nix is a general and convenient concept for - realizing rollbacks. Profiles are used to compose - components that are spread among multiple paths under a new - unified path. Not only that, but profiles are made up of multiple - "generations": they are versioned. Whenever you change a profile, - a new generation is created. - - - - Generations can be switched and rolled back atomically, which makes - them convenient for managing changes to your system. - - - - Let's take a closer look at our profile: - - - - - That nix-2.1.3 derivation in the Nix store is - Nix itself, with binaries and libraries. The process of "installing" - the derivation in the profile basically reproduces the hierarchy of the - nix-2.1.3 store derivation in the profile by means of - symbolic links. - - - The contents of this profile are special, because only one - program has been installed in our profile, therefore e.g. the - bin directory points to the only program - which has been installed (Nix itself). - - - But that's only the contents of the latest generation of our - profile. In fact, ~/.nix-profile itself is a - symbolic link to - /nix/var/nix/profiles/default. - - - - In turn, that's a symlink to default-1-link - in the same directory. Yes, that means it's the first generation of - the default profile. - - - - Finally, default-1-link is a symlink to the nix - store "user-environment" derivation that you saw printed during the installation process. - - - - We'll talk about manifest.nix more in the next article. - -
- -
- Nixpkgs expressions - - - More output from the installer: - - - - - - Nix expressions are written in the Nix language and used to - describe packages and how to build them. Nixpkgs is the - repository containing all of the expressions: https://github.com/NixOS/nixpkgs. - - - - The installer downloaded the package descriptions from commit - a1a2851. - - - - The second profile we discover is the channels profile. - ~/.nix-defexpr/channels points to - /nix/var/nix/profiles/per-user/nix/channels - which points to channels-1-link which points - to a Nix store directory containing the downloaded Nix - expressions. - - - - Channels are a set of packages and expressions available for - download. Similar to Debian stable and unstable, there's a - stable and unstable channel. In this installation, we're - tracking nixpkgs-unstable. - - - - Don't worry about Nix expressions yet, we'll get to them later. - - - - Finally, for your convenience, the installer modified - ~/.profile to automatically enter the Nix - environment. What - ~/.nix-profile/etc/profile.d/nix.sh really - does is simply to add ~/.nix-profile/bin to - PATH and - ~/.nix-defexpr/channels/nixpkgs to - NIX_PATH. We'll discuss - NIX_PATH later. - - - - Read nix.sh, it's short. - - -
- -
- FAQ: Can I change /nix to something else? - - - You can, but there's a good reason to keep using - /nix instead of a different directory. All - the derivations depend on other derivations by using absolute paths. We - saw in the first article that bash referenced a - glibc under a specific absolute path in /nix/store. - - - - You can see for yourself, don't worry if you see multiple - bash derivations: - - - - - - - Keeping the store in /nix means we can grab - the binary cache from nixos.org (just like you grab packages - from debian mirrors) otherwise: - - - - glibc would be installed under /foo/store - - - Thus bash would need to point to glibc under /foo/store, - instead of under /nix/store - - - So the binary cache can't help, because we need a different bash, - and so we'd have to recompile everything ourselves. - - - - - - After all /nix is a sensible place for the store. - -
- -
- Conclusion - - We've installed Nix on our system, fully isolated and owned by - the nix user as we're still coming to terms with - this new system. - - - We learned some new concepts like profiles and channels. In - particular, with profiles we're able to manage multiple - generations of a composition of packages, while with channels - we're able to download binaries from nixos.org. - - - The installation put everything under /nix, - and some symlinks in the Nix user home. That's because every - user is able to install and use software in her own environment. - - - I hope I left nothing uncovered so that you think there's - some kind of magic going on behind the scenes. It's all - about putting components in the store and symlinking - these components together. - -
- -
- Next pill... - - - ...we will enter the Nix environment and learn how to interact - with the store. - -
- -
diff --git a/pills/02/ldd-bash.txt b/pills/02/ldd-bash.txt deleted file mode 100644 index d4553b8..0000000 --- a/pills/02/ldd-bash.txt +++ /dev/null @@ -1,3 +0,0 @@ - -$ ldd /nix/store/*bash*/bin/bash -[...] diff --git a/pills/02/nixpkgs-expressions.txt b/pills/02/nixpkgs-expressions.txt deleted file mode 100644 index 88287be..0000000 --- a/pills/02/nixpkgs-expressions.txt +++ /dev/null @@ -1,4 +0,0 @@ -downloading Nix expressions from `http://releases.nixos.org/nixpkgs/nixpkgs-14.10pre46060.a1a2851/nixexprs.tar.xz'... -unpacking channels... -created 2 symlinks in user environment -modifying /home/nix/.profile... diff --git a/pills/02/profile.xml b/pills/02/profile.xml deleted file mode 100644 index 7230087..0000000 --- a/pills/02/profile.xml +++ /dev/null @@ -1,6 +0,0 @@ -$ ls -l ~/.nix-profile/ -bin -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin -[...] -manifest.nix -> /nix/store/q8b5238akq07lj9gfb3qb5ycq4dxxiwm-env-manifest.nix -[...] -share -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/share diff --git a/pills/02/user-environment.xml b/pills/02/user-environment.xml deleted file mode 100644 index 6dcaf0c..0000000 --- a/pills/02/user-environment.xml +++ /dev/null @@ -1,4 +0,0 @@ -creating /home/nix/.nix-profile -installing 'nix-2.1.3' -building path(s) `/nix/store/a7p1w3z2h8pl00ywvw6icr3g5l9vm5r7-user-environment' -created 7 symlinks in user environment diff --git a/pills/03-enter-environment.md b/pills/03-enter-environment.md new file mode 100644 index 0000000..19247f7 --- /dev/null +++ b/pills/03-enter-environment.md @@ -0,0 +1,234 @@ +# Enter the Environment {#enter-environment} + +Welcome to the third Nix pill. In the [second pill](02-install-on-your-running-system.md) we installed Nix on our running system. Now we can finally play with it a little, these things also apply to NixOS users. + +## Enter the environment + +**If you're using NixOS, you can skip to the [next](#install-something) step.** + +In the previous article we created a Nix user, so let's start by switching to it with `su - nix`. If your `~/.profile` got evaluated, then you should now be able to run commands like `nix-env` and `nix-store`. + +If that's not the case: + +```console +$ source ~/.nix-profile/etc/profile.d/nix.sh +``` + +To remind you, `~/.nix-profile/etc` points to the `nix-2.1.3` derivation. At this point, we are in our Nix user profile. + +## Install something {#install-something} + +Finally something practical! Installation into the Nix environment is an interesting process. Let's install `hello`, a simple CLI tool which prints `Hello world` and is mainly used to test compilers and package installations. + +Back to the installation: + +```console +$ nix-env -i hello +installing 'hello-2.10' +[...] +building '/nix/store/0vqw0ssmh6y5zj48yg34gc6macr883xk-user-environment.drv'... +created 36 symlinks in user environment +``` + +Now you can run `hello`. Things to notice: + +- We installed software as a user, and only for the Nix user. + +- It created a new user environment. That's a new generation of our Nix user profile. + +- The [nix-env](https://nixos.org/manual/nix/stable/command-ref/nix-env.html) tool manages environments, profiles and their generations. + +- We installed `hello` by derivation name minus the version. I repeat: we specified the **derivation name** (minus the version) to install it. + +We can list generations without walking through the `/nix` hierarchy: + +```console +$ nix-env --list-generations + 1 2014-07-24 09:23:30 + 2 2014-07-25 08:45:01 (current) +``` + +Listing installed derivations: + +```console +$ nix-env -q +nix-2.1.3 +hello-2.10 +``` + +So, where did `hello` really get installed? `which hello` is `~/.nix-profile/bin/hello` which points to the store. We can also list the derivation paths with `nix-env -q --out-path`. So that's what those derivation paths are called: the **output** of a build. + +## Path merging + +At this point you probably want to run `man` to get some documentation. Even if you already have man system-wide outside of the Nix environment, you can install and use it within Nix with `nix-env -i man-db`. As usual, a new generation will be created, and `~/.nix-profile` will point to it. + +Let's inspect the [profile](https://nixos.org/manual/nix/stable/package-management/profiles.html) a bit: + +```console +$ ls -l ~/.nix-profile/ +dr-xr-xr-x 2 nix nix 4096 Jan 1 1970 bin +lrwxrwxrwx 1 nix nix 55 Jan 1 1970 etc -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/etc +[...] +``` + +Now that's interesting. When only `nix-2.1.3` was installed, `bin` was a symlink to `nix-2.1.3`. Now that we've actually installed some things (`man`, `hello`), it's a real directory, not a symlink. + +```console +$ ls -l ~/.nix-profile/bin/ +[...] +man -> /nix/store/83cn9ing5sc6644h50dqzzfxcs07r2jn-man-1.6g/bin/man +[...] +nix-env -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env +[...] +hello -> /nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10/bin/hello +[...] +``` + +Okay, that's clearer now. `nix-env` merged the paths from the installed derivations. `which man` points to the Nix profile, rather than the system `man`, because `~/.nix-profile/bin` is at the head of `$PATH`. + +## Rolling back and switching generation + +The last command installed `man`. We should be at generation 3, unless you changed something in the middle. Let's say we want to rollback to the old generation: + +```console +$ nix-env --rollback +switching from generation 3 to 2 +``` + +Now `nix-env -q` does not list `man` anymore. `` ls -l `which man` `` should now be your system copy. + +Enough with the rollback, let's go back to the most recent generation: + +```console +$ nix-env -G 3 +switching from generation 2 to 3 +``` + +I invite you to read the manpage of `nix-env`. `nix-env` requires an operation to perform, then there are common options for all operations, as well as options specific to each operation. + +You can of course also [ uninstall](https://nixos.org/manual/nix/stable/command-ref/nix-env.html#operation---uninstall) and [upgrade](https://nixos.org/manual/nix/stable/command-ref/nix-env.html#operation---upgrade) packages. + +## Querying the store + +So far we learned how to query and manipulate the environment. But all of the environment components point to the store. + +To query and manipulate the store, there's the `nix-store` command. We can do some interesting things, but we'll only see some queries for now. + +To show the direct runtime dependencies of `hello`: + +```console +$ nix-store -q --references `which hello` +/nix/store/fg4yq8i8wd08xg3fy58l6q73cjy8hjr2-glibc-2.27 +/nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10 +``` + +The argument to `nix-store` can be anything as long as it points to the Nix store. It will follow symlinks. + +It may not make sense to you right now, but let's print reverse dependencies of `hello`: + +```console +$ nix-store -q --referrers `which hello` +/nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10 +/nix/store/fhvy2550cpmjgcjcx5rzz328i0kfv3z3-env-manifest.nix +/nix/store/yzdk0xvr0b8dcwhi2nns6d75k2ha5208-env-manifest.nix +/nix/store/mp987abm20c70pl8p31ljw1r5by4xwfw-user-environment +/nix/store/ppr3qbq7fk2m2pa49i2z3i32cvfhsv7p-user-environment +``` + +Was it what you expected? It turns out that our environments depend upon `hello`. Yes, that means that the environments are in the store, and since they contain symlinks to `hello`, therefore the environment depends upon `hello`. + +Two environments were listed, generation 2 and generation 3, since these are the ones that had `hello` installed in them. + +The `manifest.nix` file contains metadata about the environment, such as which derivations are installed. So that `nix-env` can list, upgrade or remove them. And yet again, the current `manifest.nix` can be found at `~/.nix-profile/manifest.nix`. + +## Closures + +The closures of a derivation is a list of all its dependencies, recursively, including absolutely everything necessary to use that derivation. + +```console +$ nix-store -qR `which man` +[...] +``` + +Copying all those derivations to the Nix store of another machine makes you able to run `man` out of the box on that other machine. That's the base of deployment using Nix, and you can already foresee the potential when deploying software in the cloud (hint: `nix-copy-closures` and `nix-store --export`). + +A nicer view of the closure: + +```console +$ nix-store -q --tree `which man` +[...] +``` + +With the above command, you can find out exactly why a _runtime_ dependency, be it direct or indirect, exists for a given derivation. + +The same applies to environments. As an exercise, run `nix-store -q --tree ~/.nix-profile`, and see that the first children are direct dependencies of the user environment: the installed derivations, and the `manifest.nix`. + +## Dependency resolution + +There isn't anything like `apt` which solves a SAT problem in order to satisfy dependencies with lower and upper bounds on versions. There's no need for this because all the dependencies are static: if a derivation X depends on a derivation Y, then it always depends on it. A version of X which depended on Z would be a different derivation. + +## Recovering the hard way + +```console +$ nix-env -e '*' +uninstalling 'hello-2.10' +uninstalling 'nix-2.1.3' +[...] +``` + +Oops, that uninstalled all derivations from the environment, including Nix. That means we can't even run `nix-env`, what now? + +Previously we got `nix-env` from the environment. Environments are a convenience for the user, but Nix is still there in the store! + +First, pick one `nix-2.1.3` derivation: `ls /nix/store/*nix-2.1.3`, say `/nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3`. + +The first option is to rollback: + +```console +$ /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env --rollback +``` + +The second option is to install Nix, thus creating a new generation: + +```console +$ /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env -i /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env +``` + +## Channels + +So where are we getting packages from? We said something about this already in the [second article](02-install-on-your-running-system.md). There's a list of channels from which we get packages, although usually we use a single channel. The tool to manage channels is [nix-channel](https://nixos.org/manual/nix/stable/command-ref/nix-channel.html). + +```console +$ nix-channel --list +nixpkgs http://nixos.org/channels/nixpkgs-unstable +``` + +If you're using NixOS, you may not see any output from the above command (if you're using the default), or you may see a channel whose name begins with "nixos-" instead of "nixpkgs". + +That's essentially the contents of `~/.nix-channels`. + +
+ +Note: `~/.nix-channels` is not a symlink to the nix store! + +
+ +To update the channel run `nix-channel --update`. That will download the new Nix expressions (descriptions of the packages), create a new generation of the channels profile and unpack it under `~/.nix-defexpr/channels`. + +This is quite similar to `apt-get update`. (See [this table](https://nixos.wiki/wiki/Cheatsheet) for a rough mapping between Ubuntu and NixOS package management.) + +## Conclusion + +We learned how to query the user environment and to manipulate it by installing and uninstalling software. Upgrading software is also straightforward, as you can read in [the manual](https://nixos.org/manual/nix/stable/command-ref/nix-env.html#operation---upgrade) (`nix-env -u` will upgrade all packages in the environment). + +Every time we change the environment, a new generation is created. Switching between generations is easy and immediate. + +Then we learned how to query the store. We inspected the dependencies and reverse dependencies of store paths. + +We saw how symlinks are used to compose paths from the Nix store, a useful trick. + +A quick analogy with programming languages: you have the heap with all the objects, that corresponds to the Nix store. You have objects that point to other objects, those correspond to derivations. This is a suggestive metaphor, but will it be the right path? + +## Next pill + +...we will learn the basics of the Nix language. The Nix language is used to describe how to build derivations, and it's the basis for everything else, including NixOS. Therefore it's very important to understand both the syntax and the semantics of the language. diff --git a/pills/03-enter-environment.xml b/pills/03-enter-environment.xml deleted file mode 100644 index ccddacb..0000000 --- a/pills/03-enter-environment.xml +++ /dev/null @@ -1,406 +0,0 @@ - - - Enter the Environment - - - Welcome to the third Nix pill. In the second pill we - installed Nix on our running system. Now we can finally play with it a - little, these things also apply to NixOS users. - - -
- Enter the environment - - - If you're using NixOS, you can skip to the next step. - - - - In the previous article we created a Nix user, so let's start by switching - to it with su - nix. If your - ~/.profile got evaluated, then you should now be able - to run commands like nix-env and - nix-store. - - - - If that's not the case: - - - - - - To remind you, ~/.nix-profile/etc points to the nix-2.1.3 - derivation. At this point, we are in our Nix user profile. - -
- -
- Install something - - - Finally something practical! Installation into the Nix environment is an - interesting process. Let's install hello, a simple CLI - tool which prints Hello world and is mainly used to test compilers - and package installations. - - - - Back to the installation: - - - - - - Now you can run hello. Things to notice: - - - - - We installed software as a user, and only for the Nix user. - - - - - It created a new user environment. That's a new generation of our - Nix user profile. - - - - - The nix-env - tool manages environments, profiles and their generations. - - - - - We installed hello by derivation name minus the version. I repeat: - we specified the derivation name - (minus the version) to install it. - - - - - - We can list generations without walking through the /nix hierarchy: - - - - - - Listing installed derivations: - - - - - - So, where did hello really get installed? - which hello is - ~/.nix-profile/bin/hello which points to the store. - We can also list the derivation paths with nix-env -q --out-path. So - that's what those derivation paths are called: the - output of a build. - -
- -
- Path merging - - - At this point you probably want to run man to get some documentation. - Even if you - already have man system-wide outside of the Nix environment, you can - install and use it within Nix with nix-env -i man-db. As - usual, a new generation will be created, and ~/.nix-profile will point to - it. - - - - Let's inspect the profile - a bit: - - - - - - Now that's interesting. When only nix-2.1.3 was installed, bin was a - symlink to nix-2.1.3. Now that we've actually installed some things - (man, hello), it's a real directory, not a symlink. - - - - - - Okay, that's clearer now. nix-env merged the paths from the installed derivations. - which man points to the Nix profile, rather than the - system man, because ~/.nix-profile/bin is at the head - of $PATH. - -
- -
- Rolling back and switching generation - - - The last command installed man. We should be at generation 3, unless - you changed something in the middle. Let's say we want to rollback to the - old generation: - - - - - - Now nix-env -q does not list man anymore. - ls -l `which man` should now be your system copy. - - - - Enough with the rollback, let's go back to the most recent generation: - - - - - - I invite you to read the manpage of nix-env. nix-env requires an operation - to perform, then there are common options for all operations, as well as - options specific to each operation. - - - - You can of course also - uninstall and upgrade packages. - -
- -
- Querying the store - - - So far we learned how to query and manipulate the environment. But all - of the environment components point to the store. - - - - To query and manipulate the store, there's the - nix-store command. We can do some interesting things, but we'll - only see some queries for now. - - - - To show the direct runtime dependencies of hello: - - - - - - The argument to nix-store can be anything as long as it points to the - Nix store. It will follow symlinks. - - - - It may not make sense to you right now, but let's print reverse - dependencies of hello: - - - - - - Was it what you expected? It turns out that our environments depend upon hello. - Yes, that means that the environments are in the store, and since they contain symlinks to hello, - therefore the environment depends upon hello. - - - - Two environments were listed, generation 2 and generation 3, since these are the ones that had - hello installed in them. - - - - The manifest.nix file contains metadata about the environment, such as - which derivations are installed. So that nix-env can list, upgrade - or remove them. And yet again, the current manifest.nix can be found at - ~/.nix-profile/manifest.nix. - -
- -
- Closures - - - The closures of a derivation is a list of all its dependencies, recursively, - including absolutely everything necessary to use that derivation. - - - - - - Copying all those derivations to the Nix store of another machine makes - you able to run man out of the box on that other machine. That's the - base of deployment using Nix, and you can already foresee the potential when - deploying software in the cloud (hint: - nix-copy-closures and - nix-store --export). - - - - A nicer view of the closure: - - - - - - With the above command, you can find out exactly why a - runtime dependency, be it direct or - indirect, exists for a given derivation. - - - - The same applies to environments. As an exercise, run - nix-store -q --tree ~/.nix-profile, and see that the - first children are direct dependencies of the user environment: - the installed derivations, and the manifest.nix. - -
- -
- Dependency resolution - - - There isn't anything like apt which solves a SAT problem in order to - satisfy dependencies with lower and upper bounds on versions. There's no need - for this because all the dependencies are static: if a derivation X depends on a derivation Y, - then it always depends on it. A version of X which depended on Z would be a different derivation. - -
- -
- Recovering the hard way - - - - - Oops, that uninstalled all derivations from the environment, including - Nix. That means we can't even run nix-env, what now? - - - - Previously we got nix-env from the environment. Environments - are a convenience for the user, but Nix is still there in the store! - - - - First, pick one nix-2.1.3 derivation: - ls /nix/store/*nix-2.1.3, say - /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3. - - - - The first option is to rollback: - - - - - - The second option is to install Nix, thus creating a new generation: - - - -
- -
- Channels - - - So where are we getting packages from? We said something about this already in the - second article. - There's a list of channels from which we get packages, although usually we use a - single channel. The tool to manage channels is - nix-channel. - - - - - - If you're using NixOS, you may not see any output from the above command - (if you're using the default), or you may see a channel whose - name begins with "nixos-" instead of "nixpkgs". - - - - That's essentially the contents of ~/.nix-channels. - - - - ~/.nix-channels is not a symlink to the - nix store! - - - - To update the channel run nix-channel --update. - That will download the new Nix expressions (descriptions of the packages), - create a new generation of the channels profile and unpack it under - ~/.nix-defexpr/channels. - - - - This is quite similar to apt-get update. - (See this table - for a rough mapping between Ubuntu and NixOS package management.) - -
- -
- Conclusion - - - We learned how to query the user environment and to manipulate it by - installing and uninstalling software. Upgrading software is also straightforward, - as you can read in - the manual - (nix-env -u will upgrade all packages in the - environment). - - - - Every time we change the environment, a new generation is created. - Switching between generations is easy and immediate. - - - - Then we learned how to query the store. We inspected the dependencies and reverse - dependencies of store paths. - - - - We saw how symlinks are used to compose paths from the Nix store, a useful - trick. - - - - A quick analogy with programming languages: you have the heap with all the - objects, that corresponds to the Nix store. You have objects that point to other - objects, those correspond to derivations. This is a suggestive metaphor, but will it be the right path? - -
- -
- Next pill - - - ...we will learn the basics of the Nix language. The Nix language is used - to describe how to build derivations, and it's the basis for everything - else, including NixOS. Therefore it's very important to understand both the - syntax and the semantics of the language. - -
-
diff --git a/pills/03/channel-list.txt b/pills/03/channel-list.txt deleted file mode 100644 index 477e56c..0000000 --- a/pills/03/channel-list.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-channel --list -nixpkgs http://nixos.org/channels/nixpkgs-unstable diff --git a/pills/03/generation-3.txt b/pills/03/generation-3.txt deleted file mode 100644 index c7baf1d..0000000 --- a/pills/03/generation-3.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-env -G 3 -switching from generation 2 to 3 diff --git a/pills/03/install-hello.txt b/pills/03/install-hello.txt deleted file mode 100644 index 7d707f2..0000000 --- a/pills/03/install-hello.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ nix-env -i hello -installing 'hello-2.10' -[...] -building '/nix/store/0vqw0ssmh6y5zj48yg34gc6macr883xk-user-environment.drv'... -created 36 symlinks in user environment diff --git a/pills/03/install.txt b/pills/03/install.txt deleted file mode 100644 index 09e46d1..0000000 --- a/pills/03/install.txt +++ /dev/null @@ -1 +0,0 @@ -$ /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env -i /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env diff --git a/pills/03/list-generations.txt b/pills/03/list-generations.txt deleted file mode 100644 index d4d8f4d..0000000 --- a/pills/03/list-generations.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-env --list-generations - 1 2014-07-24 09:23:30 - 2 2014-07-25 08:45:01 (current) diff --git a/pills/03/list-installed-derivations.txt b/pills/03/list-installed-derivations.txt deleted file mode 100644 index b485f85..0000000 --- a/pills/03/list-installed-derivations.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-env -q -nix-2.1.3 -hello-2.10 diff --git a/pills/03/ls-nix-profile.txt b/pills/03/ls-nix-profile.txt deleted file mode 100644 index 57d6d96..0000000 --- a/pills/03/ls-nix-profile.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ ls -l ~/.nix-profile/ -dr-xr-xr-x 2 nix nix 4096 Jan 1 1970 bin -lrwxrwxrwx 1 nix nix 55 Jan 1 1970 etc -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/etc -[...] diff --git a/pills/03/ls-profile-bin.txt b/pills/03/ls-profile-bin.txt deleted file mode 100644 index 4b4fb6a..0000000 --- a/pills/03/ls-profile-bin.txt +++ /dev/null @@ -1,8 +0,0 @@ -$ ls -l ~/.nix-profile/bin/ -[...] -man -> /nix/store/83cn9ing5sc6644h50dqzzfxcs07r2jn-man-1.6g/bin/man -[...] -nix-env -> /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env -[...] -hello -> /nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10/bin/hello -[...] diff --git a/pills/03/nix-env-rollback.txt b/pills/03/nix-env-rollback.txt deleted file mode 100644 index dab9f06..0000000 --- a/pills/03/nix-env-rollback.txt +++ /dev/null @@ -1 +0,0 @@ -$ /nix/store/ig31y9gfpp8pf3szdd7d4sf29zr7igbr-nix-2.1.3/bin/nix-env --rollback diff --git a/pills/03/nix-store-tree.txt b/pills/03/nix-store-tree.txt deleted file mode 100644 index 31978d1..0000000 --- a/pills/03/nix-store-tree.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-store -q --tree `which man` -[...] diff --git a/pills/03/nix-store.txt b/pills/03/nix-store.txt deleted file mode 100644 index 9eb350f..0000000 --- a/pills/03/nix-store.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-store -qR `which man` -[...] diff --git a/pills/03/references.txt b/pills/03/references.txt deleted file mode 100644 index 08e391e..0000000 --- a/pills/03/references.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-store -q --references `which hello` -/nix/store/fg4yq8i8wd08xg3fy58l6q73cjy8hjr2-glibc-2.27 -/nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10 diff --git a/pills/03/referrers.txt b/pills/03/referrers.txt deleted file mode 100644 index dfef101..0000000 --- a/pills/03/referrers.txt +++ /dev/null @@ -1,6 +0,0 @@ -$ nix-store -q --referrers `which hello` -/nix/store/58r35bqb4f3lxbnbabq718svq9i2pda3-hello-2.10 -/nix/store/fhvy2550cpmjgcjcx5rzz328i0kfv3z3-env-manifest.nix -/nix/store/yzdk0xvr0b8dcwhi2nns6d75k2ha5208-env-manifest.nix -/nix/store/mp987abm20c70pl8p31ljw1r5by4xwfw-user-environment -/nix/store/ppr3qbq7fk2m2pa49i2z3i32cvfhsv7p-user-environment diff --git a/pills/03/rollback.txt b/pills/03/rollback.txt deleted file mode 100644 index 3c3de9f..0000000 --- a/pills/03/rollback.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-env --rollback -switching from generation 3 to 2 diff --git a/pills/03/source-nix.txt b/pills/03/source-nix.txt deleted file mode 100644 index 906e3f8..0000000 --- a/pills/03/source-nix.txt +++ /dev/null @@ -1 +0,0 @@ -$ source ~/.nix-profile/etc/profile.d/nix.sh diff --git a/pills/03/uninstall-all.txt b/pills/03/uninstall-all.txt deleted file mode 100644 index 823fd33..0000000 --- a/pills/03/uninstall-all.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-env -e '*' -uninstalling 'hello-2.10' -uninstalling 'nix-2.1.3' -[...] diff --git a/pills/04-basics-of-language.md b/pills/04-basics-of-language.md new file mode 100644 index 0000000..ef403b6 --- /dev/null +++ b/pills/04-basics-of-language.md @@ -0,0 +1,272 @@ +# The Basics of the Language {#basics-of-language} + +Welcome to the fourth Nix pill. In the [previous article](03-enter-environment.md) we learned about Nix environments. We installed software as a user, managed their profile, switched between generations, and queried the Nix store. Those are the very basics of system administration using Nix. + +The [Nix language](https://nixos.org/manual/nix/stable/expressions/expression-language.html) is used to write expressions that produce derivations. The [nix-build](https://nixos.org/manual/nix/stable/command-ref/nix-build.html) tool is used to build derivations from an expression. Even as a system administrator that wants to customize the installation, it's necessary to master Nix. Using Nix for your jobs means you get the features we saw in the previous articles for free. + +The syntax of Nix is quite unfamiliar, so looking at existing examples may lead you to think that there's a lot of magic happening. In reality, it's mostly about writing utility functions to make things convenient. + +On the other hand, the same syntax is great for describing packages, so learning the language itself will pay off when writing package expressions. + +
+ +Important: In Nix, everything is an expression, there are no statements. This is common in functional languages. + +
+ +
+ +Important: Values in Nix are immutable. + +
+ +## Value types + +Nix 2.0 contains a command named `nix repl` which is a simple command line tool for playing with the Nix language. In fact, Nix is a [pure, lazy, functional language](https://nixos.org/manual/nix/stable/expressions/expression-language.html), not only a set of tools to manage derivations. The `nix repl` syntax is slightly different to Nix syntax when it comes to assigning variables, but it shouldn't be confusing so long as you bear it in mind. I prefer to start with `nix repl` before cluttering your mind with more complex expressions. + +Launch `nix repl`. First of all, Nix supports basic arithmetic operations: `+`, `-`, `*` and `/`. (To exit `nix repl`, use the command `:q`. Help is available through the `:?` command.) + +```console +nix-repl> 1+3 +4 + +nix-repl> 7-4 +3 + +nix-repl> 3*2 +6 +``` + +Attempting to perform division in Nix can lead to some surprises. + +```console +nix-repl> 6/3 +/home/nix/6/3 +``` + +What happened? Recall that Nix is not a general purpose language, it's a domain-specific language for writing packages. Integer division isn't actually that useful when writing package expressions. Nix parsed `6/3` as a relative path to the current directory. To get Nix to perform division instead, leave a space after the `/`. Alternatively, you can use `builtins.div`. + +```console +nix-repl> 6/ 3 +2 + +nix-repl> builtins.div 6 3 +2 +``` + +Other operators are `||`, `&&` and `!` for booleans, and relational operators such as `!=`, `==`, `<`, `>`, `<=`, `>=`. In Nix, `<`, `>`, `<=` and `>=` are not much used. There are also other operators we will see in the course of this series. + +Nix has integer, floating point, string, path, boolean and null [simple](https://nixos.org/manual/nix/stable/expressions/language-values.html) types. Then there are also lists, sets and functions. These types are enough to build an operating system. + +Nix is strongly typed, but it's not statically typed. That is, you cannot mix strings and integers, you must first do the conversion. + +As demonstrated above, expressions will be parsed as paths as long as there's a slash not followed by a space. Therefore to specify the current directory, use `./.` In addition, Nix also parses urls specially. + +Not all urls or paths can be parsed this way. If a syntax error occurs, it's still possible to fallback to plain strings. Literal urls and paths are convenient for additional safety. + +## Identifier + +There's not much to say here, except that dash (`-`) is allowed in identifiers. That's convenient since many packages use dash in their names. In fact: + +```console +nix-repl> a-b +error: undefined variable `a-b' at (string):1:1 +nix-repl> a - b +error: undefined variable `a' at (string):1:1 +``` + +As you can see, `a-b` is parsed as identifier, not as a subtraction. + +## Strings + +It's important to understand the syntax for strings. When learning to read Nix expressions, you may find dollars (`$`) ambiguous, but they are very important . Strings are enclosed by double quotes (`"`), or two single quotes (`''`). + +```console +nix-repl> "foo" +"foo" +nix-repl> ''foo'' +"foo" +``` + +In other languages like Python you can also use single quotes for strings (e.g. `'foo'`), but not in Nix. + +It's possible to [interpolate](https://nixos.org/manual/nix/stable/expressions/language-values.html) whole Nix expressions inside strings with the `${...}` syntax and only that syntax, not `$foo` or `{$foo}` or anything else. + +```console +nix-repl> foo = "strval" +nix-repl> "$foo" +"$foo" +nix-repl> "${foo}" +"strval" +nix-repl> "${2+3}" +error: cannot coerce an integer to a string, at (string):1:2 +``` + +Note: ignore the `foo = "strval"` assignment, special syntax in `nix repl`. + +As said previously, you cannot mix integers and strings. You need to explicitly include conversions. We'll see this later: function calls are another story. + +Using the syntax with two single quotes is useful for writing double quotes inside strings without needing to escape them: + +```console +nix-repl> ''test " test'' +"test \" test" +nix-repl> ''${foo}'' +"strval" +``` + +Escaping `${...}` within double quoted strings is done with the backslash. Within two single quotes, it's done with `''`: + +```console +nix-repl> "\${foo}" +"${foo}" +nix-repl> ''test ''${foo} test'' +"test ${foo} test" +``` + +## Lists + +Lists are a sequence of expressions delimited by space (_not_ comma): + +```console +nix-repl> [ 2 "foo" true (2+3) ] +[ 2 "foo" true 5 ] +``` + +Lists, like everything else in Nix, are immutable. Adding or removing elements from a list is possible, but will return a new list. + +## Attribute sets + +An attribute set is an association between string keys and Nix values. Keys can only be strings. When writing attribute sets you can also use unquoted identifiers as keys. + +```console +nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; } +nix-repl> s +{ "123" = "num"; a-b = "baz"; foo = "bar"; } +``` + +For those reading Nix expressions from nixpkgs: do not confuse attribute sets with argument sets used in functions. + +To access elements in the attribute set: + +```console +nix-repl> s.a-b +"baz" +nix-repl> s."123" +"num" +``` + +Yes, you can use strings to address keys which aren't valid identifiers. + +Inside an attribute set you cannot normally refer to elements of the same attribute set: + +```console +nix-repl> { a = 3; b = a+4; } +error: undefined variable `a' at (string):1:10 +``` + +To do so, use [recursive attribute sets](https://nixos.org/manual/nix/stable/expressions/language-constructs.html#recursive-sets): + +```console +nix-repl> rec { a = 3; b = a+4; } +{ a = 3; b = 7; } +``` + +This is very convenient when defining packages, which tend to be recursive attribute sets. + +## If expressions + +These are expressions, not statements. + +```console +nix-repl> a = 3 +nix-repl> b = 4 +nix-repl> if a > b then "yes" else "no" +"no" +``` + +You can't have only the `then` branch, you must specify also the `else` branch, because an expression must have a value in all cases. + +## Let expressions + +This kind of expression is used to define local variables for inner expressions. + +```console +nix-repl> let a = "foo"; in a +"foo" +``` + +The syntax is: first assign variables, then `in`, then an expression which can use the defined variables. The value of the whole `let` expression will be the value of the expression after the `in`. + +```console +nix-repl> let a = 3; b = 4; in a + b +7 +``` + +Let's write two `let` expressions, one inside the other: + +```console +nix-repl> let a = 3; in let b = 4; in a + b +7 +``` + +With `let` you cannot assign twice to the same variable. However, you can shadow outer variables: + +```console +nix-repl> let a = 3; a = 8; in a +error: attribute `a' at (string):1:12 already defined at (string):1:5 +nix-repl> let a = 3; in let a = 8; in a +8 +``` + +You cannot refer to variables in a `let` expression outside of it: + +```console +nix-repl> let a = (let c = 3; in c); in c +error: undefined variable `c' at (string):1:31 +``` + +You can refer to variables in the `let` expression when assigning variables, like with recursive attribute sets: + +```console +nix-repl> let a = 4; b = a + 5; in b +9 +``` + +So beware when you want to refer to a variable from the outer scope, but it's also defined in the current let expression. The same applies to recursive attribute sets. + +## With expression + +This kind of expression is something you rarely see in other languages. You can think of it like a more granular version of `using` from C++, or `from module import *` from Python. You decide per-expression when to include symbols into the scope. + +```console +nix-repl> longName = { a = 3; b = 4; } +nix-repl> longName.a + longName.b +7 +nix-repl> with longName; a + b +7 +``` + +That's it, it takes an attribute set and includes symbols from it in the scope of the inner expression. Of course, only valid identifiers from the keys of the set will be included. If a symbol exists in the outer scope and would also be introduced by the `with`, it will _not_ be shadowed. You can however still refer to the attribute set: + +```console +nix-repl> let a = 10; in with longName; a + b +14 +nix-repl> let a = 10; in with longName; longName.a + b +7 +``` + +## Laziness + +Nix evaluates expressions only when needed. This is a great feature when working with packages. + +```console +nix-repl> let a = builtins.div 4 0; b = 6; in b +6 +``` + +Since `a` is not needed, there's no error about division by zero, because the expression is not in need to be evaluated. That's why we can have all the packages defined on demand, yet have access to specific packages very quickly. + +## Next pill + +...we will talk about functions and imports. In this pill I've tried to avoid function calls as much as possible, otherwise the post would have been too long. diff --git a/pills/04-basics-of-language.xml b/pills/04-basics-of-language.xml deleted file mode 100644 index 545b6df..0000000 --- a/pills/04-basics-of-language.xml +++ /dev/null @@ -1,363 +0,0 @@ - - - The Basics of the Language - - - Welcome to the fourth Nix pill. In the - previous article we learned about Nix - environments. We installed software as a user, managed their profile, switched - between generations, and queried the Nix store. Those are the very basics of - system administration using Nix. - - - - The - Nix language - is used to write expressions that produce derivations. The - nix-build - tool is used to build derivations from an expression. Even as a system administrator that - wants to customize the installation, it's necessary to master Nix. Using - Nix for your jobs means you get the features we saw in the previous articles - for free. - - - - The syntax of Nix is quite unfamiliar, so looking at existing examples may lead you to - think that there's a lot of magic happening. In reality, it's mostly about - writing utility functions to make things convenient. - - - - On the other hand, the same syntax is great for describing packages, so learning the language - itself will pay off when writing package expressions. - - - - In Nix, everything is an expression, there are no statements. This is common in functional - languages. - - - - Values in Nix are immutable. - - -
- Value types - - - Nix 2.0 contains a command named nix repl which is a simple command line tool - for playing with the Nix language. In fact, Nix is a - pure, lazy, functional language, - not only a set of tools to manage derivations. The nix repl syntax is slightly - different to Nix syntax when it comes to assigning variables, but it shouldn't - be confusing so long as you bear it in mind. I prefer to start with nix repl - before cluttering your mind with more complex expressions. - - - - Launch nix repl. First of all, Nix supports basic arithmetic operations: - +, -, * and /. - (To exit nix repl, use the command :q. - Help is available through the :? command.) - - - - - - Attempting to perform division in Nix can lead to some surprises. - - - - - - What happened? Recall that Nix is not a general purpose language, it's a - domain-specific language for writing packages. Integer division isn't - actually that useful when writing package expressions. Nix parsed - 6/3 as a relative path to the current directory. To get - Nix to perform division instead, leave a space after the - /. Alternatively, you can use - builtins.div. - - - - - - Other operators are ||, && and ! - for booleans, and relational - operators such as !=, ==, <, >, - <=, >=. In Nix, <, >, - <= and >= are not much used. There are also other operators we will see in the - course of this series. - - - - Nix has integer, floating point, string, path, boolean and null - simple - types. Then there are also lists, sets and functions. These types are enough - to build an operating system. - - - - Nix is strongly typed, but it's not statically typed. That is, you - cannot mix strings and integers, you must first do the conversion. - - - - As demonstrated above, expressions will - be parsed as paths as long as there's a slash not followed by a space. - Therefore to specify the current directory, use ./. - In addition, Nix also parses urls specially. - - - - Not all urls or paths can be parsed this way. If a syntax error occurs, - it's still possible to fallback to plain strings. Literal urls and paths - are convenient for additional safety. - -
- -
- Identifier - - - There's not much to say here, except that dash (-) is allowed in identifiers. That's - convenient since many packages use dash in their names. In fact: - - - - - - As you can see, a-b is parsed as identifier, not as - a subtraction. - -
- -
- Strings - - - It's important to understand the syntax for strings. When learning to read Nix - expressions, you may find dollars ($) ambiguous, but they are very important . - Strings are enclosed by double quotes ("), or two single quotes (''). - - - - - - In other languages like Python you can also use single quotes for strings (e.g. 'foo'), - but not in Nix. - - - - It's possible to - interpolate - whole Nix expressions inside strings with the ${...} syntax and only that syntax, - not $foo or {$foo} or anything else. - - - - - - Note: ignore the foo = "strval" assignment, special syntax in nix repl. - - - - As said previously, you cannot mix integers and strings. You need to explicitly - include conversions. We'll see this later: function calls are another story. - - - - Using the syntax with two single quotes is useful for writing double - quotes inside strings without needing to escape them: - - - - - - Escaping ${...} within double quoted strings is done with the backslash. - Within two single quotes, it's done with '': - - - - -
- -
- Lists - - - Lists are a sequence of expressions delimited by space (not comma): - - - - - - Lists, like everything else in Nix, are immutable. Adding or removing - elements from a list is possible, but will return a new list. - -
- -
- Attribute sets - - - An attribute set is an association between string keys and Nix values. Keys - can only be strings. When writing attribute sets you can also use unquoted identifiers as - keys. - - - - - - For those reading Nix expressions from nixpkgs: do not confuse attribute sets with - argument sets used in functions. - - - - To access elements in the attribute set: - - - - - - Yes, you can use strings to address keys which aren't valid identifiers. - - - - Inside an attribute set you cannot normally refer to elements of the same attribute set: - - - - - - To do so, use - recursive attribute sets: - - - - - - This is very convenient when defining packages, which tend to be recursive attribute sets. - -
- -
- If expressions - - - These are expressions, not statements. - - - - - - You can't have only the then branch, you must specify also the else - branch, because an expression must have a value in all cases. - -
- -
- Let expressions - - - This kind of expression is used to define local variables for inner - expressions. - - - - - - The syntax is: first assign variables, then in, then an expression which can - use the defined variables. The value of the whole let expression will be - the value of the expression after the in. - - - - - - Let's write two let expressions, one inside the other: - - - - - - With let you cannot assign twice to the same variable. However, you can - shadow outer variables: - - - - - - You cannot refer to variables in a let expression outside of it: - - - - - - You can refer to variables in the let expression when assigning variables, - like with recursive attribute sets: - - - - - - So beware when you want to refer to a variable from the outer scope, but - it's also defined in the current let expression. The same applies to - recursive attribute sets. - -
- -
- With expression - - - This kind of expression is something you rarely see in other languages. - You can think of it like a more granular version of using - from C++, or from module import * from Python. You decide - per-expression when to include symbols into the scope. - - - - - - That's it, it takes an attribute set and includes symbols from it in the scope of the inner - expression. Of course, only valid identifiers from the keys of the set will be - included. If a symbol exists in the outer scope and would also be introduced by - the with, it will not be shadowed. - You can however still refer to the attribute set: - - - -
- -
- Laziness - - - Nix evaluates expressions only when needed. This is a great feature when - working with packages. - - - - - - Since a is not needed, there's no error about division by zero, because - the expression is not in need to be evaluated. That's why we can have all - the packages defined on demand, yet have access to specific packages very quickly. - -
- -
- Next pill - - - ...we will talk about functions and imports. In this pill I've tried to - avoid function calls as much as possible, otherwise the post would have - been too long. - -
-
diff --git a/pills/04/basics.txt b/pills/04/basics.txt deleted file mode 100644 index 8cd4664..0000000 --- a/pills/04/basics.txt +++ /dev/null @@ -1,8 +0,0 @@ -nix-repl> 1+3 -4 - -nix-repl> 7-4 -3 - -nix-repl> 3*2 -6 diff --git a/pills/04/dash.txt b/pills/04/dash.txt deleted file mode 100644 index c018aaa..0000000 --- a/pills/04/dash.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> a-b -error: undefined variable `a-b' at (string):1:1 -nix-repl> a - b -error: undefined variable `a' at (string):1:1 diff --git a/pills/04/division.txt b/pills/04/division.txt deleted file mode 100644 index 36e768b..0000000 --- a/pills/04/division.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> 6/ 3 -2 - -nix-repl> builtins.div 6 3 -2 diff --git a/pills/04/double-quotes.txt b/pills/04/double-quotes.txt deleted file mode 100644 index aed382b..0000000 --- a/pills/04/double-quotes.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> ''test " test'' -"test \" test" -nix-repl> ''${foo}'' -"strval" diff --git a/pills/04/escaping.txt b/pills/04/escaping.txt deleted file mode 100644 index 041a502..0000000 --- a/pills/04/escaping.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> "\${foo}" -"${foo}" -nix-repl> ''test ''${foo} test'' -"test ${foo} test" diff --git a/pills/04/if.txt b/pills/04/if.txt deleted file mode 100644 index b496bc1..0000000 --- a/pills/04/if.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> a = 3 -nix-repl> b = 4 -nix-repl> if a > b then "yes" else "no" -"no" diff --git a/pills/04/interpolate.txt b/pills/04/interpolate.txt deleted file mode 100644 index ef3b069..0000000 --- a/pills/04/interpolate.txt +++ /dev/null @@ -1,7 +0,0 @@ -nix-repl> foo = "strval" -nix-repl> "$foo" -"$foo" -nix-repl> "${foo}" -"strval" -nix-repl> "${2+3}" -error: cannot coerce an integer to a string, at (string):1:2 diff --git a/pills/04/lazy.txt b/pills/04/lazy.txt deleted file mode 100644 index f659943..0000000 --- a/pills/04/lazy.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = builtins.div 4 0; b = 6; in b -6 diff --git a/pills/04/let-basic.txt b/pills/04/let-basic.txt deleted file mode 100644 index 1b5a42e..0000000 --- a/pills/04/let-basic.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = "foo"; in a -"foo" diff --git a/pills/04/let-multiple-assign.txt b/pills/04/let-multiple-assign.txt deleted file mode 100644 index dc88d30..0000000 --- a/pills/04/let-multiple-assign.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> let a = 3; a = 8; in a -error: attribute `a' at (string):1:12 already defined at (string):1:5 -nix-repl> let a = 3; in let a = 8; in a -8 diff --git a/pills/04/let-multiple.txt b/pills/04/let-multiple.txt deleted file mode 100644 index 5d94010..0000000 --- a/pills/04/let-multiple.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = 3; b = 4; in a + b -7 diff --git a/pills/04/let-nested.txt b/pills/04/let-nested.txt deleted file mode 100644 index 8acd36f..0000000 --- a/pills/04/let-nested.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = 3; in let b = 4; in a + b -7 diff --git a/pills/04/let-reference.txt b/pills/04/let-reference.txt deleted file mode 100644 index f947d7a..0000000 --- a/pills/04/let-reference.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = 4; b = a + 5; in b -9 diff --git a/pills/04/let-scope.txt b/pills/04/let-scope.txt deleted file mode 100644 index 669a98c..0000000 --- a/pills/04/let-scope.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let a = (let c = 3; in c); in c -error: undefined variable `c' at (string):1:31 diff --git a/pills/04/lists.txt b/pills/04/lists.txt deleted file mode 100644 index 70f3daf..0000000 --- a/pills/04/lists.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> [ 2 "foo" true (2+3) ] -[ 2 "foo" true 5 ] diff --git a/pills/04/relative-path.txt b/pills/04/relative-path.txt deleted file mode 100644 index 7ff07b2..0000000 --- a/pills/04/relative-path.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> 6/3 -/home/nix/6/3 diff --git a/pills/04/set-access.txt b/pills/04/set-access.txt deleted file mode 100644 index 50dcb65..0000000 --- a/pills/04/set-access.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> s.a-b -"baz" -nix-repl> s."123" -"num" diff --git a/pills/04/set-basics.txt b/pills/04/set-basics.txt deleted file mode 100644 index ce7fe28..0000000 --- a/pills/04/set-basics.txt +++ /dev/null @@ -1,3 +0,0 @@ -nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; } -nix-repl> s -{ "123" = "num"; a-b = "baz"; foo = "bar"; } diff --git a/pills/04/set-failed.txt b/pills/04/set-failed.txt deleted file mode 100644 index 4503bf4..0000000 --- a/pills/04/set-failed.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> { a = 3; b = a+4; } -error: undefined variable `a' at (string):1:10 diff --git a/pills/04/set-recursive.txt b/pills/04/set-recursive.txt deleted file mode 100644 index 5dbc393..0000000 --- a/pills/04/set-recursive.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> rec { a = 3; b = a+4; } -{ a = 3; b = 7; } diff --git a/pills/04/strings-basic.txt b/pills/04/strings-basic.txt deleted file mode 100644 index 05e8b75..0000000 --- a/pills/04/strings-basic.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> "foo" -"foo" -nix-repl> ''foo'' -"foo" diff --git a/pills/04/with-basic.txt b/pills/04/with-basic.txt deleted file mode 100644 index 3d0ac96..0000000 --- a/pills/04/with-basic.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> longName = { a = 3; b = 4; } -nix-repl> longName.a + longName.b -7 -nix-repl> with longName; a + b -7 diff --git a/pills/04/with-scope.txt b/pills/04/with-scope.txt deleted file mode 100644 index d90f07e..0000000 --- a/pills/04/with-scope.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> let a = 10; in with longName; a + b -14 -nix-repl> let a = 10; in with longName; longName.a + b -7 diff --git a/pills/05-functions-and-imports.md b/pills/05-functions-and-imports.md new file mode 100644 index 0000000..fe378dc --- /dev/null +++ b/pills/05-functions-and-imports.md @@ -0,0 +1,222 @@ +# Functions and Imports + +Welcome to the fifth Nix pill. In the previous [fourth pill](04-basics-of-language.md) we touched the Nix language for a moment. We introduced basic types and values of the Nix language, and basic expressions such as `if`, `with` and `let`. I invite you to re-read about these expressions and play with them in the repl. + +Functions help to build reusable components in a big repository like [nixpkgs](https://github.com/NixOS/nixpkgs/). The Nix manual has a [great explanation of functions](https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions). Let's go: pill on one hand, Nix manual on the other hand. + +I remind you how to enter the Nix environment: `source ~/.nix-profile/etc/profile.d/nix.sh` + +## Nameless and single parameter + +Functions are anonymous (lambdas), and only have a single parameter. The syntax is extremely simple. Type the parameter name, then "`:`", then the body of the function. + +```console +nix-repl> x: x*2 +«lambda» +``` + +So here we defined a function that takes a parameter `x`, and returns `x*2`. The problem is that we cannot use it in any way, because it's unnamed... joke! + +We can store functions in variables. + +```console +nix-repl> double = x: x*2 +nix-repl> double +«lambda» +nix-repl> double 3 +6 +``` + +As usual, please ignore the special syntax for assignments inside `nix repl`. So, we defined a function `x: x*2` that takes one parameter `x`, and returns `x*2`. This function is then assigned to the variable `double`. Finally we did our first function call: `double 3`. + +Big note: it's not like many other programming languages where you write `double(3)`. It really is `double 3`. + +In summary: to call a function, name the variable, then space, then the argument. Nothing else to say, it's as easy as that. + +## More than one parameter + +How do we create a function that accepts more than one parameter? For people not used to functional programming, this may take a while to grasp. Let's do it step by step. + +```console +nix-repl> mul = a: (b: a*b) +nix-repl> mul +«lambda» +nix-repl> mul 3 +«lambda» +nix-repl> (mul 3) 4 +12 +``` + +We defined a function that takes the parameter `a`, the body returns another function. This other function takes a parameter `b` and returns `a*b`. Therefore, calling `mul 3` returns this kind of function: `b: 3*b`. In turn, we call the returned function with `4`, and get the expected result. + +You don't have to use parentheses at all, Nix has sane priorities when parsing the code: + +```console +nix-repl> mul = a: b: a*b +nix-repl> mul +«lambda» +nix-repl> mul 3 +«lambda» +nix-repl> mul 3 4 +12 +nix-repl> mul (6+7) (8+9) +221 +``` + +Much more readable, you don't even notice that functions only receive one argument. Since the argument is separated by a space, to pass more complex expressions you need parentheses. In other common languages you would write `mul(6+7, 8+9)`. + +Given that functions have only one parameter, it is straightforward to use **partial application**: + +```console +nix-repl> foo = mul 3 +nix-repl> foo 4 +12 +nix-repl> foo 5 +15 +``` + +We stored the function returned by `mul 3` into a variable foo, then reused it. + +## Argument set + +Now this is a very cool feature of Nix. It is possible to pattern match over a set in the parameter. We write an alternative version of `mul = a: b: a*b` first by using a set as argument, then using pattern matching. + +```console +nix-repl> mul = s: s.a*s.b +nix-repl> mul { a = 3; b = 4; } +12 +nix-repl> mul = { a, b }: a*b +nix-repl> mul { a = 3; b = 4; } +12 +``` + +In the first case we defined a function that accepts a single parameter. We then access attributes `a` and `b` from the given set. Note how the parentheses-less syntax for function calls is very elegant in this case, instead of doing `mul({ a=3; b=4; })` in other languages. + +In the second case we defined an argument set. It's like defining a set, except without values. We require that the passed set contains the keys `a` and `b`. Then we can use those `a` and `b` in the function body directly. + +```console +nix-repl> mul = { a, b }: a*b +nix-repl> mul { a = 3; b = 4; c = 6; } +error: anonymous function at (string):1:2 called with unexpected argument `c', at (string):1:1 +nix-repl> mul { a = 3; } +error: anonymous function at (string):1:2 called without required argument `b', at (string):1:1 +``` + +Only a set with exactly the attributes required by the function is accepted, nothing more, nothing less. + +## Default and variadic attributes + +It is possible to specify **default values** of attributes in the argument set: + +```console +nix-repl> mul = { a, b ? 2 }: a*b +nix-repl> mul { a = 3; } +6 +nix-repl> mul { a = 3; b = 4; } +12 +``` + +Also you can allow passing more attributes (**variadic**) than the expected ones: + +```console +nix-repl> mul = { a, b, ... }: a*b +nix-repl> mul { a = 3; b = 4; c = 2; } +``` + +However, in the function body you cannot access the "c" attribute. The solution is to give a name to the given set with the **@-pattern**: + +```console +nix-repl> mul = s@{ a, b, ... }: a*b*s.c +nix-repl> mul { a = 3; b = 4; c = 2; } +24 +``` + +That's it, you give a name to the whole parameter with name@ before the set pattern. + +Advantages of using argument sets: + +- Named unordered arguments: you don't have to remember the order of the arguments. + +- You can pass sets, that adds a whole new layer of flexibility and convenience. + +Disadvantages: + +- Partial application does not work with argument sets. You have to specify the whole attribute set, not part of it. + +You may find similarities with [Python \*\*kwargs](https://docs.python.org/3/faq/programming.html#how-can-i-pass-optional-or-keyword-parameters-from-one-function-to-another). + +## Imports + +The `import` function is built-in and provides a way to parse a `.nix` file. The natural approach is to define each component in a `.nix` file, then compose by importing these files. + +Let's start with the bare metal. + +`a.nix`: + +```nix +3 +``` + +`b.nix`: + +```nix +4 +``` + +`mul.nix`: + +```nix +a: b: a*b +``` + +```console +nix-repl> a = import ./a.nix +nix-repl> b = import ./b.nix +nix-repl> mul = import ./mul.nix +nix-repl> mul a b +12 +``` + +Yes it's really that simple. You import a file, and it gets parsed as an expression. Note that the scope of the imported file does not inherit the scope of the importer. + +`test.nix`: + +```nix +x +``` + +```console +nix-repl> let x = 5; in import ./test.nix +error: undefined variable `x' at /home/lethal/test.nix:1:1 +``` + +So how do we pass information to the module? Use functions, like we did with `mul.nix`. A more complex example: + +`test.nix`: + +```nix +{ a, b ? 3, trueMsg ? "yes", falseMsg ? "no" }: +if a > b + then builtins.trace trueMsg true + else builtins.trace falseMsg false +``` + +```console +nix-repl> import ./test.nix { a = 5; trueMsg = "ok"; } +trace: ok +true +``` + +Explaining: + +- In `test.nix` we return a function. It accepts a set, with default attributes `b`, `trueMsg` and `falseMsg`. + +- `builtins.trace` is a [built-in function](https://nixos.org/manual/nix/stable/expressions/builtins.html) that takes two arguments. The first is the message to display, the second is the value to return. It's usually used for debugging purposes. + +- Then we import `test.nix`, and call the function with that set. + +So when is the message shown? Only when it needs to be evaluated. + +## Next pill + +...we will finally write our first derivation. diff --git a/pills/05-functions-and-imports.xml b/pills/05-functions-and-imports.xml deleted file mode 100644 index 3a5639c..0000000 --- a/pills/05-functions-and-imports.xml +++ /dev/null @@ -1,339 +0,0 @@ - - -Functions and Imports - - - Welcome to the fifth Nix pill. In the previous fourth pill we touched the Nix language - for a moment. We introduced basic types and values of the Nix language, and - basic expressions such as - if, with and - let. I invite you to re-read about these expressions and play - with them in the repl. - - - - Functions help to build reusable components in a big repository like - nixpkgs. The Nix - manual has a great explanation of - functions. Let's go: pill on one hand, Nix manual on the other hand. - - - - I remind you how to enter the Nix environment: source - ~/.nix-profile/etc/profile.d/nix.sh - - -
- Nameless and single parameter - - - Functions are anonymous (lambdas), and only have a single parameter. The - syntax is extremely simple. Type the parameter name, then ":", - then the body of the function. - - - - - - So here we defined a function that takes a parameter - x, and returns x*2. The problem is that we cannot - use it in any way, because it's unnamed... joke! - - - - We can store functions in variables. - - - - - - As usual, please ignore the special syntax for assignments inside nix repl. - So, we defined a function x: x*2 that takes one parameter - x, and returns - x*2. This function is then assigned to the variable - double. Finally we did our first function call: double - 3. - - - - Big note: it's not like many other - programming languages where you write - double(3). It really is double 3. - - - - In summary: to call a function, name the variable, then space, then the - argument. Nothing else to say, it's as easy as that. - -
- -
- More than one parameter - - - How do we create a function that accepts more than one parameter? For people - not used to functional programming, this may take a while to grasp. Let's do - it step by step. - - - - - - We defined a function that takes the parameter a, the body - returns another function. This other function takes a parameter - b and returns a*b. Therefore, calling mul - 3 returns this kind of function: b: 3*b. In turn, we - call the returned function with 4, and get the expected result. - - - - You don't have to use parentheses at all, Nix has sane priorities when - parsing the code: - - - - - - Much more readable, you don't even notice that functions only receive one - argument. Since the argument is separated by a space, to pass more complex - expressions you need parentheses. In other common languages you would write - mul(6+7, 8+9). - - - - Given that functions have only one parameter, it is straightforward to use - partial application: - - - - - - We stored the function returned by mul 3 into a variable foo, - then reused it. - -
- -
- Argument set - - - Now this is a very cool feature of Nix. It is possible to pattern match over - a set in the parameter. We write an alternative version of mul = a: b: - a*b first by using a set as argument, then using pattern matching. - - - - - - In the first case we defined a function that accepts a single parameter. We - then access attributes a and - b from the given set. Note how the parentheses-less syntax for - function calls is very elegant in this case, instead of doing mul({ - a=3; b=4; }) in other languages. - - - - In the second case we defined an argument set. It's like defining a set, - except without values. We require that the passed set contains the keys - a and b. Then we can use those a and - b in the function body directly. - - - - - - Only a set with exactly the attributes required by the function is accepted, - nothing more, nothing less. - -
- -
- Default and variadic attributes - - - It is possible to specify default values - of attributes in the argument set: - - - - - - Also you can allow passing more attributes (variadic) than the expected ones: - - - - - - However, in the function body you cannot access the "c" attribute. The - solution is to give a name to the given set with the @-pattern: - - - - - - That's it, you give a name to the whole parameter with name@ before the set - pattern. - - - - Advantages of using argument sets: - - - - - - Named unordered arguments: you don't have to remember the order of the - arguments. - - - - - You can pass sets, that adds a whole new layer of flexibility and - convenience. - - - - - - Disadvantages: - - - - - - Partial application does not work with argument sets. You have to - specify the whole attribute set, not part of it. - - - - - - You may find similarities with Python - **kwargs. - -
- -
- Imports - - - The import function is built-in and provides a way to parse a - .nix file. The natural approach is to define each - component in a .nix file, then compose by importing - these files. - - - - Let's start with the bare metal. - - - - a.nix: - - - - - - b.nix: - - - - - - mul.nix: - - - - - - - - Yes it's really that simple. You import a file, and it gets parsed as an - expression. Note that the scope of the imported file does not inherit the - scope of the importer. - - - - test.nix: - - - - - - - - So how do we pass information to the module? Use functions, like we did with - mul.nix. A more complex example: - - - - test.nix: - - - - - - - - - Explaining: - - - - - - In test.nix we return a function. It accepts a set, - with default attributes - b, trueMsg and - falseMsg. - - - - - builtins.trace is a built-in - function that takes two arguments. The first is the message to - display, the second is the value to return. It's usually used for - debugging purposes. - - - - - Then we import test.nix, and call the function with - that set. - - - - - - So when is the message shown? Only when it needs to be evaluated. - -
- -
- Next pill - - - ...we will finally write our first derivation. - -
- -
diff --git a/pills/05/a-nix.txt b/pills/05/a-nix.txt deleted file mode 100644 index e440e5c..0000000 --- a/pills/05/a-nix.txt +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/pills/05/anon-function.txt b/pills/05/anon-function.txt deleted file mode 100644 index 21cadb5..0000000 --- a/pills/05/anon-function.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> x: x*2 -«lambda» \ No newline at end of file diff --git a/pills/05/argument-set-error.txt b/pills/05/argument-set-error.txt deleted file mode 100644 index a5a4802..0000000 --- a/pills/05/argument-set-error.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> mul = { a, b }: a*b -nix-repl> mul { a = 3; b = 4; c = 6; } -error: anonymous function at (string):1:2 called with unexpected argument `c', at (string):1:1 -nix-repl> mul { a = 3; } -error: anonymous function at (string):1:2 called without required argument `b', at (string):1:1 \ No newline at end of file diff --git a/pills/05/b-nix.txt b/pills/05/b-nix.txt deleted file mode 100644 index bf0d87a..0000000 --- a/pills/05/b-nix.txt +++ /dev/null @@ -1 +0,0 @@ -4 \ No newline at end of file diff --git a/pills/05/default-values.txt b/pills/05/default-values.txt deleted file mode 100644 index 6613ddd..0000000 --- a/pills/05/default-values.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> mul = { a, b ? 2 }: a*b -nix-repl> mul { a = 3; } -6 -nix-repl> mul { a = 3; b = 4; } -12 \ No newline at end of file diff --git a/pills/05/import.txt b/pills/05/import.txt deleted file mode 100644 index 1defa99..0000000 --- a/pills/05/import.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> a = import ./a.nix -nix-repl> b = import ./b.nix -nix-repl> mul = import ./mul.nix -nix-repl> mul a b -12 \ No newline at end of file diff --git a/pills/05/mul-nix.txt b/pills/05/mul-nix.txt deleted file mode 100644 index 5c42a1b..0000000 --- a/pills/05/mul-nix.txt +++ /dev/null @@ -1 +0,0 @@ -a: b: a*b \ No newline at end of file diff --git a/pills/05/multi-argument-function.txt b/pills/05/multi-argument-function.txt deleted file mode 100644 index 8bd584d..0000000 --- a/pills/05/multi-argument-function.txt +++ /dev/null @@ -1,7 +0,0 @@ -nix-repl> mul = a: (b: a*b) -nix-repl> mul -«lambda» -nix-repl> mul 3 -«lambda» -nix-repl> (mul 3) 4 -12 \ No newline at end of file diff --git a/pills/05/named-function.txt b/pills/05/named-function.txt deleted file mode 100644 index 3d61774..0000000 --- a/pills/05/named-function.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> double = x: x*2 -nix-repl> double -«lambda» -nix-repl> double 3 -6 \ No newline at end of file diff --git a/pills/05/named-set-argument.txt b/pills/05/named-set-argument.txt deleted file mode 100644 index 1ed840b..0000000 --- a/pills/05/named-set-argument.txt +++ /dev/null @@ -1,3 +0,0 @@ -nix-repl> mul = s@{ a, b, ... }: a*b*s.c -nix-repl> mul { a = 3; b = 4; c = 2; } -24 \ No newline at end of file diff --git a/pills/05/no-parenthesis.txt b/pills/05/no-parenthesis.txt deleted file mode 100644 index 36d12ff..0000000 --- a/pills/05/no-parenthesis.txt +++ /dev/null @@ -1,9 +0,0 @@ -nix-repl> mul = a: b: a*b -nix-repl> mul -«lambda» -nix-repl> mul 3 -«lambda» -nix-repl> mul 3 4 -12 -nix-repl> mul (6+7) (8+9) -221 \ No newline at end of file diff --git a/pills/05/partial-application.txt b/pills/05/partial-application.txt deleted file mode 100644 index eb925b0..0000000 --- a/pills/05/partial-application.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> foo = mul 3 -nix-repl> foo 4 -12 -nix-repl> foo 5 -15 \ No newline at end of file diff --git a/pills/05/set-argument.txt b/pills/05/set-argument.txt deleted file mode 100644 index 792b6f5..0000000 --- a/pills/05/set-argument.txt +++ /dev/null @@ -1,6 +0,0 @@ -nix-repl> mul = s: s.a*s.b -nix-repl> mul { a = 3; b = 4; } -12 -nix-repl> mul = { a, b }: a*b -nix-repl> mul { a = 3; b = 4; } -12 \ No newline at end of file diff --git a/pills/05/test-import-2.txt b/pills/05/test-import-2.txt deleted file mode 100644 index b5fadc4..0000000 --- a/pills/05/test-import-2.txt +++ /dev/null @@ -1,3 +0,0 @@ -nix-repl> import ./test.nix { a = 5; trueMsg = "ok"; } -trace: ok -true diff --git a/pills/05/test-import.txt b/pills/05/test-import.txt deleted file mode 100644 index 02b4bd7..0000000 --- a/pills/05/test-import.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> let x = 5; in import ./test.nix -error: undefined variable `x' at /home/lethal/test.nix:1:1 \ No newline at end of file diff --git a/pills/05/test-nix-2.txt b/pills/05/test-nix-2.txt deleted file mode 100644 index 4738f7a..0000000 --- a/pills/05/test-nix-2.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ a, b ? 3, trueMsg ? "yes", falseMsg ? "no" }: -if a > b - then builtins.trace trueMsg true - else builtins.trace falseMsg false diff --git a/pills/05/test-nix.txt b/pills/05/test-nix.txt deleted file mode 100644 index c1b0730..0000000 --- a/pills/05/test-nix.txt +++ /dev/null @@ -1 +0,0 @@ -x \ No newline at end of file diff --git a/pills/05/variadic-arguments.txt b/pills/05/variadic-arguments.txt deleted file mode 100644 index 05fdfec..0000000 --- a/pills/05/variadic-arguments.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> mul = { a, b, ... }: a*b -nix-repl> mul { a = 3; b = 4; c = 2; } \ No newline at end of file diff --git a/pills/06-our-first-derivation.md b/pills/06-our-first-derivation.md new file mode 100644 index 0000000..811cdfd --- /dev/null +++ b/pills/06-our-first-derivation.md @@ -0,0 +1,314 @@ +# Our First Derivation + +Welcome to the sixth Nix pill. In the previous [fifth pill](05-functions-and-imports.md) we introduced functions and imports. Functions and imports are very simple concepts that allow for building complex abstractions and composition of modules to build a flexible Nix system. + +In this post we finally arrived to writing a derivation. Derivations are the building blocks of a Nix system, from a file system view point. The Nix language is used to describe such derivations. + +I remind you how to enter the Nix environment: `source ~/.nix-profile/etc/profile.d/nix.sh` + +## The derivation function + +The [derivation built-in function](https://nixos.org/manual/nix/stable/expressions/derivations.html) is used to create derivations. I invite you to read the link in the Nix manual about the derivation built-in. A derivation from a Nix language view point is simply a set, with some attributes. Therefore you can pass the derivation around with variables like anything else. + +That's where the real power comes in. + +The `derivation` function receives a set as its first argument. This set requires at least the following three attributes: + +- name: the name of the derivation. In the nix store the format is hash-name, that's the name. + +- system: is the name of the system in which the derivation can be built. For example, x86_64-linux. + +- builder: is the binary program that builds the derivation. + +First of all, what's the name of our system as seen by nix? + +```console +nix-repl> builtins.currentSystem +"x86_64-linux" +``` + +Let's try to fake the name of the system: + +```console +nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } +nix-repl> d +«derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv» +``` + +Oh oh, what's that? Did it build the derivation? No it didn't, but it **did create the .drv file**. `nix repl` does not build derivations unless you tell it to do so. + +## Digression about .drv files + +What's that `.drv` file? It is the specification of how to build the derivation, without all the Nix language fuzz. + +Before continuing, some analogies with the C language: + +- `.nix` files are like `.c` files. + +- `.drv` files are intermediate files like `.o` files. The `.drv` describes how to build a derivation; it's the bare minimum information. + +- out paths are then the product of the build. + +Both drv paths and out paths are stored in the nix store as you can see. + +What's in that `.drv` file? You can read it, but it's better to pretty print it: + +
+ +Note: If your version of nix doesn't have `nix derivation show`, use `nix show-derivation` instead. + +
+ +```console +$ nix derivation show /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv +{ + "/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv": { + "outputs": { + "out": { + "path": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" + } + }, + "inputSrcs": [], + "inputDrvs": {}, + "platform": "mysystem", + "builder": "mybuilder", + "args": [], + "env": { + "builder": "mybuilder", + "name": "myname", + "out": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname", + "system": "mysystem" + } + } +} +``` + +Ok, we can see there's an out path, but it does not exist yet. We never told Nix to build it, but we know beforehand where the build output will be. Why? + +Think, if Nix ever built the derivation just because we accessed it in Nix, we would have to wait a long time if it was, say, Firefox. That's why Nix let us know the path beforehand and kept evaluating the Nix expressions, but it's still empty because no build was ever made. + +Important: the hash of the out path is based solely on the input derivations in the current version of Nix, not on the contents of the build product. It's possible however to have [content-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage) derivations for e.g. tarballs as we'll see later on. + +Many things are empty in that `.drv`, however I'll write a summary of the [.drv format](http://nixos.org/~eelco/pubs/phd-thesis.pdf) for you: + +1. The output paths (there can be multiple ones). By default nix creates one out path called "out". + +2. The list of input derivations. It's empty because we are not referring to any other derivation. Otherwise, there would be a list of other .drv files. + +3. The system and the builder executable (yes, it's a fake one). + +4. Then a list of environment variables passed to the builder. + +That's it, the minimum necessary information to build our derivation. + +Important note: the environment variables passed to the builder are just those you see in the .drv plus some other Nix related configuration (number of cores, temp dir, ...). The builder will not inherit any variable from your running shell, otherwise builds would suffer from [non-determinism](https://wiki.debian.org/ReproducibleBuilds). + +Back to our fake derivation. + +Let's build our really fake derivation: + +```console +nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } +nix-repl> :b d +[...] +these derivations will be built: + /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv +building path(s) `/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname' +error: a `mysystem' is required to build `/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv', but I am a `x86_64-linux' +``` + +The `:b` is a `nix repl` specific command to build a derivation. You can see more commands with `:?` . So in the output you can see that it takes the `.drv` as information on how to build the derivation. Then it says it's trying to produce our out path. Finally the error we were waiting for: that derivation can't be built on our system. + +We're doing the build inside `nix repl`, but what if we don't want to use `nix repl`? You can **realise** a `.drv` with: + +```console +$ nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv +``` + +You will get the same output as before. + +Let's fix the system attribute: + +```console +nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = builtins.currentSystem; } +nix-repl> :b d +[...] +build error: invalid file name `mybuilder' +``` + +A step forward: of course, that `mybuilder` executable does not really exist. Stop for a moment. + +## What's in a derivation set + +It is useful to start by inspecting the return value from the derivation function. In this case, the returned value is a plain set: + +```console +nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } +nix-repl> builtins.isAttrs d +true +nix-repl> builtins.attrNames d +[ "all" "builder" "drvAttrs" "drvPath" "name" "out" "outPath" "outputName" "system" "type" ] +``` + +You can guess what `builtins.isAttrs` does; it returns true if the argument is a set. While `builtins.attrNames` returns a list of keys of the given set. Some kind of reflection, you might say. + +Start from drvAttrs: + +```console +nix-repl> d.drvAttrs +{ builder = "mybuilder"; name = "myname"; system = "mysystem"; } +``` + +That's basically the input we gave to the derivation function. Also the `d.name`, `d.system` and `d.builder` attributes are exactly the ones we gave as input. + +```console +nix-repl> (d == d.out) +true +``` + +So out is just the derivation itself, it seems weird but the reason is that we only have one output from the derivation. That's also the reason why `d.all` is a singleton. We'll see multiple outputs later. + +The `d.drvPath` is the path of the `.drv` file: `/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv`. + +Something interesting is the `type` attribute. It's `"derivation"`. Nix does add a little of magic to sets with type derivation, but not that much. To help you understand, you can create yourself a set with that type, it's a simple set: + +```console +nix-repl> { type = "derivation"; } +«derivation ???» +``` + +Of course it has no other information, so Nix doesn't know what to say :-) But you get it, the `type = "derivation"` is just a convention for Nix and for us to understand the set is a derivation. + +When writing packages, we are interested in the outputs. The other metadata is needed for Nix to know how to create the drv path and the out path. + +The `outPath` attribute is the build path in the nix store: `/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname`. + +## Referring to other derivations + +Just like dependencies in other package managers, how do we refer to other packages? How do we refer to other derivations in terms of files on the disk? We use the `outPath`. The `outPath` describes the location of the files of that derivation. To make it more convenient, Nix is able to do a conversion from a derivation set to a string. + +```console +nix-repl> d.outPath +"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" +nix-repl> builtins.toString d +"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" +``` + +Nix does the "set to string conversion" as long as there is the `outPath` attribute (much like a toString method in other languages): + +```console +nix-repl> builtins.toString { outPath = "foo"; } +"foo" +nix-repl> builtins.toString { a = "b"; } +error: cannot coerce a set to a string, at (string):1:1 +``` + +Say we want to use binaries from coreutils (ignore the nixpkgs etc.): + +```console +nix-repl> :l +Added 3950 variables. +nix-repl> coreutils +«derivation /nix/store/1zcs1y4n27lqs0gw4v038i303pb89rw6-coreutils-8.21.drv» +nix-repl> builtins.toString coreutils +"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21" +``` + +Apart from the nixpkgs stuff, just think we added to the scope a series of variables. One of them is coreutils. It is the derivation of the coreutils package you all know of from other Linux distributions. It contains basic binaries for GNU/Linux systems (you may have multiple derivations of coreutils in the nix store, no worries): + +```console +$ ls /nix/store/*coreutils*/bin +[...] +``` + +I remind you, inside strings it's possible to interpolate Nix expressions with `${...}`: + +```console +nix-repl> "${d}" +"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" +nix-repl> "${coreutils}" +"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21" +``` + +That's very convenient, because then we could refer to e.g. the bin/true binary like this: + +```console +nix-repl> "${coreutils}/bin/true" +"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21/bin/true" +``` + +## An almost working derivation + +In the previous attempt we used a fake builder, `mybuilder` which obviously does not exist. But we can use for example bin/true, which always exits with 0 (success). + +```console +nix-repl> :l +nix-repl> d = derivation { name = "myname"; builder = "${coreutils}/bin/true"; system = builtins.currentSystem; } +nix-repl> :b d +[...] +builder for `/nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv' failed to produce output path `/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname' +``` + +Another step forward, it executed the builder (bin/true), but the builder did not create the out path of course, it just exited with 0. + +Obvious note: every time we change the derivation, a new hash is created. + +Let's examine the new `.drv` now that we referred to another derivation: + +```console +$ nix derivation show /nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv +{ + "/nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv": { + "outputs": { + "out": { + "path": "/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname" + } + }, + "inputSrcs": [], + "inputDrvs": { + "/nix/store/hixdnzz2wp75x1jy65cysq06yl74vx7q-coreutils-8.29.drv": [ + "out" + ] + }, + "platform": "x86_64-linux", + "builder": "/nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29/bin/true", + "args": [], + "env": { + "builder": "/nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29/bin/true", + "name": "myname", + "out": "/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname", + "system": "x86_64-linux" + } + } +} +``` + +Aha! Nix added a dependency to our myname.drv, it's the coreutils.drv. Before doing our build, Nix should build the coreutils.drv. But since coreutils is already in our nix store, no build is needed, it's already there with out path `/nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29`. + +## When is the derivation built + +Nix does not build derivations **during evaluation** of Nix expressions. In fact, that's why we have to do ":b drv" in `nix repl`, or use nix-store -r in the first place. + +An important separation is made in Nix: + +- **Instantiate/Evaluation time**: the Nix expression is parsed, interpreted and finally returns a derivation set. During evaluation, you can refer to other derivations because Nix will create .drv files and we will know out paths beforehand. This is achieved with [nix-instantiate](https://nixos.org/manual/nix/stable/command-ref/nix-instantiate.html). + +- **Realise/Build time**: the .drv from the derivation set is built, first building .drv inputs (build dependencies). This is achieved with [nix-store -r](https://nixos.org/manual/nix/stable/command-ref/nix-store.html#operation---realise). + +Think of it as of compile time and link time like with C/C++ projects. You first compile all source files to object files. Then link object files in a single executable. + +In Nix, first the Nix expression (usually in a .nix file) is compiled to .drv, then each .drv is built and the product is installed in the relative out paths. + +## Conclusion + +Is it that complicated to create a package for Nix? No, it's not. + +We're walking through the fundamentals of Nix derivations, to understand how they work, how they are represented. Packaging in Nix is certainly easier than that, but we're not there yet in this post. More Nix pills are needed. + +With the derivation function we provide a set of information on how to build a package, and we get back the information about where the package was built. Nix converts a set to a string when there's an `outPath`; that's very convenient. With that, it's easy to refer to other derivations. + +When Nix builds a derivation, it first creates a .drv file from a derivation expression, and uses it to build the output. It does so recursively for all the dependencies (inputs). It "executes" the .drv files like a machine. Not much magic after all. + +## Next pill + +...we will finally write our first **working** derivation. Yes, this post is about "our first derivation", but I never said it was a working one ;) diff --git a/pills/06-our-first-derivation.xml b/pills/06-our-first-derivation.xml deleted file mode 100644 index cad3f14..0000000 --- a/pills/06-our-first-derivation.xml +++ /dev/null @@ -1,499 +0,0 @@ - - -Our First Derivation - - - Welcome to the sixth Nix pill. In the previous fifth pill we introduced functions - and imports. Functions and imports are very simple concepts that allow for - building complex abstractions and composition of modules to build a flexible - Nix system. - - - - In this post we finally arrived to writing a derivation. Derivations are the - building blocks of a Nix system, from a file system view point. The Nix - language is used to describe such derivations. - - - - I remind you how to enter the Nix environment: source - ~/.nix-profile/etc/profile.d/nix.sh - - - -
- The derivation function - - - The derivation - built-in function is used to create derivations. I invite you to read - the link in the Nix manual about the derivation built-in. A derivation from - a Nix language view point is simply a set, with some attributes. Therefore - you can pass the derivation around with variables like anything else. - - - - That's where the real power comes in. - - - - The derivation function receives a set as its first argument. This - set requires at least the following three attributes: - - - - - - name: the name of the derivation. In the nix store the format is - hash-name, that's the name. - - - - - system: is the name of the system in which the derivation can be built. - For example, x86_64-linux. - - - - - builder: is the binary program that builds the derivation. - - - - - - First of all, what's the name of our system as seen by nix? - - - - - - Let's try to fake the name of the system: - - - - - - Oh oh, what's that? Did it build the derivation? No it didn't, but it - did create the .drv file. nix repl does - not build derivations unless you tell it to do so. - - -
- -
- Digression about .drv files - - - What's that .drv file? It is the specification of how - to build the derivation, without all the Nix language fuzz. - - - - Before continuing, some analogies with the C language: - - - - - - .nix files are like .c files. - - - - - .drv files are intermediate files like - .o files. The .drv describes - how to build a derivation; it's the bare minimum information. - - - - - out paths are then the product of the build. - - - - - - Both drv paths and out paths are stored in the nix store as you can see. - - - - What's in that .drv file? You can read it, but it's - better to pretty print it: - - - - If your version of nix doesn't have nix derivation show, use nix show-derivation instead. - - - - - - Ok, we can see there's an out path, but it does not exist yet. We never told - Nix to build it, but we know beforehand where the build output will be. Why? - - - - Think, if Nix ever built the derivation just because we accessed it in Nix, - we would have to wait a long time if it was, say, Firefox. That's why Nix - let us know the path beforehand and kept evaluating the Nix expressions, but - it's still empty because no build was ever made. - - - - Important: the hash of the out path is - based solely on the input derivations in the current version of Nix, not on - the contents of the build product. It's possible however to have content-addressable - derivations for e.g. tarballs as we'll see later on. - - - - Many things are empty in that .drv, however I'll write a - summary of the .drv format - for you: - - - - - - The output paths (there can be multiple ones). By default nix creates one - out path called "out". - - - - - The list of input derivations. It's empty because we are not referring - to any other derivation. Otherwise, there would be a list of other .drv - files. - - - - - The system and the builder executable (yes, it's a fake one). - - - - - Then a list of environment variables passed to the builder. - - - - - - That's it, the minimum necessary information to build our derivation. - - - - Important note: the environment - variables passed to the builder are just those you see in the .drv plus some - other Nix related configuration (number of cores, temp dir, ...). The - builder will not inherit any variable from your running shell, otherwise - builds would suffer from non-determinism. - - - - Back to our fake derivation. - - - - Let's build our really fake derivation: - - - - - - The :b is a nix repl specific command to build a derivation. - You can see more commands with :? . So in the output you can - see that it takes the .drv as information on how to - build the derivation. Then it says it's trying to produce our out path. - Finally the error we were waiting for: that derivation can't be built on our - system. - - - - We're doing the build inside nix repl, but what if we don't want to use - nix repl? You can realise a - .drv with: - - - - - - You will get the same output as before. - - - - Let's fix the system attribute: - - - - - - A step forward: of course, that mybuilder executable does not - really exist. Stop for a moment. - - - -
-
- What's in a derivation set - - - It is useful to start by inspecting the return value from the derivation function. - In this case, the returned value is a plain set: - - - - - - You can guess what builtins.isAttrs does; it returns true if - the argument is a set. While builtins.attrNames returns a list - of keys of the given set. Some kind of reflection, you might say. - - - - Start from drvAttrs: - - - - - - That's basically the input we gave to the derivation function. Also the - d.name, d.system and d.builder - attributes are exactly the ones we gave as input. - - - - - - So out is just the derivation itself, it seems weird but the reason is that - we only have one output from the derivation. That's also the reason why - d.all is a singleton. We'll see multiple outputs later. - - - - The d.drvPath is the path of the .drv - file: /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv. - - - - Something interesting is the type attribute. It's - "derivation". Nix does add a little of magic to sets with type - derivation, but not that much. To help you understand, you can create - yourself a set with that type, it's a simple set: - - - - - - Of course it has no other information, so Nix doesn't know what to say :-) - But you get it, the type = "derivation" is just a convention - for Nix and for us to understand the set is a derivation. - - - - When writing packages, we are interested in the outputs. The other metadata - is needed for Nix to know how to create the drv path and the out path. - - - - The outPath attribute is the build path in the nix store: - /nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname. - - - -
-
- Referring to other derivations - - - Just like dependencies in other package managers, how do we refer to other - packages? How do we refer to other derivations in terms of files on the - disk? We use the outPath. The outPath describes - the location of the files of that derivation. To make it more convenient, - Nix is able to do a conversion from a derivation set to a string. - - - - - - Nix does the "set to string conversion" as long as there is the - outPath attribute (much like a toString method in other - languages): - - - - - - Say we want to use binaries from coreutils (ignore the nixpkgs etc.): - - - - - - Apart from the nixpkgs stuff, just think we added to the scope a series of - variables. One of them is coreutils. It is the derivation of the coreutils - package you all know of from other Linux distributions. It contains basic - binaries for GNU/Linux systems (you may have multiple derivations of - coreutils in the nix store, no worries): - - - - - - - I remind you, inside strings it's possible to interpolate Nix expressions - with ${...}: - - - - - - - That's very convenient, because then we could refer to e.g. the bin/true - binary like this: - - - - - -
-
- An almost working derivation - - - In the previous attempt we used a fake builder, mybuilder which - obviously does not exist. But we can use for example bin/true, which always - exits with 0 (success). - - - - - - Another step forward, it executed the builder (bin/true), but the builder - did not create the out path of course, it just exited with 0. - - - - - Obvious note: every time we change the - derivation, a new hash is created. - - - - Let's examine the new .drv now that we referred to - another derivation: - - - - - - Aha! Nix added a dependency to our myname.drv, it's the coreutils.drv. - Before doing our build, Nix should build the coreutils.drv. But since - coreutils is already in our nix store, no build is needed, it's already - there with out path - /nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29. - - -
-
- When is the derivation built - - - Nix does not build derivations during - evaluation of Nix expressions. In fact, that's why we have to do - ":b drv" in nix repl, or use nix-store -r in the first place. - - - - An important separation is made in Nix: - - - - - - Instantiate/Evaluation time: the Nix - expression is parsed, interpreted and finally returns a derivation set. - During evaluation, you can refer to other derivations because Nix will - create .drv files and we will know out paths beforehand. This is - achieved with nix-instantiate. - - - - - Realise/Build time: the .drv from the - derivation set is built, first building .drv inputs (build - dependencies). This is achieved with nix-store - -r. - - - - - - Think of it as of compile time and link time like with C/C++ projects. You - first compile all source files to object files. Then link object files in a - single executable. - - - - In Nix, first the Nix expression (usually in a .nix file) is compiled to - .drv, then each .drv is built and the product is installed in the relative - out paths. - - -
-
- Conclusion - - - Is it that complicated to create a package for Nix? No, it's not. - - - - We're walking through the fundamentals of Nix derivations, to understand how - they work, how they are represented. Packaging in Nix is certainly easier - than that, but we're not there yet in this post. More Nix pills are needed. - - - - With the derivation function we provide a set of information on how to build - a package, and we get back the information about where the package was - built. Nix converts a set to a string when there's an outPath; - that's very convenient. With that, it's easy to refer to other derivations. - - - - When Nix builds a derivation, it first creates a .drv file from a derivation - expression, and uses it to build the output. It does so recursively for all - the dependencies (inputs). It "executes" the .drv files like a machine. Not - much magic after all. - - - -
-
- Next pill - - - ...we will finally write our first working derivation. Yes, this post is about "our - first derivation", but I never said it was a working one ;) - - -
- -
diff --git a/pills/06/build-derivation.txt b/pills/06/build-derivation.txt deleted file mode 100644 index f6c9c9c..0000000 --- a/pills/06/build-derivation.txt +++ /dev/null @@ -1,7 +0,0 @@ -nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } -nix-repl> :b d -[...] -these derivations will be built: - /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv -building path(s) `/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname' -error: a `mysystem' is required to build `/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv', but I am a `x86_64-linux' \ No newline at end of file diff --git a/pills/06/check-drvattrs.txt b/pills/06/check-drvattrs.txt deleted file mode 100644 index cbc746d..0000000 --- a/pills/06/check-drvattrs.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> (d == d.out) -true \ No newline at end of file diff --git a/pills/06/coreutils.txt b/pills/06/coreutils.txt deleted file mode 100644 index 50990a5..0000000 --- a/pills/06/coreutils.txt +++ /dev/null @@ -1,6 +0,0 @@ -nix-repl> :l -Added 3950 variables. -nix-repl> coreutils -«derivation /nix/store/1zcs1y4n27lqs0gw4v038i303pb89rw6-coreutils-8.21.drv» -nix-repl> builtins.toString coreutils -"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21" \ No newline at end of file diff --git a/pills/06/current-system.txt b/pills/06/current-system.txt deleted file mode 100644 index 18ebe18..0000000 --- a/pills/06/current-system.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> builtins.currentSystem -"x86_64-linux" \ No newline at end of file diff --git a/pills/06/drvattrs.txt b/pills/06/drvattrs.txt deleted file mode 100644 index b9fb564..0000000 --- a/pills/06/drvattrs.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> d.drvAttrs -{ builder = "mybuilder"; name = "myname"; system = "mysystem"; } \ No newline at end of file diff --git a/pills/06/examine-build.xml b/pills/06/examine-build.xml deleted file mode 100644 index 3d88ea0..0000000 --- a/pills/06/examine-build.xml +++ /dev/null @@ -1,25 +0,0 @@ -$ nix derivation show /nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv -{ - "/nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv": { - "outputs": { - "out": { - "path": "/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname" - } - }, - "inputSrcs": [], - "inputDrvs": { - "/nix/store/hixdnzz2wp75x1jy65cysq06yl74vx7q-coreutils-8.29.drv": [ - "out" - ] - }, - "platform": "x86_64-linux", - "builder": "/nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29/bin/true", - "args": [], - "env": { - "builder": "/nix/store/qrxs7sabhqcr3j9ai0j0cp58zfnny0jz-coreutils-8.29/bin/true", - "name": "myname", - "out": "/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname", - "system": "x86_64-linux" - } - } -} diff --git a/pills/06/fake-system.txt b/pills/06/fake-system.txt deleted file mode 100644 index 66b8c07..0000000 --- a/pills/06/fake-system.txt +++ /dev/null @@ -1,3 +0,0 @@ -nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } -nix-repl> d -«derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv» \ No newline at end of file diff --git a/pills/06/fix-attribute.txt b/pills/06/fix-attribute.txt deleted file mode 100644 index 2e37796..0000000 --- a/pills/06/fix-attribute.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = builtins.currentSystem; } -nix-repl> :b d -[...] -build error: invalid file name `mybuilder' \ No newline at end of file diff --git a/pills/06/inspect-values.txt b/pills/06/inspect-values.txt deleted file mode 100644 index 167c6ab..0000000 --- a/pills/06/inspect-values.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } -nix-repl> builtins.isAttrs d -true -nix-repl> builtins.attrNames d -[ "all" "builder" "drvAttrs" "drvPath" "name" "out" "outPath" "outputName" "system" "type" ] \ No newline at end of file diff --git a/pills/06/interpolate.txt b/pills/06/interpolate.txt deleted file mode 100644 index 1925d00..0000000 --- a/pills/06/interpolate.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> "${d}" -"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" -nix-repl> "${coreutils}" -"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21" \ No newline at end of file diff --git a/pills/06/list-coreutils.txt b/pills/06/list-coreutils.txt deleted file mode 100644 index b8123c9..0000000 --- a/pills/06/list-coreutils.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ ls /nix/store/*coreutils*/bin -[...] \ No newline at end of file diff --git a/pills/06/outpath.txt b/pills/06/outpath.txt deleted file mode 100644 index f5d8b86..0000000 --- a/pills/06/outpath.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> d.outPath -"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" -nix-repl> builtins.toString d -"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" \ No newline at end of file diff --git a/pills/06/realise-derivation.txt b/pills/06/realise-derivation.txt deleted file mode 100644 index 55582e8..0000000 --- a/pills/06/realise-derivation.txt +++ /dev/null @@ -1 +0,0 @@ -$ nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv \ No newline at end of file diff --git a/pills/06/reference.txt b/pills/06/reference.txt deleted file mode 100644 index 20df787..0000000 --- a/pills/06/reference.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> "${coreutils}/bin/true" -"/nix/store/8w4cbiy7wqvaqsnsnb3zvabq1cp2zhyz-coreutils-8.21/bin/true" \ No newline at end of file diff --git a/pills/06/show-derivation.xml b/pills/06/show-derivation.xml deleted file mode 100644 index 31245a8..0000000 --- a/pills/06/show-derivation.xml +++ /dev/null @@ -1,21 +0,0 @@ -$ nix derivation show /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv -{ - "/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv": { - "outputs": { - "out": { - "path": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname" - } - }, - "inputSrcs": [], - "inputDrvs": {}, - "platform": "mysystem", - "builder": "mybuilder", - "args": [], - "env": { - "builder": "mybuilder", - "name": "myname", - "out": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname", - "system": "mysystem" - } - } -} diff --git a/pills/06/test-build.txt b/pills/06/test-build.txt deleted file mode 100644 index 0468ce0..0000000 --- a/pills/06/test-build.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> :l -nix-repl> d = derivation { name = "myname"; builder = "${coreutils}/bin/true"; system = builtins.currentSystem; } -nix-repl> :b d -[...] -builder for `/nix/store/qyfrcd53wmc0v22ymhhd5r6sz5xmdc8a-myname.drv' failed to produce output path `/nix/store/ly2k1vswbfmswr33hw0kf0ccilrpisnk-myname' diff --git a/pills/06/tostring.txt b/pills/06/tostring.txt deleted file mode 100644 index 6f45ee8..0000000 --- a/pills/06/tostring.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> builtins.toString { outPath = "foo"; } -"foo" -nix-repl> builtins.toString { a = "b"; } -error: cannot coerce a set to a string, at (string):1:1 \ No newline at end of file diff --git a/pills/06/troll.png b/pills/06/troll.png deleted file mode 100644 index 6f4b82e2eea95d9d687c351654a08aadf026cda1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70746 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 E1J0niZvX%Q diff --git a/pills/06/troll.svg b/pills/06/troll.svg deleted file mode 100644 index e3b3f77..0000000 --- a/pills/06/troll.svg +++ /dev/null @@ -1,16 +0,0 @@ - - .str0 {stroke:black;stroke-width:0.238063} - .fil0 {fill:black} - .fil2 {fill:#050707} - .fil3 {fill:#881846} - .fil5 {fill:#F6BCD6} - .fil1 {fill:#F9DD56} - .fil4 {fill:#FDFCFD} - - - - - Layer 1 - - image/svg+xmlOpenclipartTrollface2011-04-03T12:59:21http://encyclopediadramatica.com/Trollfacehttps://openclipart.org/detail/130795/trollface-by-ronesronesfacememetrolltrollface - \ No newline at end of file diff --git a/pills/06/type-derivation.txt b/pills/06/type-derivation.txt deleted file mode 100644 index 17b05a4..0000000 --- a/pills/06/type-derivation.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> { type = "derivation"; } -«derivation ???» \ No newline at end of file diff --git a/pills/07-working-derivation.md b/pills/07-working-derivation.md new file mode 100644 index 0000000..86f79ec --- /dev/null +++ b/pills/07-working-derivation.md @@ -0,0 +1,243 @@ +# Working Derivation + +## Introduction + +Welcome to the seventh nix pill. In the previous [sixth pill](06-our-first-derivation.md) we introduced the notion of derivation in the Nix language --- how to define a raw derivation and how to (try to) build it. + +In this post we continue along the path, by creating a derivation that actually builds something. Then, we try to package a real program: we compile a simple C file and create a derivation out of it, given a blessed toolchain. + +I remind you how to enter the Nix environment: `source ~/.nix-profile/etc/profile.d/nix.sh` + +## Using a script as a builder + +What's the easiest way to run a sequence of commands for building something? A bash script. We write a custom bash script, and we want it to be our builder. Given a `builder.sh`, we want the derivation to run `bash builder.sh`. + +We don't use hash bangs in `builder.sh`, because at the time we are writing it we do not know the path to bash in the nix store. Yes, even bash is in the nix store, everything is there. + +We don't even use /usr/bin/env, because then we lose the cool stateless property of Nix. Not to mention that `PATH` gets cleared when building, so it wouldn't find bash anyway. + +In summary, we want the builder to be bash, and pass it an argument, `builder.sh`. Turns out the `derivation` function accepts an optional `args` attribute which is used to pass arguments to the builder executable. + +First of all, let's write our `builder.sh` in the current directory: + +```sh +declare -xp +echo foo > $out +``` + +The command `declare -xp` lists exported variables (`declare` is a builtin bash function). As we covered in the previous pill, Nix computes the output path of the derivation. The resulting `.drv` file contains a list of environment variables passed to the builder. One of these is `$out`. + +What we have to do is create something in the path `$out`, be it a file or a directory. In this case we are creating a file. + +In addition, we print out the environment variables during the build process. We cannot use env for this, because env is part of coreutils and we don't have a dependency to it yet. We only have bash for now. + +Like for coreutils in the previous pill, we get a blessed bash for free from our magic nixpkgs stuff: + +```console +nix-repl> :l +Added 3950 variables. +nix-repl> "${bash}" +"/nix/store/ihmkc7z2wqk3bbipfnlh0yjrlfkkgnv6-bash-4.2-p45" +``` + +So with the usual trick, we can refer to bin/bash and create our derivation: + +```console +nix-repl> d = derivation { name = "foo"; builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; system = builtins.currentSystem; } +nix-repl> :b d +[1 built, 0.0 MiB DL] + +this derivation produced the following outputs: + out -> /nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo +``` + +We did it! The contents of `/nix/store/w024zci0x1hh1wj6gjq0jagkc1sgrf5r-foo` is really foo. We've built our first derivation. + +Note that we used `./builder.sh` and not `"./builder.sh"`. This way, it is parsed as a path, and Nix performs some magic which we will cover later. Try using the string version and you will find that it cannot find `builder.sh`. This is because it tries to find it relative to the temporary build directory. + +## The builder environment + +We can use `nix-store --read-log` to see the logs our builder produced: + +```console +$ nix-store --read-log /nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo +declare -x HOME="/homeless-shelter" +declare -x NIX_BUILD_CORES="4" +declare -x NIX_BUILD_TOP="/tmp/nix-build-foo.drv-0" +declare -x NIX_LOG_FD="2" +declare -x NIX_STORE="/nix/store" +declare -x OLDPWD +declare -x PATH="/path-not-set" +declare -x PWD="/tmp/nix-build-foo.drv-0" +declare -x SHLVL="1" +declare -x TEMP="/tmp/nix-build-foo.drv-0" +declare -x TEMPDIR="/tmp/nix-build-foo.drv-0" +declare -x TMP="/tmp/nix-build-foo.drv-0" +declare -x TMPDIR="/tmp/nix-build-foo.drv-0" +declare -x builder="/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash" +declare -x name="foo" +declare -x out="/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo" +declare -x system="x86_64-linux" +``` + +Let's inspect those environment variables printed during the build process. + +- `$HOME` is not your home directory, and `/homeless-shelter` doesn't exist at all. We force packages not to depend on `$HOME` during the build process. + +- `$PATH` plays the same game as `$HOME` + +- `$NIX_BUILD_CORES` and `$NIX_STORE` are [nix configuration options](https://nixos.org/manual/nix/stable/command-ref/conf-file.html) + +- `$PWD` and `$TMP` clearly show that nix created a temporary build directory + +- Then `$builder`, `$name`, `$out`, and `$system` are variables set due to the .drv file's contents. + +And that's how we were able to use `$out` in our derivation and put stuff in it. It's like Nix reserved a slot in the nix store for us, and we must fill it. + +In terms of autotools, `$out` will be the `--prefix` path. Yes, not the make `DESTDIR`, but the `--prefix`. That's the essence of stateless packaging. You don't install the package in a global common path under `/`, you install it in a local isolated path under your nix store slot. + +## The .drv contents + +We added something else to the derivation this time: the args attribute. Let's see how this changed the .drv compared to the previous pill: + +```console +$ nix derivation show /nix/store/i76pr1cz0za3i9r6xq518bqqvd2raspw-foo.drv +{ + "/nix/store/i76pr1cz0za3i9r6xq518bqqvd2raspw-foo.drv": { + "outputs": { + "out": { + "path": "/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo" + } + }, + "inputSrcs": [ + "/nix/store/lb0n38r2b20r8rl1k45a7s4pj6ny22f7-builder.sh" + ], + "inputDrvs": { + "/nix/store/hcgwbx42mcxr7ksnv0i1fg7kw6jvxshb-bash-4.4-p19.drv": [ + "out" + ] + }, + "platform": "x86_64-linux", + "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", + "args": [ + "/nix/store/lb0n38r2b20r8rl1k45a7s4pj6ny22f7-builder.sh" + ], + "env": { + "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", + "name": "foo", + "out": "/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo", + "system": "x86_64-linux" + } + } +} +``` + +Much like the usual .drv, except that there's a list of arguments in there passed to the builder (bash) with `builder.sh`... In the nix store..? Nix automatically copies files or directories needed for the build into the store to ensure that they are not changed during the build process and that the deployment is stateless and independent of the building machine. `builder.sh` is not only in the arguments passed to the builder, it's also in the input derivations. + +Given that `builder.sh` is a plain file, it has no .drv associated with it. The store path is computed based on the filename and on the hash of its contents. Store paths are covered in detail in [a later pill](18-nix-store-paths.md). + +## Packaging a simple C program + +Start off by writing a simple C program called `simple.c`: + +```c +void main() { + puts("Simple!"); +} +``` + +And its `simple_builder.sh`: + +```sh +export PATH="$coreutils/bin:$gcc/bin" +mkdir $out +gcc -o $out/simple $src +``` + +Don't worry too much about where those variables come from yet; let's write the derivation and build it: + +```console +nix-repl> :l +nix-repl> simple = derivation { name = "simple"; builder = "${bash}/bin/bash"; args = [ ./simple_builder.sh ]; gcc = gcc; coreutils = coreutils; src = ./simple.c; system = builtins.currentSystem; } +nix-repl> :b simple +this derivation produced the following outputs: + + out -> /nix/store/ni66p4jfqksbmsl616llx3fbs1d232d4-simple +``` + +Now you can run `/nix/store/ni66p4jfqksbmsl616llx3fbs1d232d4-simple/simple` in your shell. + +## Explanation + +We added two new attributes to the derivation call, `gcc` and `coreutils`. In `gcc = gcc;`, the name on the left is the name in the derivation set, and the name on the right refers to the gcc derivation from nixpkgs. The same applies for coreutils. + +We also added the `src` attribute, nothing magical --- it's just a name, to which the path `./simple.c` is assigned. Like `simple-builder.sh`, `simple.c` will be added to the store. + +The trick: every attribute in the set passed to `derivation` will be converted to a string and passed to the builder as an environment variable. This is how the builder gains access to coreutils and gcc: when converted to strings, the derivations evaluate to their output paths, and appending `/bin` to these leads us to their binaries. + +The same goes for the `src` variable. `$src` is the path to `simple.c` in the nix store. As an exercise, pretty print the .drv file. You'll see `simple_builder.sh` and `simple.c` listed in the input derivations, along with bash, gcc and coreutils .drv files. The newly added environment variables described above will also appear. + +In `simple_builder.sh` we set the `PATH` for gcc and coreutils binaries, so that our build script can find the necessary utilities like mkdir and gcc. + +We then create `$out` as a directory and place the binary inside it. Note that gcc is found via the `PATH` environment variable, but it could equivalently be referenced explicitly using `$gcc/bin/gcc`. + +## Enough of `nix repl` + +Drop out of nix repl and write a file `simple.nix`: + +```nix +let + pkgs = import { }; +in +pkgs.stdenv.mkDerivation { + name = "simple"; + builder = "${pkgs.bash}/bin/bash"; + args = [ ./simple_builder.sh ]; + gcc = pkgs.gcc; + coreutils = pkgs.coreutils; + src = ./simple.c; + system = builtins.currentSystem; +} +``` + +Now you can build it with `nix-build simple.nix`. This will create a symlink `result` in the current directory, pointing to the out path of the derivation. + +nix-build does two jobs: + +- [ nix-instantiate ](https://nixos.org/manual/nix/stable/command-ref/nix-instantiate.html): parse and evaluate `simple.nix` and return the .drv file corresponding to the parsed derivation set + +- [ `nix-store -r` ](https://nixos.org/manual/nix/stable/command-ref/nix-store.html#operation---realise): realise the .drv file, which actually builds it. + +Finally, it creates the symlink. + +In the second line of `simple.nix`, we have an `import` function call. Recall that `import` accepts one argument, a nix file to load. In this case, the contents of the file evaluate to a function. + +Afterwards, we call the function with the empty set. We saw this already in [the fifth pill](05-functions-and-imports.md). To reiterate: `import {}` is calling two functions, not one. Reading it as `(import ) {}` makes this clearer. + +The value returned by the nixpkgs function is a set; more specifically, it's a set of derivations. Calling `import {}` into a `let`-expression creates the local variable `pkgs` and brings it into scope. This has an effect similar to the `:l ` we used in nix repl, in that it allows us to easily access derivations such as `bash`, `gcc`, and `coreutils`, but those derivations will have to be explicitly referred to as members of the `pkgs` set (e.g., `pkgs.bash` instead of just `bash`). + +Below is a revised version of the `simple.nix` file, using the `inherit` keyword: + +```nix +let + pkgs = import { }; +in +pkgs.stdenv.mkDerivation { + name = "simple"; + builder = "${pkgs.bash}/bin/bash"; + args = [ ./simple_builder.sh ]; + inherit (pkgs) gcc coreutils; + src = ./simple.c; + system = builtins.currentSystem; +} +``` + +Here we also take the opportunity to introduce the [`inherit` keyword](https://nixos.org/manual/nix/stable/expressions/language-constructs.html#inheriting-attributes). `inherit foo;` is equivalent to `foo = foo;`. Similarly, `inherit gcc coreutils;` is equivalent to `gcc = gcc; coreutils = coreutils;`. Lastly, `inherit (pkgs) gcc coreutils;` is equivalent to `gcc = pkgs.gcc; coreutils = pkgs.coreutils;`. + +This syntax only makes sense inside sets. There's no magic involved, it's simply a convenience to avoid repeating the same name for both the attribute name and the value in scope. + +## Next pill + +We will generalize the builder. You may have noticed that we wrote two separate `builder.sh` scripts in this post. We would like to have a generic builder script instead, especially since each build script goes in the nix store: a bit of a waste. + +_Is it really that hard to package stuff in Nix? No_, here we're studying the fundamentals of Nix. diff --git a/pills/07-working-derivation.xml b/pills/07-working-derivation.xml deleted file mode 100644 index 17d0dac..0000000 --- a/pills/07-working-derivation.xml +++ /dev/null @@ -1,341 +0,0 @@ - - - Working Derivation - -
- Introduction - - Welcome to the seventh nix pill. In the previous - sixth pill we introduced the - notion of derivation in the Nix language — how to define a raw derivation - and how to (try to) build it. - - - In this post we continue along the path, by creating a derivation that - actually builds something. Then, we try to package a real program: we - compile a simple C file and create a derivation out of it, given a blessed - toolchain. - - - - I remind you how to enter the Nix environment: - source ~/.nix-profile/etc/profile.d/nix.sh - -
- -
- Using a script as a builder - - - What's the easiest way to run a sequence of commands for building - something? A bash script. We write a custom bash script, and we want it to - be our builder. Given a builder.sh, we want the - derivation to run bash builder.sh. - - - - We don't use hash bangs in builder.sh, because at the - time we are writing it we do not know the path to - bash in the nix store. Yes, even bash is in the - nix store, everything is there. - - - - We don't even use /usr/bin/env, because then we - lose the cool stateless property of Nix. Not to mention that - PATH gets cleared when building, so it wouldn't find - bash anyway. - - - - In summary, we want the builder to be bash, and - pass it an argument, builder.sh. Turns out the - derivation function accepts an optional - args attribute which is used to pass arguments to - the builder executable. - - - - First of all, let's write our builder.sh in the - current directory: - - - - The command declare -xp - lists exported variables - (declare is a builtin bash function). - As we covered in the previous pill, Nix computes the output path of the - derivation. The resulting .drv file contains a list of - environment variables passed to the builder. One of these is - $out. - - - What we have to do is create something in the path - $out, be it a file or a directory. In this case we are - creating a file. - - - - In addition, we print out the environment variables during the build - process. We cannot use env for this, because - env is part of - coreutils and we don't have a dependency to it - yet. We only have bash for now. - - - - Like for coreutils in the previous pill, we get a blessed bash for free - from our magic nixpkgs stuff: - - - - So with the usual trick, we can refer to - bin/bash and create our derivation: - - - - We did it! The contents of - /nix/store/w024zci0x1hh1wj6gjq0jagkc1sgrf5r-foo - is really foo. We've built our first derivation. - - - Note that we used ./builder.sh and not - "./builder.sh". This way, it is parsed as a path, and Nix - performs some magic which we will cover later. Try using the string - version and you will find that it cannot find - builder.sh. This is because it tries to find it - relative to the temporary build directory. - -
- -
- The builder environment - - We can use nix-store --read-log to see the logs our - builder produced: - - - - Let's inspect those environment variables printed during the build process. - - - $HOME is not your home directory, and - /homeless-shelter doesn't exist at all. We force - packages not to depend on $HOME during the build - process. - - - $PATH plays the same game as $HOME - - - $NIX_BUILD_CORES and $NIX_STORE are - nix - configuration options - - - $PWD and $TMP clearly show that nix - created a temporary build directory - - - Then $builder, $name, - $out, and $system are variables set due - to the .drv file's contents. - - - - - And that's how we were able to use $out in our derivation - and put stuff in it. It's like Nix reserved a slot in the nix store for - us, and we must fill it. - - - - In terms of autotools, $out will be the - path. Yes, not the make - , but the . That's the - essence of stateless packaging. You don't install the package in a global - common path under /, you install it in a local - isolated path under your nix store slot. - -
-
- The .drv contents - - We added something else to the derivation this time: the args attribute. - Let's see how this changed the .drv compared to the previous pill: - - - Much like the usual .drv, except that there's a list of arguments in there - passed to the builder (bash) with - builder.sh… In the nix store..? Nix automatically - copies files or directories needed for the build into the store to ensure - that they are not changed during the build process and that the deployment - is stateless and independent of the building machine. - builder.sh is not only in the arguments passed to the - builder, it's also in the input derivations. - - - Given that builder.sh is a plain file, it has no .drv - associated with it. The store path is computed based on the filename and - on the hash of its contents. Store paths are covered in detail in a later pill. - -
-
- Packaging a simple C program - - Start off by writing a simple C program called simple.c: - - - - And its simple_builder.sh: - - - - Don't worry too much about where those variables come from yet; let's - write the derivation and build it: - - - - Now you can run - /nix/store/ni66p4jfqksbmsl616llx3fbs1d232d4-simple/simple - in your shell. - -
-
- Explanation - - We added two new attributes to the derivation call, gcc - and coreutils. In gcc = gcc;, the name on - the left is the name in the derivation set, and the name on the right - refers to the gcc derivation from nixpkgs. The same applies for coreutils. - - - We also added the src attribute, nothing magical — it's - just a name, to which the path ./simple.c is - assigned. Like simple-builder.sh, - simple.c will be added to the store. - - - The trick: every attribute in the set passed to - derivation will be converted to a string and passed - to the builder as an environment variable. This is how the builder gains - access to coreutils and - gcc: when converted to strings, the derivations - evaluate to their output paths, and appending /bin to - these leads us to their binaries. - - - The same goes for the src variable. $src - is the path to simple.c in the nix store. As an - exercise, pretty print the .drv file. You'll see - simple_builder.sh and simple.c - listed in the input derivations, along with - bash, gcc and - coreutils .drv files. The newly added - environment variables described above will also appear. - - - In simple_builder.sh we set the PATH - for gcc and - coreutils binaries, so that our build script - can find the necessary utilities like mkdir and - gcc. - - - We then create $out as a directory and place the binary - inside it. Note that gcc is found via the - PATH environment variable, but it could equivalently be - referenced explicitly using $gcc/bin/gcc. - -
-
- Enough of <literal>nix repl</literal> - - Drop out of nix repl and write a file - simple.nix: - - - - Now you can build it with nix-build simple.nix. This - will create a symlink result in the current - directory, pointing to the out path of the derivation. - - - nix-build does two jobs: - - - - nix-instantiate - : parse and evaluate simple.nix and return - the .drv file corresponding to the parsed derivation set - - - - nix-store -r - : realise the .drv file, which actually builds it. - - - Finally, it creates the symlink. - - - In the second line of simple.nix, we have an - import function call. Recall that import - accepts one argument, a nix file to load. In this case, the contents of - the file evaluate to a function. - - - Afterwards, we call the function with the empty set. We saw this already - in the fifth pill. To - reiterate: import <nixpkgs> {} is calling two functions, - not one. Reading it as (import <nixpkgs>) {} makes this - clearer. - - - The value returned by the nixpkgs function is a set; more specifically, - it's a set of derivations. Calling import <nixpkgs> {} - into a let-expression creates the local variable - pkgs and brings it into scope. This has an effect similar to - the :l <nixpkgs> we used in nix repl, - in that it allows us to easily access derivations such as bash, - gcc, and coreutils, but those derivations - will have to be explicitly referred to as members of the pkgs set - (e.g., pkgs.bash instead of just bash). - - - Below is a revised version of the simple.nix file, using the inherit keyword: - - - - Here we also take the opportunity to introduce the - inherit keyword. - inherit foo; is equivalent to foo = foo;. - Similarly, inherit gcc coreutils; is equivalent to gcc = gcc; coreutils = coreutils;. - Lastly, inherit (pkgs) gcc coreutils; is equivalent to gcc = pkgs.gcc; coreutils = pkgs.coreutils;. - - - This syntax only makes sense inside sets. There's no magic involved, it's - simply a convenience to avoid repeating the same name for both the - attribute name and the value in scope. - -
-
- Next pill - - We will generalize the builder. You may have noticed that we wrote two - separate builder.sh scripts in this post. We would - like to have a generic builder script instead, especially since each build - script goes in the nix store: a bit of a waste. - - - Is it really that hard to package stuff in Nix? No, - here we're studying the fundamentals of Nix. - -
-
diff --git a/pills/07/bash.xml b/pills/07/bash.xml deleted file mode 100644 index e288773..0000000 --- a/pills/07/bash.xml +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> :l <nixpkgs> -Added 3950 variables. -nix-repl> "${bash}" -"/nix/store/ihmkc7z2wqk3bbipfnlh0yjrlfkkgnv6-bash-4.2-p45" - diff --git a/pills/07/builder.sh.txt b/pills/07/builder.sh.txt deleted file mode 100644 index 19459b7..0000000 --- a/pills/07/builder.sh.txt +++ /dev/null @@ -1,2 +0,0 @@ -declare -xp -echo foo > $out diff --git a/pills/07/c-program-derivation.xml b/pills/07/c-program-derivation.xml deleted file mode 100644 index 2f57f2c..0000000 --- a/pills/07/c-program-derivation.xml +++ /dev/null @@ -1,7 +0,0 @@ -nix-repl> :l <nixpkgs> -nix-repl> simple = derivation { name = "simple"; builder = "${bash}/bin/bash"; args = [ ./simple_builder.sh ]; gcc = gcc; coreutils = coreutils; src = ./simple.c; system = builtins.currentSystem; } -nix-repl> :b simple -this derivation produced the following outputs: - - out -> /nix/store/ni66p4jfqksbmsl616llx3fbs1d232d4-simple - diff --git a/pills/07/foo.drv.xml b/pills/07/foo.drv.xml deleted file mode 100644 index f9a5a92..0000000 --- a/pills/07/foo.drv.xml +++ /dev/null @@ -1,29 +0,0 @@ -$ nix derivation show /nix/store/i76pr1cz0za3i9r6xq518bqqvd2raspw-foo.drv -{ - "/nix/store/i76pr1cz0za3i9r6xq518bqqvd2raspw-foo.drv": { - "outputs": { - "out": { - "path": "/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo" - } - }, - "inputSrcs": [ - "/nix/store/lb0n38r2b20r8rl1k45a7s4pj6ny22f7-builder.sh" - ], - "inputDrvs": { - "/nix/store/hcgwbx42mcxr7ksnv0i1fg7kw6jvxshb-bash-4.4-p19.drv": [ - "out" - ] - }, - "platform": "x86_64-linux", - "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", - "args": [ - "/nix/store/lb0n38r2b20r8rl1k45a7s4pj6ny22f7-builder.sh" - ], - "env": { - "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", - "name": "foo", - "out": "/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo", - "system": "x86_64-linux" - } - } -} diff --git a/pills/07/read-log.xml b/pills/07/read-log.xml deleted file mode 100644 index 2124383..0000000 --- a/pills/07/read-log.xml +++ /dev/null @@ -1,18 +0,0 @@ -$ nix-store --read-log /nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo -declare -x HOME="/homeless-shelter" -declare -x NIX_BUILD_CORES="4" -declare -x NIX_BUILD_TOP="/tmp/nix-build-foo.drv-0" -declare -x NIX_LOG_FD="2" -declare -x NIX_STORE="/nix/store" -declare -x OLDPWD -declare -x PATH="/path-not-set" -declare -x PWD="/tmp/nix-build-foo.drv-0" -declare -x SHLVL="1" -declare -x TEMP="/tmp/nix-build-foo.drv-0" -declare -x TEMPDIR="/tmp/nix-build-foo.drv-0" -declare -x TMP="/tmp/nix-build-foo.drv-0" -declare -x TMPDIR="/tmp/nix-build-foo.drv-0" -declare -x builder="/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash" -declare -x name="foo" -declare -x out="/nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo" -declare -x system="x86_64-linux" diff --git a/pills/07/simple-derivation.xml b/pills/07/simple-derivation.xml deleted file mode 100644 index 49f3599..0000000 --- a/pills/07/simple-derivation.xml +++ /dev/null @@ -1,6 +0,0 @@ -nix-repl> d = derivation { name = "foo"; builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; system = builtins.currentSystem; } -nix-repl> :b d -[1 built, 0.0 MiB DL] - -this derivation produced the following outputs: - out -> /nix/store/gczb4qrag22harvv693wwnflqy7lx5pb-foo diff --git a/pills/07/simple.c.txt b/pills/07/simple.c.txt deleted file mode 100644 index c0a3532..0000000 --- a/pills/07/simple.c.txt +++ /dev/null @@ -1,3 +0,0 @@ -void main() { - puts("Simple!"); -} diff --git a/pills/07/simple.txt b/pills/07/simple.txt deleted file mode 100644 index eef5693..0000000 --- a/pills/07/simple.txt +++ /dev/null @@ -1,12 +0,0 @@ -let - pkgs = import { }; -in -pkgs.stdenv.mkDerivation { - name = "simple"; - builder = "${pkgs.bash}/bin/bash"; - args = [ ./simple_builder.sh ]; - gcc = pkgs.gcc; - coreutils = pkgs.coreutils; - src = ./simple.c; - system = builtins.currentSystem; -} diff --git a/pills/07/simple_builder.sh.txt b/pills/07/simple_builder.sh.txt deleted file mode 100644 index 8a38571..0000000 --- a/pills/07/simple_builder.sh.txt +++ /dev/null @@ -1,3 +0,0 @@ -export PATH="$coreutils/bin:$gcc/bin" -mkdir $out -gcc -o $out/simple $src diff --git a/pills/07/simple_inherit.txt b/pills/07/simple_inherit.txt deleted file mode 100644 index 3f8c4ae..0000000 --- a/pills/07/simple_inherit.txt +++ /dev/null @@ -1,11 +0,0 @@ -let - pkgs = import { }; -in -pkgs.stdenv.mkDerivation { - name = "simple"; - builder = "${pkgs.bash}/bin/bash"; - args = [ ./simple_builder.sh ]; - inherit (pkgs) gcc coreutils; - src = ./simple.c; - system = builtins.currentSystem; -} diff --git a/pills/08-generic-builders.md b/pills/08-generic-builders.md new file mode 100644 index 0000000..ebd651e --- /dev/null +++ b/pills/08-generic-builders.md @@ -0,0 +1,272 @@ +# Generic Builders + +Welcome to the 8th Nix pill. In the previous [7th pill](07-working-derivation.md) we successfully built a derivation. We wrote a builder script that compiled a C file and installed the binary under the nix store. + +In this post, we will generalize the builder script, write a Nix expression for [GNU hello world](https://www.gnu.org/software/hello/) and create a wrapper around the derivation built-in function. + +## Packaging GNU hello world + +In the previous pill we packaged a simple .c file, which was being compiled with a raw gcc call. That's not a good example of a project. Many use autotools, and since we're going to generalize our builder, it would be better to do it with the most used build system. + +[GNU hello world](https://www.gnu.org/software/hello/), despite its name, is a simple yet complete project which uses autotools. Fetch the latest tarball here: . + +Let's create a builder script for GNU hello world, hello_builder.sh: + +```sh +export PATH="$gnutar/bin:$gcc/bin:$gnumake/bin:$coreutils/bin:$gawk/bin:$gzip/bin:$gnugrep/bin:$gnused/bin:$bintools/bin" +tar -xzf $src +cd hello-2.12.1 +./configure --prefix=$out +make +make install +``` + +And the derivation hello.nix: + +```nix +let + pkgs = import { }; +in +derivation { + name = "hello"; + builder = "${pkgs.bash}/bin/bash"; + args = [ ./hello_builder.sh ]; + inherit (pkgs) + gnutar + gzip + gnumake + gcc + coreutils + gawk + gnused + gnugrep + ; + bintools = pkgs.binutils.bintools; + src = ./hello-2.12.1.tar.gz; + system = builtins.currentSystem; +} +``` + +
+

Nix on darwin

+ +Darwin (i.e. macOS) builds typically use `clang` rather than `gcc` for a C compiler. We can adapt this early example for darwin by using this modified version of `hello.nix`: + +```nix +let + pkgs = import { }; +in +derivation { + name = "hello"; + builder = "${pkgs.bash}/bin/bash"; + args = [ ./hello_builder.sh ]; + inherit (pkgs) + gnutar + gzip + gnumake + coreutils + gawk + gnused + gnugrep + ; + gcc = pkgs.clang; + bintools = pkgs.clang.bintools.bintools_bin; + src = ./hello-2.12.1.tar.gz; + system = builtins.currentSystem; +} +``` + +Later, we will show how Nix can automatically handle these differences. For now, please be just aware that changes similar to the above may be needed in what follows. + +
+ +Now build it with `nix-build hello.nix` and you can launch `result/bin/hello`. Nothing easier, but do we have to create a builder.sh for each package? Do we always have to pass the dependencies to the `derivation` function? + +Please note the `--prefix=$out` we were talking about in the [previous pill](07-working-derivation.md). + +## A generic builder + +Let's create a generic `builder.sh` for autotools projects: + +```sh +set -e +unset PATH +for p in $buildInputs; do + export PATH=$p/bin${PATH:+:}$PATH +done + +tar -xf $src + +for d in *; do + if [ -d "$d" ]; then + cd "$d" + break + fi +done + +./configure --prefix=$out +make +make install +``` + +What do we do here? + +1. Exit the build on any error with `set -e`. + +2. First `unset PATH`, because it's initially set to a non-existent path. + +3. We'll see this below in detail, however for each path in `$buildInputs`, we append `bin` to `PATH`. + +4. Unpack the source. + +5. Find a directory where the source has been unpacked and `cd` into it. + +6. Once we're set up, compile and install. + +As you can see, there's no reference to "hello" in the builder anymore. It still makes several assumptions, but it's certainly more generic. + +Now let's rewrite `hello.nix`: + +```nix +let + pkgs = import { }; +in +derivation { + name = "hello"; + builder = "${pkgs.bash}/bin/bash"; + args = [ ./builder.sh ]; + buildInputs = with pkgs; [ + gnutar + gzip + gnumake + gcc + coreutils + gawk + gnused + gnugrep + binutils.bintools + ]; + src = ./hello-2.12.1.tar.gz; + system = builtins.currentSystem; +} +``` + +All clear, except that buildInputs. However it's easier than any black magic you are thinking of at this moment. + +Nix is able to convert a list to a string. It first converts the elements to strings, and then concatenates them separated by a space: + +```console +nix-repl> builtins.toString 123 +"123" +nix-repl> builtins.toString [ 123 456 ] +"123 456" +``` + +Recall that derivations can be converted to a string, hence: + +```console +nix-repl> :l +Added 3950 variables. +nix-repl> builtins.toString gnugrep +"/nix/store/g5gdylclfh6d224kqh9sja290pk186xd-gnugrep-2.14" +nix-repl> builtins.toString [ gnugrep gnused ] +"/nix/store/g5gdylclfh6d224kqh9sja290pk186xd-gnugrep-2.14 /nix/store/krgdc4sknzpw8iyk9p20lhqfd52kjmg0-gnused-4.2.2" +``` + +Simple! The buildInputs variable is a string with out paths separated by space, perfect for bash usage in a for loop. + +## A more convenient derivation function + +We managed to write a builder that can be used for multiple autotools projects. But in the hello.nix expression we are specifying tools that are common to more projects; we don't want to pass them every time. + +A natural approach would be to create a function that accepts an attribute set, similar to the one used by the derivation function, and merge it with another attribute set containing values common to many projects. + +Create `autotools.nix`: + +```nix +pkgs: attrs: +let + defaultAttrs = { + builder = "${pkgs.bash}/bin/bash"; + args = [ ./builder.sh ]; + baseInputs = with pkgs; [ + gnutar + gzip + gnumake + gcc + coreutils + gawk + gnused + gnugrep + binutils.bintools + ]; + buildInputs = [ ]; + system = builtins.currentSystem; + }; +in +derivation (defaultAttrs // attrs) +``` + +Ok now we have to remember a little about [Nix functions](05-functions-and-imports.md). The whole nix expression of this `autotools.nix` file will evaluate to a function. This function accepts a parameter `pkgs`, then returns a function which accepts a parameter `attrs`. + +The body of the function is simple, yet at first sight it might be hard to grasp: + +1. First drop in the scope the magic `pkgs` attribute set. + +2. Within a let expression we define a helper variable, `defaultAttrs`, which serves as a set of common attributes used in derivations. + +3. Finally we create the derivation with that strange expression, (`defaultAttrs // attrs`). + +The [// operator](https://nixos.org/manual/nix/stable/expressions/language-operators.html) is an operator between two sets. The result is the union of the two sets. In case of conflicts between attribute names, the value on the right set is preferred. + +So we use `defaultAttrs` as base set, and add (or override) the attributes from `attrs`. + +A couple of examples ought to be enough to clear out the behavior of the operator: + +```console +nix-repl> { a = "b"; } // { c = "d"; } +{ a = "b"; c = "d"; } +nix-repl> { a = "b"; } // { a = "c"; } +{ a = "c"; } +``` + +**Exercise:** Complete the new `builder.sh` by adding `$baseInputs` in the `for` loop together with `$buildInputs`. As you noticed, we passed that new variable in the derivation. Instead of merging buildInputs with the base ones, we prefer to preserve buildInputs as seen by the caller, so we keep them separated. Just a matter of choice. + +Then we rewrite `hello.nix` as follows: + +```nix +let + pkgs = import { }; + mkDerivation = import ./autotools.nix pkgs; +in +mkDerivation { + name = "hello"; + src = ./hello-2.12.1.tar.gz; +} +``` + +Finally! We got a very simple description of a package! Below are a couple of remarks that you may find useful as you're continuing to understand the nix language: + +- We assigned to pkgs the import that we did in the previous expressions in the "with". Don't be afraid, it's that straightforward. + +- The mkDerivation variable is a nice example of partial application, look at it as (`import ./autotools.nix`) `pkgs`. First we import the expression, then we apply the `pkgs` parameter. That will give us a function that accepts the attribute set `attrs`. + +- We create the derivation specifying only name and src. If the project eventually needed other dependencies to be in PATH, then we would simply add those to buildInputs (not specified in hello.nix because empty). + +Note we didn't use any other library. Special C flags may be needed to find include files of other libraries at compile time, and ld flags at link time. + +## Conclusion + +Nix gives us the bare metal tools for creating derivations, setting up a build environment and storing the result in the nix store. + +Out of this pill we managed to create a generic builder for autotools projects, and a function `mkDerivation` that composes by default the common components used in autotools projects instead of repeating them in all the packages we would write. + +We are familiarizing ourselves with the way a Nix system grows up: it's about creating and composing derivations with the Nix language. + +Analogy: in C you create objects in the heap, and then you compose them inside new objects. Pointers are used to refer to other objects. + +In Nix you create derivations stored in the nix store, and then you compose them by creating new derivations. Store paths are used to refer to other derivations. + +## Next pill + +...we will talk a little about runtime dependencies. Is the GNU hello world package self-contained? What are its runtime dependencies? We only specified build dependencies by means of using other derivations in the "hello" derivation. diff --git a/pills/08-generic-builders.xml b/pills/08-generic-builders.xml deleted file mode 100644 index aa5062d..0000000 --- a/pills/08-generic-builders.xml +++ /dev/null @@ -1,333 +0,0 @@ - - - Generic Builders - - - Welcome to the 8th Nix pill. In the previous - 7th pill we successfully built a - derivation. We wrote a builder script that compiled a C file and installed - the binary under the nix store. - - - - In this post, we will generalize the builder script, write a Nix expression - for GNU hello world - and create a wrapper around the derivation built-in function. - - -
- Packaging GNU hello world - - - In the previous pill we packaged a simple .c file, which was being - compiled with a raw gcc call. That's not a good example of a project. Many - use autotools, and since we're going to generalize our builder, it would - be better to do it with the most used build system. - - - - GNU hello world, - despite its name, is a simple yet complete project which uses autotools. - Fetch the latest tarball here: - https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz. - - - - Let's create a builder script for GNU hello world, hello_builder.sh: - - - - - - And the derivation hello.nix: - - - - Nix on darwin - Darwin (i.e. macOS) builds typically use clang rather than gcc for a C compiler. - We can adapt this early example for darwin by using this modified version of hello.nix: - - Later, we will show how Nix can automatically handle these differences. - For now, please be just aware that changes similar to the above may be needed in what follows. - - - - - Now build it with nix-build hello.nix and you can - launch result/bin/hello. Nothing easier, but do we - have to create a builder.sh for each package? Do we always have to pass - the dependencies to the derivation function? - - - - Please note the --prefix=$out we were talking about in - the previous pill. - -
- -
- A generic builder - - - Let's create a generic builder.sh for autotools - projects: - - - - - - What do we do here? - - - - - - Exit the build on any error with set -e. - - - - - First unset PATH, because it's initially set to a - non-existent path. - - - - - We'll see this below in detail, however for each path in - $buildInputs, we append bin to - PATH. - - - - - Unpack the source. - - - - - Find a directory where the source has been unpacked and - cd into it. - - - - - Once we're set up, compile and install. - - - - - - As you can see, there's no reference to "hello" in the builder anymore. - It still makes several assumptions, but it's certainly more generic. - - - - Now let's rewrite hello.nix: - - - - - - All clear, except that buildInputs. However it's easier than any black - magic you are thinking of at this moment. - - - - Nix is able to convert a list to a string. It first converts the elements - to strings, and then concatenates them separated by a space: - - - - - - Recall that derivations can be converted to a string, hence: - - - - - - Simple! The buildInputs variable is a string with out paths separated by - space, perfect for bash usage in a for loop. - -
- -
- A more convenient derivation function - - - We managed to write a builder that can be used for multiple autotools - projects. But in the hello.nix expression we are specifying tools that - are common to more projects; we don't want to pass them every time. - - - - A natural approach would be to create a function that accepts an - attribute set, similar to the one used by the derivation function, and - merge it with another attribute set containing values common to many - projects. - - - - Create autotools.nix: - - - - - - Ok now we have to remember a little about - Nix functions. The whole nix - expression of this autotools.nix file will evaluate - to a function. This function accepts a parameter pkgs, then - returns a function which accepts a parameter attrs. - - - - The body of the function is simple, yet at first sight it might be hard - to grasp: - - - - - - First drop in the scope the magic pkgs attribute set. - - - - - Within a let expression we define a helper variable, - defaultAttrs, which serves as a set of common attributes - used in derivations. - - - - - Finally we create the derivation with that strange expression, - (defaultAttrs // attrs). - - - - - - The - // operator - is an operator between two sets. The result is the union of the two sets. - In case of conflicts between attribute names, the value on the right set - is preferred. - - - - So we use defaultAttrs as base set, and add (or override) the - attributes from attrs. - - - - A couple of examples ought to be enough to clear out the behavior of the - operator: - - - - - - Exercise: - Complete the new builder.sh by adding - $baseInputs in the for loop together with - $buildInputs. As you noticed, we passed that new variable in - the derivation. Instead of merging buildInputs with the base ones, we - prefer to preserve buildInputs as seen by the caller, so we keep them - separated. Just a matter of choice. - - - - Then we rewrite hello.nix as follows: - - - - - - Finally! We got a very simple description of a package! Below are a - couple of remarks that you may find useful as you're continuing to - understand the nix language: - - - - - - We assigned to pkgs the import that we did in the previous expressions - in the "with". Don't be afraid, it's that straightforward. - - - - - The mkDerivation variable is a nice example of partial application, - look at it as (import ./autotools.nix) pkgs. - First we import the expression, then we apply the pkgs - parameter. That will give us a function that accepts the attribute - set attrs. - - - - - We create the derivation specifying only name and src. If the project - eventually needed other dependencies to be in PATH, then we would - simply add those to buildInputs (not specified in hello.nix because - empty). - - - - - - Note we didn't use any other library. Special C flags may be needed to - find include files of other libraries at compile time, and ld flags at - link time. - -
- -
- Conclusion - - - Nix gives us the bare metal tools for creating derivations, setting up a - build environment and storing the result in the nix store. - - - - Out of this pill we managed to create a generic builder for autotools - projects, and a function mkDerivation that composes by default - the common components used in autotools projects instead of repeating them - in all the packages we would write. - - - - We are familiarizing ourselves with the way a Nix system grows up: it's - about creating and composing derivations with the Nix language. - - - - Analogy: in C you create objects - in the heap, and then you compose them inside new objects. Pointers are - used to refer to other objects. - - - - In Nix you create derivations stored in the nix store, and then you - compose them by creating new derivations. Store paths are used to refer - to other derivations. - -
- -
- Next pill - - - ...we will talk a little about runtime dependencies. Is the GNU hello - world package self-contained? What are its runtime dependencies? We only - specified build dependencies by means of using other derivations in the - "hello" derivation. - -
-
diff --git a/pills/08/autotools-nix.txt b/pills/08/autotools-nix.txt deleted file mode 100644 index 65d3bc5..0000000 --- a/pills/08/autotools-nix.txt +++ /dev/null @@ -1,21 +0,0 @@ -pkgs: attrs: -let - defaultAttrs = { - builder = "${pkgs.bash}/bin/bash"; - args = [ ./builder.sh ]; - baseInputs = with pkgs; [ - gnutar - gzip - gnumake - gcc - coreutils - gawk - gnused - gnugrep - binutils.bintools - ]; - buildInputs = [ ]; - system = builtins.currentSystem; - }; -in -derivation (defaultAttrs // attrs) diff --git a/pills/08/generic-builder.txt b/pills/08/generic-builder.txt deleted file mode 100644 index 0d1c764..0000000 --- a/pills/08/generic-builder.txt +++ /dev/null @@ -1,18 +0,0 @@ -set -e -unset PATH -for p in $buildInputs; do - export PATH=$p/bin${PATH:+:}$PATH -done - -tar -xf $src - -for d in *; do - if [ -d "$d" ]; then - cd "$d" - break - fi -done - -./configure --prefix=$out -make -make install diff --git a/pills/08/hello-builder.txt b/pills/08/hello-builder.txt deleted file mode 100644 index 3ad635e..0000000 --- a/pills/08/hello-builder.txt +++ /dev/null @@ -1,6 +0,0 @@ -export PATH="$gnutar/bin:$gcc/bin:$gnumake/bin:$coreutils/bin:$gawk/bin:$gzip/bin:$gnugrep/bin:$gnused/bin:$bintools/bin" -tar -xzf $src -cd hello-2.12.1 -./configure --prefix=$out -make -make install diff --git a/pills/08/hello-nix-darwin.txt b/pills/08/hello-nix-darwin.txt deleted file mode 100644 index 68a11bb..0000000 --- a/pills/08/hello-nix-darwin.txt +++ /dev/null @@ -1,21 +0,0 @@ -let - pkgs = import { }; -in -derivation { - name = "hello"; - builder = "${pkgs.bash}/bin/bash"; - args = [ ./hello_builder.sh ]; - inherit (pkgs) - gnutar - gzip - gnumake - coreutils - gawk - gnused - gnugrep - ; - gcc = pkgs.clang; - bintools = pkgs.clang.bintools.bintools_bin; - src = ./hello-2.12.1.tar.gz; - system = builtins.currentSystem; -} diff --git a/pills/08/hello-nix-rev-1.txt b/pills/08/hello-nix-rev-1.txt deleted file mode 100644 index 748ff5d..0000000 --- a/pills/08/hello-nix-rev-1.txt +++ /dev/null @@ -1,21 +0,0 @@ -let - pkgs = import { }; -in -derivation { - name = "hello"; - builder = "${pkgs.bash}/bin/bash"; - args = [ ./builder.sh ]; - buildInputs = with pkgs; [ - gnutar - gzip - gnumake - gcc - coreutils - gawk - gnused - gnugrep - binutils.bintools - ]; - src = ./hello-2.12.1.tar.gz; - system = builtins.currentSystem; -} diff --git a/pills/08/hello-nix-rev-2.txt b/pills/08/hello-nix-rev-2.txt deleted file mode 100644 index 8c660f7..0000000 --- a/pills/08/hello-nix-rev-2.txt +++ /dev/null @@ -1,8 +0,0 @@ -let - pkgs = import { }; - mkDerivation = import ./autotools.nix pkgs; -in -mkDerivation { - name = "hello"; - src = ./hello-2.12.1.tar.gz; -} diff --git a/pills/08/hello-nix.txt b/pills/08/hello-nix.txt deleted file mode 100644 index 948bcfd..0000000 --- a/pills/08/hello-nix.txt +++ /dev/null @@ -1,21 +0,0 @@ -let - pkgs = import { }; -in -derivation { - name = "hello"; - builder = "${pkgs.bash}/bin/bash"; - args = [ ./hello_builder.sh ]; - inherit (pkgs) - gnutar - gzip - gnumake - gcc - coreutils - gawk - gnused - gnugrep - ; - bintools = pkgs.binutils.bintools; - src = ./hello-2.12.1.tar.gz; - system = builtins.currentSystem; -} diff --git a/pills/08/set-union.txt b/pills/08/set-union.txt deleted file mode 100644 index 392e6a7..0000000 --- a/pills/08/set-union.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> { a = "b"; } // { c = "d"; } -{ a = "b"; c = "d"; } -nix-repl> { a = "b"; } // { a = "c"; } -{ a = "c"; } diff --git a/pills/08/to-string-nixpkgs.txt b/pills/08/to-string-nixpkgs.txt deleted file mode 100644 index f09ee03..0000000 --- a/pills/08/to-string-nixpkgs.txt +++ /dev/null @@ -1,6 +0,0 @@ -nix-repl> :l -Added 3950 variables. -nix-repl> builtins.toString gnugrep -"/nix/store/g5gdylclfh6d224kqh9sja290pk186xd-gnugrep-2.14" -nix-repl> builtins.toString [ gnugrep gnused ] -"/nix/store/g5gdylclfh6d224kqh9sja290pk186xd-gnugrep-2.14 /nix/store/krgdc4sknzpw8iyk9p20lhqfd52kjmg0-gnused-4.2.2" diff --git a/pills/08/to-string.txt b/pills/08/to-string.txt deleted file mode 100644 index 0e126a3..0000000 --- a/pills/08/to-string.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> builtins.toString 123 -"123" -nix-repl> builtins.toString [ 123 456 ] -"123 456" diff --git a/pills/09-automatic-runtime-dependencies.md b/pills/09-automatic-runtime-dependencies.md new file mode 100644 index 0000000..3641c27 --- /dev/null +++ b/pills/09-automatic-runtime-dependencies.md @@ -0,0 +1,138 @@ +# Automatic Runtime Dependencies + +Welcome to the 9th Nix pill. In the previous [8th pill](08-generic-builders.md) we wrote a generic builder for autotools projects. We fed in build dependencies and a source tarball, and we received a Nix derivation as a result. + +Today we stop by the GNU `hello` program to analyze build and runtime dependencies, and we enhance our builder to eliminate unnecessary runtime dependencies. + +## Build dependencies + +Let's start analyzing build dependencies for our GNU `hello` package: + +```console +$ nix-instantiate hello.nix +/nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv +$ nix-store -q --references /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv +/nix/store/0q6pfasdma4as22kyaknk4kwx4h58480-hello-2.10.tar.gz +/nix/store/1zcs1y4n27lqs0gw4v038i303pb89rw6-coreutils-8.21.drv +/nix/store/2h4b30hlfw4fhqx10wwi71mpim4wr877-gnused-4.2.2.drv +/nix/store/39bgdjissw9gyi4y5j9wanf4dbjpbl07-gnutar-1.27.1.drv +/nix/store/7qa70nay0if4x291rsjr7h9lfl6pl7b1-builder.sh +/nix/store/g6a0shr58qvx2vi6815acgp9lnfh9yy8-gnugrep-2.14.drv +/nix/store/jdggv3q1sb15140qdx0apvyrps41m4lr-bash-4.2-p45.drv +/nix/store/pglhiyp1zdbmax4cglkpz98nspfgbnwr-gnumake-3.82.drv +/nix/store/q9l257jn9lndbi3r9ksnvf4dr8cwxzk7-gawk-4.1.0.drv +/nix/store/rgyrqxz1ilv90r01zxl0sq5nq0cq7v3v-binutils-2.23.1.drv +/nix/store/qzxhby795niy6wlagfpbja27dgsz43xk-gcc-wrapper-4.8.3.drv +/nix/store/sk590g7fv53m3zp0ycnxsc41snc2kdhp-gzip-1.6.drv +``` + +It has precisely the derivations referenced in the `derivation` function; nothing more, nothing less. Of course, we may not use some of them at all. However, given that our generic `mkDerivation` function always pulls such dependencies (think of it like [build-essential](https://packages.debian.org/unstable/build-essential) from Debian), we will already have these packages in the nix store for any future packages that need them. + +Why are we looking at `.drv` files? Because the `hello.drv` file is the representation of the build action that builds the `hello` out path. As such, it contains the input derivations needed before building `hello`. + +## Digression about NAR files + +The `NAR` format is the "Nix ARchive". This format was designed due to existing archive formats, such as `tar`, being insufficient. Nix benefits from deterministic build tools, but commonly used archivers lack this property: they add padding, they do not sort files, they add timestamps, and so on. This can result in directories containing bit-identical files turning into non-bit-identical archives, which leads to different hashes. + +Thus the `NAR` format was developed as a simple, deterministic archive format. `NAR`s are used extensively within Nix, as we will see below. + +For more rationale and implementation details behind `NAR` see [Dolstra's PhD Thesis](http://nixos.org/~eelco/pubs/phd-thesis.pdf). + +To create NAR archives from store paths, we can use `nix-store --dump` and `nix-store --restore`. + +## Runtime dependencies + +We now note that Nix automatically recognized build dependencies once our `derivation` call referred to them, but we never specified the runtime dependencies. + +Nix handles runtime dependencies for us automatically. The technique it uses to do so may seem fragile at first glance, but it works so well that the NixOS operating system is built off of it. The underlying mechanism relies on the hash of the store paths. It proceeds in three steps: + +1. Dump the derivation as a NAR. Recall that this is a serialization of the derivation output \-- meaning this works fine whether the output is a single file or a directory. + +2. For each build dependency `.drv` and its relative out path, search the contents of the NAR for this out path. + +3. If the path is found, then it's a runtime dependency. + +The snippet below shows the dependencies for `hello`. + +```console +$ nix-instantiate hello.nix +/nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv +$ nix-store -r /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv +/nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello +$ nix-store -q --references /nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello +/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 +/nix/store/8jm0wksask7cpf85miyakihyfch1y21q-gcc-4.8.3 +/nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello +``` + +We see that `glibc` and `gcc` are runtime dependencies. Intuitively, `gcc` shouldn't be in this list! Displaying the printable strings in the `hello` binary shows that the out path of `gcc` does indeed appear: + +```console +$ strings result/bin/hello|grep gcc +/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib:/nix/store/8jm0wksask7cpf85miyakihyfch1y21q-gcc-4.8.3/lib64 +``` + +This is why Nix added `gcc`. But why is that path present in the first place? The answer is that it is the [ld rpath](http://en.wikipedia.org/wiki/Rpath): the list of directories where libraries can be found at runtime. In other distributions, this is usually not abused. But in Nix, we have to refer to particular versions of libraries, and thus the rpath has an important role. + +The build process adds the `gcc` lib path thinking it may be useful at runtime, but this isn't necessary. To address issues like these, Nix provides a tool called [patchelf](https://nixos.org/patchelf.html), which reduces the rpath to the paths that are actually used by the binary. + +Even after reducing the rpath, the `hello` binary would still depend upon `gcc` because of some debugging information. This unnecessarily increases the size of our runtime dependencies. We'll explore how `strip` can help us with that in the next section. + +## Another phase in the builder + +We will add a new phase to our autotools builder. The builder has six phases already: + +1. The "environment setup" phase + +2. The "unpack phase": we unpack the sources in the current directory (remember, Nix changes to a temporary directory first) + +3. The "change directory" phase, where we change source root to the directory that has been unpacked + +4. The "configure" phase: `./configure` + +5. The "build" phase: `make` + +6. The "install" phase: `make install` + +Now we will add a new phase after the installation phase, which we call the "fixup" phase. At the end of the `builder.sh`, we append: + +```console +find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null +``` + +That is, for each file we run `patchelf --shrink-rpath` and `strip`. Note that we used two new commands here, `find` and `patchelf`. These must be added to our derivation. + +**Exercise:** Add `findutils` and `patchelf` to the `baseInputs` of `autotools.nix`. + +Now, we rebuild `hello.nix`... + +```console +$ nix-build hello.nix +[...] +$ nix-store -q --references result +/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 +/nix/store/md4a3zv0ipqzsybhjb8ndjhhga1dj88x-hello +``` + +and we see that `glibc` is a runtime dependency. This is exactly what we wanted. + +The package is self-contained. This means that we can copy its closure onto another machine and we will be able to run it. Remember, only a very few components under the `/nix/store` are required to [run nix](02-install-on-your-running-system.md). The `hello` binary will use the exact version of `glibc` library and interpreter referred to in the binary, rather than the system one: + +```console +$ ldd result/bin/hello + linux-vdso.so.1 (0x00007fff11294000) + libc.so.6 => /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/libc.so.6 (0x00007f7ab7362000) + /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/ld-linux-x86-64.so.2 (0x00007f7ab770f000) +``` + +Of course, the executable will run fine as long as everything is under the `/nix/store` path. + +## Conclusion + +We saw some of the tools Nix provides, along with their features. In particular, we saw how Nix is able to compute runtime dependencies automatically. This is not limited to only shared libraries, but can also reference executables, scripts, Python libraries, and so forth. + +Approaching builds in this way makes packages self-contained, ensuring (apart from data and configuration) that copying the runtime closure onto another machine is sufficient to run the program. This enables us to run programs without installation using `nix-shell`, and forms the basis for [reliable deployment in the cloud](https://nixos.org/manual/nix/stable/introduction.html). + +## Next pill + +The next pill will introduce `nix-shell`. With `nix-build`, we've always built derivations from scratch: the source gets unpacked, configured, built, and installed. But this can take a long time for large packages. What if we want to apply some small changes and compile incrementally instead, yet still want to keep a self-contained environment similar to `nix-build`? `nix-shell` enables this. diff --git a/pills/09-automatic-runtime.xml b/pills/09-automatic-runtime.xml deleted file mode 100644 index 7af0c1b..0000000 --- a/pills/09-automatic-runtime.xml +++ /dev/null @@ -1,281 +0,0 @@ - - - Automatic Runtime Dependencies - - - Welcome to the 9th Nix pill. In the previous - 8th pill we wrote a generic builder - for autotools projects. We fed in build dependencies and a source tarball, and - we received a Nix derivation as a result. - - - - Today we stop by the GNU hello program to analyze build and runtime - dependencies, and we enhance our builder to eliminate unnecessary runtime - dependencies. - - -
- Build dependencies - - - Let's start analyzing build dependencies for our GNU hello package: - - - - - - It has precisely the derivations referenced in the derivation function; - nothing more, nothing less. Of course, we may not use some of them at all. - However, given that our generic mkDerivation function always pulls - such dependencies (think of it like - build-essential - from Debian), we will already have these packages in the nix store for any future packages that - need them. - - - - Why are we looking at .drv files? Because the hello.drv - file is the representation of the build action that builds the hello - out path. As such, it contains the input derivations needed before building - hello. - -
- -
- Digression about NAR files - - - The NAR format is the "Nix ARchive". This format was designed due to - existing archive formats, such as tar, being insufficient. - Nix benefits from deterministic build tools, but commonly used archivers - lack this property: they add padding, they do not sort files, they add timestamps, - and so on. This can result in directories containing bit-identical files turning into - non-bit-identical archives, which leads to different hashes. - - - - Thus the NAR format was developed as a simple, deterministic - archive format. NARs are used extensively within Nix, as we will - see below. - - - - For more rationale and implementation details behind NAR see - Dolstra's PhD Thesis. - - - - To create NAR archives from store paths, we can use - nix-store --dump and - nix-store --restore. - -
- -
- Runtime dependencies - - - We now note that Nix automatically recognized build dependencies once our - derivation call referred to them, but we never specified the - runtime dependencies. - - - - Nix handles runtime dependencies for us automatically. The technique it uses - to do so may seem fragile at first glance, but it works so well that the NixOS - operating system is built off of it. The underlying mechanism relies on the - hash of the store paths. It proceeds in three steps: - - - - - - Dump the derivation as a NAR. Recall that this is a serialization of - the derivation output -- meaning this works fine whether the output - is a single file or a directory. - - - - - For each build dependency .drv and its relative out path, - search the contents of the NAR for this out path. - - - - - If the path is found, then it's a runtime dependency. - - - - - - The snippet below shows the dependencies for hello. - - - - - - We see that glibc and gcc are runtime dependencies. - Intuitively, gcc shouldn't be in this list! Displaying the - printable strings in the hello binary shows that the out path - of gcc does indeed appear: - - - - - - This is why Nix added gcc. But why is that path present in the - first place? The answer is that it is the ld rpath: the list of - directories where libraries can be found at runtime. In other distributions, - this is usually not abused. But in Nix, we have to refer to particular versions - of libraries, and thus the rpath has an important role. - - - - The build process adds the gcc lib path thinking it may be useful - at runtime, but this isn't necessary. To address issues like these, Nix provides - a tool called patchelf, - which reduces the rpath to the paths that are actually used by the binary. - - - - Even after reducing the rpath, the hello binary would still - depend upon gcc because of some debugging information. This - unnecessarily increases the size of our runtime - dependencies. We'll explore how strip - can help us with that in the next section. - -
- -
- Another phase in the builder - - - We will add a new phase to our autotools builder. The builder has six - phases already: - - - - - - The "environment setup" phase - - - - - The "unpack phase": we unpack the sources in the current directory - (remember, Nix changes to a temporary directory first) - - - - - The "change directory" phase, where we change source root to the - directory that has been unpacked - - - - - The "configure" phase: ./configure - - - - - The "build" phase: make - - - - - The "install" phase: make install - - - - - - Now we will add a new phase after the installation phase, which we call - the "fixup" phase. At the end of the - builder.sh, we append: - - - - - - That is, for each file we run patchelf --shrink-rpath - and strip. Note that we used two new commands here, - find and patchelf. These must be - added to our derivation. - - - - Exercise: Add findutils - and patchelf to the baseInputs of - autotools.nix. - - - - Now, we rebuild hello.nix... - - - - - - and we see that glibc is a runtime dependency. This is - exactly what we wanted. - - - - The package is self-contained. This means that we can copy its closure onto - another machine and we will be able to run it. Remember, only a very few - components under the /nix/store are required to - run nix. - The hello binary will use the exact version of glibc - library and interpreter referred to in the binary, rather than the system one: - - - - - - Of course, the executable will run fine as long as everything is under the - /nix/store path. - -
- -
- Conclusion - - - We saw some of the tools Nix provides, along with their features. - In particular, we saw how Nix is able to compute runtime dependencies - automatically. This is not limited to only shared libraries, - but can also reference executables, scripts, Python libraries, and so - forth. - - - - Approaching builds in this way makes packages self-contained, ensuring - (apart from data and configuration) that copying the runtime closure onto - another machine is sufficient to run the program. This enables us to run programs - without installation using nix-shell, and forms the basis for - reliable deployment in the cloud. - -
- -
- Next pill - - - The next pill will introduce nix-shell. With - nix-build, we've always built derivations from - scratch: the source gets unpacked, configured, built, and installed. - But this can take a long time for large packages. What if we want to - apply some small changes and compile incrementally instead, yet still - want to keep a self-contained environment similar to nix-build? - nix-shell enables this. - -
-
diff --git a/pills/09/build-hello-nix.txt b/pills/09/build-hello-nix.txt deleted file mode 100644 index b364bd8..0000000 --- a/pills/09/build-hello-nix.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ nix-build hello.nix -[...] -$ nix-store -q --references result -/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 -/nix/store/md4a3zv0ipqzsybhjb8ndjhhga1dj88x-hello diff --git a/pills/09/find.txt b/pills/09/find.txt deleted file mode 100644 index 361ce92..0000000 --- a/pills/09/find.txt +++ /dev/null @@ -1 +0,0 @@ -find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null diff --git a/pills/09/instantiate-hello.txt b/pills/09/instantiate-hello.txt deleted file mode 100644 index 53d15b7..0000000 --- a/pills/09/instantiate-hello.txt +++ /dev/null @@ -1,8 +0,0 @@ -$ nix-instantiate hello.nix -/nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv -$ nix-store -r /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv -/nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello -$ nix-store -q --references /nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello -/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19 -/nix/store/8jm0wksask7cpf85miyakihyfch1y21q-gcc-4.8.3 -/nix/store/a42k52zwv6idmf50r9lps1nzwq9khvpf-hello diff --git a/pills/09/instantiate.txt b/pills/09/instantiate.txt deleted file mode 100644 index 9be974e..0000000 --- a/pills/09/instantiate.txt +++ /dev/null @@ -1,15 +0,0 @@ -$ nix-instantiate hello.nix -/nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv -$ nix-store -q --references /nix/store/z77vn965a59irqnrrjvbspiyl2rph0jp-hello.drv -/nix/store/0q6pfasdma4as22kyaknk4kwx4h58480-hello-2.10.tar.gz -/nix/store/1zcs1y4n27lqs0gw4v038i303pb89rw6-coreutils-8.21.drv -/nix/store/2h4b30hlfw4fhqx10wwi71mpim4wr877-gnused-4.2.2.drv -/nix/store/39bgdjissw9gyi4y5j9wanf4dbjpbl07-gnutar-1.27.1.drv -/nix/store/7qa70nay0if4x291rsjr7h9lfl6pl7b1-builder.sh -/nix/store/g6a0shr58qvx2vi6815acgp9lnfh9yy8-gnugrep-2.14.drv -/nix/store/jdggv3q1sb15140qdx0apvyrps41m4lr-bash-4.2-p45.drv -/nix/store/pglhiyp1zdbmax4cglkpz98nspfgbnwr-gnumake-3.82.drv -/nix/store/q9l257jn9lndbi3r9ksnvf4dr8cwxzk7-gawk-4.1.0.drv -/nix/store/rgyrqxz1ilv90r01zxl0sq5nq0cq7v3v-binutils-2.23.1.drv -/nix/store/qzxhby795niy6wlagfpbja27dgsz43xk-gcc-wrapper-4.8.3.drv -/nix/store/sk590g7fv53m3zp0ycnxsc41snc2kdhp-gzip-1.6.drv diff --git a/pills/09/ldd-hello.txt b/pills/09/ldd-hello.txt deleted file mode 100644 index 20c61e8..0000000 --- a/pills/09/ldd-hello.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ ldd result/bin/hello - linux-vdso.so.1 (0x00007fff11294000) - libc.so.6 => /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/libc.so.6 (0x00007f7ab7362000) - /nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib/ld-linux-x86-64.so.2 (0x00007f7ab770f000) diff --git a/pills/09/strings.txt b/pills/09/strings.txt deleted file mode 100644 index f766ab9..0000000 --- a/pills/09/strings.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ strings result/bin/hello|grep gcc -/nix/store/94n64qy99ja0vgbkf675nyk39g9b978n-glibc-2.19/lib:/nix/store/8jm0wksask7cpf85miyakihyfch1y21q-gcc-4.8.3/lib64 diff --git a/pills/10-developing-with-nix-shell.md b/pills/10-developing-with-nix-shell.md new file mode 100644 index 0000000..9164972 --- /dev/null +++ b/pills/10-developing-with-nix-shell.md @@ -0,0 +1,172 @@ +# Developing with `nix-shell` + +Welcome to the 10th Nix pill. In the previous [9th pill](09-automatic-runtime-dependencies.md) we saw one of the powerful features of Nix: automatic discovery of runtime dependencies. We also finalized the GNU `hello` package. + +In this pill, we will introduce the `nix-shell` tool and use it to hack on the GNU `hello` program. We will see how `nix-shell` gives us an isolated environment while we modify the source files of the project, similar to how `nix-build` gave us an isolated environment while building the derivation. + +Finally, we will modify our builder to work more ergonomically with a `nix-shell`-focused workflow. + +## What is `nix-shell`? + +The [nix-shell](https://nixos.org/manual/nix/stable/command-ref/nix-shell.html) tool drops us in a shell after setting up the environment variables necessary to hack on a derivation. It does not build the derivation; it only serves as a preparation so that we can run the build steps manually. + +Recall that in a nix environment, we don't have access to libraries or programs unless they have been installed with `nix-env`. However, installing libraries with `nix-env` is not good practice. We prefer to have isolated environments for development, which `nix-shell` provides for us. + +We can call `nix-shell` on any Nix expression which returns a derivation, but the resulting `bash` shell's `PATH` does not have the utilities we want: + +```console +$ nix-shell hello.nix +[nix-shell]$ make +bash: make: command not found +[nix-shell]$ echo $baseInputs +/nix/store/jff4a6zqi0yrladx3kwy4v6844s3swpc-gnutar-1.27.1 [...] +``` + +This shell is rather useless. It would be reasonable to expect that the GNU `hello` build inputs are available in `PATH`, including GNU `make`, but this is not the case. + +However, we do have the environment variables that we set in the derivation, like `$baseInputs`, `$buildInputs`, `$src`, and so on. + +This means that we can `source` our `builder.sh`, and it will build the derivation. You may get an error in the installation phase, because your user may not have the permission to write to `/nix/store`: + +```console +[nix-shell]$ source builder.sh +... +``` + +The derivation didn't install, but it did build. Note the following: + +- We sourced `builder.sh` and it ran all of the build steps, including setting up the `PATH` for us. + +- The working directory is no longer a temp directory created by `nix-build`, but is instead the directory in which we entered the shell. Therefore, `hello-2.10` has been unpacked in the current directory. + +We are able to `cd` into `hello-2.10` and type `make`, because `make` is now available. + +The take-away is that `nix-shell` drops us in a shell with the same (or very similar) environment used to run the builder. + +## A builder for nix-shell + +The previous steps require some manual commands to be run and are not optimized for a workflow centered on `nix-shell`. We will now improve our builder to be more `nix-shell` friendly. + +There are a few things that we would like to change. + +First, when we `source`d the `builder.sh` file, we obtained the file in the current directory. What we really wanted was the `builder.sh` that is stored in the nix store, as this is the file that would be used by `nix-build`. To achieve this, the correct technique is to pass an environment variable through the derivation. (Note that `$builder` is already defined, but it points to the bash executable rather than our `builder.sh`. Our `builder.sh` is passed as an argument to bash.) + +Second, we don't want to run the whole builder: we only want to setup the necessary environment for manually building the project. Thus, we can break `builder.sh` into two files: a `setup.sh` for setting up the environment, and the real `builder.sh` that `nix-build` expects. + +During our refactoring, we will wrap the build phases in functions to give more structure to our design. Additionally, we'll move the `set -e` to the builder file instead of the setup file. The `set -e` is annoying in `nix-shell`, as it will terminate the shell if an error is encountered (such as a mistyped command.) + +Here is our modified `autotools.nix`. Noteworthy is the `setup = ./setup.sh;` attribute in the derivation, which adds `setup.sh` to the nix store and correspondingly adds a `$setup` environment variable in the builder. + +```nix +pkgs: attrs: +let + defaultAttrs = { + builder = "${pkgs.bash}/bin/bash"; + args = [ ./builder.sh ]; + setup = ./setup.sh; + baseInputs = with pkgs; [ + gnutar + gzip + gnumake + gcc + coreutils + gawk + gnused + gnugrep + binutils.bintools + patchelf + findutils + ]; + buildInputs = [ ]; + system = builtins.currentSystem; + }; +in +derivation (defaultAttrs // attrs) +``` + +Thanks to that, we can split `builder.sh` into `setup.sh` and `builder.sh`. What `builder.sh` does is `source` `$setup` and call the `genericBuild` function. Everything else is just some changes to the bash script. + +Here is the modified `builder.sh`: + +```sh +set -e +source $setup +genericBuild +``` + +Here is the newly added `setup.sh`: + +```sh +unset PATH +for p in $baseInputs $buildInputs; do + export PATH=$p/bin${PATH:+:}$PATH +done + +function unpackPhase() { + tar -xzf $src + + for d in *; do + if [ -d "$d" ]; then + cd "$d" + break + fi + done +} + +function configurePhase() { + ./configure --prefix=$out +} + +function buildPhase() { + make +} + +function installPhase() { + make install +} + +function fixupPhase() { + find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null +} + +function genericBuild() { + unpackPhase + configurePhase + buildPhase + installPhase + fixupPhase +} +``` + +Finally, here is `hello.nix`: + +```nix +let + pkgs = import { }; + mkDerivation = import ./autotools.nix pkgs; +in +mkDerivation { + name = "hello"; + src = ./hello-2.12.1.tar.gz; +} +``` + +Now back to nix-shell: + +```console +$ nix-shell hello.nix +[nix-shell]$ source $setup +[nix-shell]$ +``` + +Now, for example, you can run `unpackPhase` which unpacks `$src` and enters the directory. And you can run commands like `./configure`, `make`, and so forth manually, or run phases with their respective functions. + +The process is that straightforward. `nix-shell` builds the `.drv` file and its input dependencies, then drops into a shell by setting up the environment variables necessary to build the `.drv`. In particular, the environment variables in the shell match those passed to the `derivation` function. + +## Conclusion + +With `nix-shell` we are able to drop into an isolated environment suitable for developing a project. This environment provides the necessary dependencies for the development shell, similar to how `nix-build` provides the necessary dependencies to a builder. Additionally, we can build and debug the project manually, executing step-by-step like we would in any other operating system. Note that we never installed tools such `gcc` or `make` system-wide; these tools and libraries are isolated and available per-build. + +## Next pill + +In the next pill, we will clean up the nix store. We have written and built derivations which add to the nix store, but until now we haven't worried about cleaning up the used space in the store. diff --git a/pills/10-developing-with-nix-shell.xml b/pills/10-developing-with-nix-shell.xml deleted file mode 100644 index e5dd5c1..0000000 --- a/pills/10-developing-with-nix-shell.xml +++ /dev/null @@ -1,234 +0,0 @@ - - - Developing with <command>nix-shell</command> - - - Welcome to the 10th Nix pill. In the previous - 9th pill we saw - one of the powerful features of Nix: automatic discovery of runtime - dependencies. We also finalized the GNU hello package. - - - - In this pill, we will introduce the nix-shell tool - and use it to hack on the GNU hello program. We will - see how nix-shell gives us an isolated environment - while we modify the source files of the project, similar to how - nix-build gave us an isolated environment while building - the derivation. - - - - Finally, we will modify our builder to work more ergonomically - with a nix-shell-focused workflow. - - -
- What is <command>nix-shell</command>? - - - The nix-shell - tool drops us in a shell after setting up the environment variables necessary - to hack on a derivation. It does not build the derivation; it - only serves as a preparation so that we can run the build steps manually. - - - - Recall that in a nix environment, we don't have access to libraries or - programs unless they have been installed with nix-env. - However, installing libraries with nix-env is not - good practice. We prefer to have isolated environments for development, which - nix-shell provides for us. - - - - We can call nix-shell on any Nix expression which - returns a derivation, but the resulting bash shell's - PATH does not have the utilities we want: - - - - - - This shell is rather useless. It would be reasonable to expect that the GNU - hello build inputs are available in PATH, including - GNU make, but this is not the case. - - - - However, we do have the environment variables that we set in the derivation, - like $baseInputs, $buildInputs, - $src, and so on. - - - - This means that we can source our - builder.sh, and it will build the derivation. - You may get an error in the installation phase, because your user may - not have the permission to write to /nix/store: - - - - - - The derivation didn't install, but it did build. Note the following: - - - - - - We sourced builder.sh and it ran all of the build - steps, including setting up the PATH for us. - - - - - The working directory is no longer a temp directory created by - nix-build, but is instead the directory in which - we entered the shell. Therefore, hello-2.10 has - been unpacked in the current directory. - - - - - - We are able to cd into hello-2.10 and type - make, because make is now available. - - - - The take-away is that nix-shell drops us in a shell with the - same (or very similar) environment used to run the builder. - -
- -
- A builder for nix-shell - - - The previous steps require some manual commands to be run and are not - optimized for a workflow centered on nix-shell. We - will now improve our builder to be more nix-shell friendly. - - - - There are a few things that we would like to change. - - - - First, when we sourced the builder.sh - file, we obtained the file in the current directory. What we really wanted - was the builder.sh that is stored in the nix store, - as this is the file that would be used by nix-build. - To achieve this, the correct technique is to pass an environment variable - through the derivation. (Note that $builder is - already defined, but it points to the bash executable rather than our - builder.sh. Our builder.sh is - passed as an argument to bash.) - - - - Second, we don't want to run the whole builder: we only want to setup - the necessary environment for manually building the project. Thus, we - can break builder.sh into two files: a - setup.sh for setting up the environment, and - the real builder.sh that nix-build - expects. - - - - During our refactoring, we will wrap the build phases in functions to - give more structure to our design. Additionally, we'll move the - set -e to the builder file instead of the setup file. - The set -e is annoying in nix-shell, - as it will terminate the shell if an error is encountered (such as - a mistyped command.) - - - - Here is our modified autotools.nix. - Noteworthy is the setup = ./setup.sh; attribute in the - derivation, which adds setup.sh to the nix store and - correspondingly adds a $setup environment variable in the builder. - - - - - - Thanks to that, we can split builder.sh into - setup.sh and builder.sh. What - builder.sh does is source - $setup and call the genericBuild function. - Everything else is just some changes to the bash script. - - - - Here is the modified builder.sh: - - - - - - Here is the newly added setup.sh: - - - - - - Finally, here is hello.nix: - - - - - - Now back to nix-shell: - - - - - - Now, for example, you can run unpackPhase which unpacks - $src and enters the directory. And you can run commands - like ./configure, make, and so forth - manually, or run phases with their respective functions. - - - - The process is that straightforward. nix-shell builds the - .drv file and its input dependencies, then drops into a shell - by setting up the environment variables necessary to build the .drv. - In particular, the environment variables in the shell match those passed - to the derivation function. - -
- -
- Conclusion - - - With nix-shell we are able to drop into an isolated - environment suitable for developing a project. This environment provides the necessary - dependencies for the development shell, similar to how - nix-build provides the necessary dependencies to a builder. - Additionally, we can build and debug the project manually, executing step-by-step - like we would in any other operating system. Note that we never installed tools - such gcc or make system-wide; these tools - and libraries are isolated and available per-build. - -
- -
- Next pill - - - In the next pill, we will clean up the nix store. We have written and built - derivations which add to the nix store, but until now we haven't worried - about cleaning up the used space in the store. - -
-
diff --git a/pills/10/autotools-nix.txt b/pills/10/autotools-nix.txt deleted file mode 100644 index fc4c43e..0000000 --- a/pills/10/autotools-nix.txt +++ /dev/null @@ -1,24 +0,0 @@ -pkgs: attrs: -let - defaultAttrs = { - builder = "${pkgs.bash}/bin/bash"; - args = [ ./builder.sh ]; - setup = ./setup.sh; - baseInputs = with pkgs; [ - gnutar - gzip - gnumake - gcc - coreutils - gawk - gnused - gnugrep - binutils.bintools - patchelf - findutils - ]; - buildInputs = [ ]; - system = builtins.currentSystem; - }; -in -derivation (defaultAttrs // attrs) diff --git a/pills/10/builder-sh.txt b/pills/10/builder-sh.txt deleted file mode 100644 index 91b081f..0000000 --- a/pills/10/builder-sh.txt +++ /dev/null @@ -1,3 +0,0 @@ -set -e -source $setup -genericBuild diff --git a/pills/10/hello-nix.txt b/pills/10/hello-nix.txt deleted file mode 100644 index 8c660f7..0000000 --- a/pills/10/hello-nix.txt +++ /dev/null @@ -1,8 +0,0 @@ -let - pkgs = import { }; - mkDerivation = import ./autotools.nix pkgs; -in -mkDerivation { - name = "hello"; - src = ./hello-2.12.1.tar.gz; -} diff --git a/pills/10/nix-shell-hello.txt b/pills/10/nix-shell-hello.txt deleted file mode 100644 index a1e5713..0000000 --- a/pills/10/nix-shell-hello.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ nix-shell hello.nix -[nix-shell]$ make -bash: make: command not found -[nix-shell]$ echo $baseInputs -/nix/store/jff4a6zqi0yrladx3kwy4v6844s3swpc-gnutar-1.27.1 [...] diff --git a/pills/10/nix-shell-source.txt b/pills/10/nix-shell-source.txt deleted file mode 100644 index 870f4e4..0000000 --- a/pills/10/nix-shell-source.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-shell hello.nix -[nix-shell]$ source $setup -[nix-shell]$ diff --git a/pills/10/setup-sh.txt b/pills/10/setup-sh.txt deleted file mode 100644 index 90e9840..0000000 --- a/pills/10/setup-sh.txt +++ /dev/null @@ -1,39 +0,0 @@ -unset PATH -for p in $baseInputs $buildInputs; do - export PATH=$p/bin${PATH:+:}$PATH -done - -function unpackPhase() { - tar -xzf $src - - for d in *; do - if [ -d "$d" ]; then - cd "$d" - break - fi - done -} - -function configurePhase() { - ./configure --prefix=$out -} - -function buildPhase() { - make -} - -function installPhase() { - make install -} - -function fixupPhase() { - find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null -} - -function genericBuild() { - unpackPhase - configurePhase - buildPhase - installPhase - fixupPhase -} diff --git a/pills/10/source-builder.txt b/pills/10/source-builder.txt deleted file mode 100644 index fb87b39..0000000 --- a/pills/10/source-builder.txt +++ /dev/null @@ -1,2 +0,0 @@ -[nix-shell]$ source builder.sh -... diff --git a/pills/11-garbage-collector.md b/pills/11-garbage-collector.md new file mode 100644 index 0000000..c523e88 --- /dev/null +++ b/pills/11-garbage-collector.md @@ -0,0 +1,135 @@ +# The Garbage Collector {#garbage-collector} + +Welcome to the 11th Nix pill. In the previous [10th pill](10-developing-with-nix-shell.md), we drew a parallel between the isolated build environment provided by `nix-build` and the isolated development shell provided by `nix-shell`. Using `nix-shell` allowed us to debug, modify, and manually build software using an environment that is almost identical to the one provided by `nix-build`. + +Today, we will stop focusing on packaging and instead look at a critical component of Nix: the garbage collector. When we use Nix tools, we are often building derivations. This includes `.drv` files as well as out paths. These artifacts go in the Nix store and take up space in our storage. Eventually we may wish to free up some space by removing derivations we no longer need. This is the focus of the 11th pill. By default, Nix takes a relatively conservative approach when automatically deciding which derivations are "needed". In this pill, we will also see a technique to conduct more destructive upgrade and deletion operations. + +## How does garbage collection work? + +Programming languages with garbage collectors use the concept of a set of "garbage collector (or 'GC') roots" to keep track of "live" objects. A GC root is an object that is always considered "live" (unless explicitly removed as GC root). The garbage collection process starts from the GC roots and proceeds by recursively marking object references as "live". All other objects can be collected and deleted. + +Instead of objects, Nix's garbage collection operates on store paths, [with the GC roots themselves being store paths](https://nixos.org/manual/nix/stable/package-management/garbage-collector-roots.html). . This approach is much more principled than traditional package managers such as `dpkg` or `rpm`, which may leave around unused packages or dangling files. + +The implementation is very simple and transparent to the user. The primary GC roots are stored under `/nix/var/nix/gcroots`. If there is a symlink to a store path, then the linked store path is a GC root. + +Nix allows this directory to have subdirectories: it will simply recursively traverse the subdirectories in search of symlinks to store paths. When a symlink is encountered, its target is added to the list of live store paths. + +In summary, Nix maintains a list of GC roots. These roots can then be used to compute a list of all live store paths. Any other store paths are considered dead. Deleting these paths is now straightforward. Nix first moves dead store paths to `/nix/store/trash`, which is an atomic operation. Afterwards, the trash is emptied. + +## Playing with the GC + +Before we begin we first run the [nix garbage collector](https://nixos.org/manual/nix/stable/command-ref/nix-collect-garbage.html) so that we have a clean setup for our experiments: + +```console +$ nix-collect-garbage +finding garbage collector roots... +[...] +deleting unused links... +note: currently hard linking saves -0.00 MiB +1169 store paths deleted, 228.43 MiB freed +``` + +If we run the garbage collector again it won't find anything new to delete, as we expect. After running the garbage collector, the nix store only contains paths with references from the GC roots. + +We now install a new program, `bsd-games`, inspect its store path, and examine its GC root. The `nix-store -q --roots` command is used to query the GC roots that refer to a given derivation. In this case, our current user environment refers to `bsd-games`: + +```console +$ nix-env -iA nixpkgs.bsdgames +$ readlink -f `which fortune` +/nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17/bin/fortune +$ nix-store -q --roots `which fortune` +/nix/var/nix/profiles/default-9-link +$ nix-env --list-generations +[...] + 9 2014-08-20 12:44:14 (current) +``` + +Now we remove it and run the garbage collector, and note that `bsd-games` is still in the nix store: + +```console +$ nix-env -e bsd-games +uninstalling `bsd-games-2.17' +$ nix-collect-garbage +[...] +$ ls /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17 +bin share +``` + +The old generation is still in the nix store because it is a GC root. As we will see below, all profiles and their generations are automatically GC roots. + +Removing a GC root is simple. In our case, we delete the generation that refers to `bsd-games`, run the garbage collector, and note that `bsd-games` is no longer in the nix store: + +```console +$ rm /nix/var/nix/profiles/default-9-link +$ nix-env --list-generations +[...] + 8 2014-07-28 10:23:24 + 10 2014-08-20 12:47:16 (current) +$ nix-collect-garbage +[...] +$ ls /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17 +ls: cannot access /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17: No such file or directory +``` + +Note: `nix-env --list-generations` does not rely on any particular metadata. It is able to list generations based solely on the file names under the profiles directory. + +Note that we removed the link from `/nix/var/nix/profiles`, not from `/nix/var/nix/gcroots`. In addition to the latter, Nix treats `/nix/var/nix/profiles` as a GC root. This is useful because it means that any profile and its generations are GC roots. Other paths are considered GC roots as well; for example, `/run/booted-system` on NixOS. The command `nix-store --gc --print-roots` prints all paths considered as GC roots when running the garbage collector. + +## Indirect roots + +Recall that building the GNU `hello` package with `nix-build` produces a `result` symlink in the current directory. Despite the garbage collection done above, the `hello` program is still working. Therefore, it has not been garbage collected. Since there is no other derivation that depends upon the GNU `hello` package, it must be a GC root. + +In fact, `nix-build` automatically adds the `result` symlink as a GC root. Note that this is not the built derivation, but the symlink itself. These GC roots are added under `/nix/var/nix/gcroots/auto`. + +```console +$ ls -l /nix/var/nix/gcroots/auto/ +total 8 +drwxr-xr-x 2 nix nix 4096 Aug 20 10:24 ./ +drwxr-xr-x 3 nix nix 4096 Jul 24 10:38 ../ +lrwxrwxrwx 1 nix nix 16 Jul 31 10:51 xlgz5x2ppa0m72z5qfc78b8wlciwvgiz -> /home/nix/result/ +``` + +The name of the GC root symlink is not important to us at this time. What is important is that such a symlink exists and points to `/home/nix/result`. This is called an **indirect GC root**. A GC root is considered indirect if its specification is outside of `/nix/var/nix/gcroots`. In this case, this means that the target of the `result` symlink will not be garbage collected. + +To remove a derivation considered "live" by an indirect GC root, there are two possibilities: + +- Remove the indirect GC root from `/nix/var/nix/gcroots/auto`. + +- Remove the `result` symlink. + +In the first case, the derivation will be deleted from the nix store during garbage collection, and `result` becomes a dangling symlink. In the second case, the derivation is removed as well as the indirect root in `/nix/var/nix/gcroots/auto`. + +Running `nix-collect-garbage` after deleting the GC root or the indirect GC root will remove the derivation from the store. + +## Cleanup everything + +The main source of software duplication in the nix store comes from GC roots, due to `nix-build` and profile generations. Running `nix-build` results in a GC root for the build that refers to a specific version of specific libraries, such as `glibc`. After an upgrade, we must delete the previous build if we want the garbage collector to remove the corresponding derivation, as well as if we want old dependencies cleaned up. + +The same holds for profiles. Manipulating the `nix-env` profile will create further generations. Old generations refer to old software, thus increasing duplication in the nix store after an upgrade. + +Other systems typically "forget" everything about their previous state after an upgrade. With Nix, we can perform this type of upgrade (having Nix remove all old derivations, including old generations), but we do so manually. There are four steps to doing this: + +- First, we download a new version of the nixpkgs channel, which holds the description of all the software. This is done via `nix-channel --update`. + +- Then we upgrade our installed packages with `nix-env -u`. This will bring us into a new generation with updated software. + +- Then we remove all the indirect roots generated by `nix-build`: beware, as this will result in dangling symlinks. A smarter strategy would also remove the target of those symlinks. + +- Finally, the `-d` option of `nix-collect-garbage` is used to delete old generations of all profiles, then collect garbage. After this, you lose the ability to rollback to any previous generation. It is important to ensure the new generation is working well before running this command. + +The four steps are shown below: + +```console +$ nix-channel --update +$ nix-env -u --always +$ rm /nix/var/nix/gcroots/auto/* +$ nix-collect-garbage -d +``` + +## Conclusion + +Garbage collection in Nix is a powerful mechanism to clean up your system. The `nix-store` commands allow us to know why a certain derivation is present in the nix store, and whether or not it is eligible for garbage collection. We also saw how to conduct more destructive deletion and upgrade operations. + +## Next pill + +In the next pill, we will package another project and introduce the "inputs" design pattern. We've only played with a single derivation until now; however we'd like to start organizing a small repository of software. The "inputs" pattern is widely used in nixpkgs; it allows us to decouple derivations from the repository itself and increase customization opportunities. diff --git a/pills/11-garbage-collector.xml b/pills/11-garbage-collector.xml deleted file mode 100644 index 2132d69..0000000 --- a/pills/11-garbage-collector.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - The Garbage Collector - - - Welcome to the 11th Nix pill. In the previous - 10th pill, we drew - a parallel between the isolated build environment provided by - nix-build and the isolated development shell provided by - nix-shell. Using nix-shell allowed us - to debug, modify, and manually build software using an environment that - is almost identical to the one provided by nix-build. - - - - Today, we will stop focusing on packaging and instead look at a critical - component of Nix: the garbage collector. When we use Nix tools, we are - often building derivations. This includes .drv files as well as - out paths. These artifacts go in the Nix store and take up space in our storage. - Eventually we may wish to free up some space by removing derivations we no longer - need. This is the focus of the 11th pill. - - By default, Nix takes a relatively conservative approach when automatically - deciding which derivations are "needed". In this pill, we will also see - a technique to conduct more destructive upgrade and deletion operations. - - - -
- How does garbage collection work? - - - Programming languages with garbage collectors use the concept of a set of - "garbage collector (or 'GC') roots" to keep track of "live" objects. - A GC root is an object that is always considered "live" (unless explicitly - removed as GC root). The garbage collection process starts from the GC roots - and proceeds by recursively marking object references as "live". All other - objects can be collected and deleted. - - - - Instead of objects, Nix's garbage collection operates on store paths, with the GC roots themselves being store paths. -. This approach is much more principled than traditional package - managers such as dpkg or rpm, which may - leave around unused packages or dangling files. - - - - The implementation is very simple and transparent to the user. The primary - GC roots are stored under /nix/var/nix/gcroots. If there - is a symlink to a store path, then the linked store path is a GC root. - - - - Nix allows this directory to have subdirectories: it will simply recursively - traverse the subdirectories in search of symlinks to store paths. When - a symlink is encountered, its target is added to the list of live store - paths. - - - - In summary, Nix maintains a list of GC roots. These roots can then be - used to compute a list of all live store paths. Any other store - paths are considered dead. Deleting these paths is now straightforward. - Nix first moves dead store paths to /nix/store/trash, - which is an atomic operation. Afterwards, the trash is emptied. - -
- -
- Playing with the GC - - - Before we begin we first run the - nix garbage collector - so that we have a clean setup for our experiments: - - - - - - If we run the garbage collector again it won't find anything new to delete, - as we expect. After running the garbage collector, the nix store only contains - paths with references from the GC roots. - - - - We now install a new program, bsd-games, inspect its - store path, and examine its GC root. The nix-store -q --roots - command is used to query the GC roots that refer to a given derivation. In this - case, our current user environment refers to bsd-games: - - - - - - Now we remove it and run the garbage collector, and note that bsd-games - is still in the nix store: - - - - - - The old generation is still in the nix store because it is a GC root. - As we will see below, all profiles and their generations are automatically - GC roots. - - - - Removing a GC root is simple. In our case, we delete the generation that - refers to bsd-games, run the garbage collector, and note - that bsd-games is no longer in the nix store: - - - - - - Note: - nix-env --list-generations does not rely on any - particular metadata. It is able to list generations based solely on the - file names under the profiles directory. - - - - Note that we removed the link from - /nix/var/nix/profiles, not from - /nix/var/nix/gcroots. In addition to the latter, - Nix treats /nix/var/nix/profiles as a GC root. - This is useful because it means that any profile and its generations - are GC roots. Other paths are considered GC roots as well; for example, - /run/booted-system on NixOS. - The command nix-store --gc --print-roots prints all - paths considered as GC roots when running the garbage collector. - - -
- -
- Indirect roots - - - Recall that building the GNU hello package with - nix-build produces a result - symlink in the current directory. Despite the garbage collection done - above, the hello program is still working. Therefore, - it has not been garbage collected. Since there is no other - derivation that depends upon the GNU hello package, it must be a - GC root. - - - - In fact, nix-build automatically adds the - result symlink as a GC root. Note that this - is not the built derivation, but the symlink itself. These GC roots - are added under /nix/var/nix/gcroots/auto. - - - - - - The name of the GC root symlink is not important to us at this time. - What is important is that such a symlink exists and points to - /home/nix/result. This is called an - indirect GC root. A GC root is - considered indirect if its specification is outside of - /nix/var/nix/gcroots. In this case, this means - that the target of the result symlink will - not be garbage collected. - - - - To remove a derivation considered "live" by an indirect GC root, - there are two possibilities: - - - - - - Remove the indirect GC root from - /nix/var/nix/gcroots/auto. - - - - - Remove the result symlink. - - - - - - In the first case, the derivation will be deleted from the nix store during - garbage collection, and result becomes a dangling symlink. - In the second case, the derivation is removed as well as the indirect root in - /nix/var/nix/gcroots/auto. - - - - Running nix-collect-garbage after deleting the GC root - or the indirect GC root will remove the derivation from the store. - -
- -
- Cleanup everything - - - The main source of software duplication in the nix store comes from - GC roots, due to nix-build and profile generations. - Running nix-build results in a GC root for the build - that refers to a specific version of specific libraries, such as - glibc. After an upgrade, we must delete the previous build - if we want the garbage collector to remove the corresponding derivation, - as well as if we want old dependencies cleaned up. - - - - The same holds for profiles. Manipulating the nix-env - profile will create further generations. Old generations refer to old - software, thus increasing duplication in the nix store after an upgrade. - - - - Other systems typically "forget" everything about their previous state after - an upgrade. With Nix, we can perform this type of upgrade (having Nix remove - all old derivations, including old generations), but we do so manually. - There are four steps to doing this: - - - - - First, we download a new version of the nixpkgs channel, which holds the - description of all the software. This is done via - nix-channel --update. - - - - - Then we upgrade our installed packages with nix-env -u. - This will bring us into a new generation with updated software. - - - - - Then we remove all the indirect roots generated by - nix-build: beware, as this will result in dangling - symlinks. A smarter strategy would also remove the target of those symlinks. - - - - - Finally, the -d option of - nix-collect-garbage is used to delete old generations - of all profiles, then collect garbage. After this, you lose the ability - to rollback to any previous generation. It is important to ensure the new - generation is working well before running this command. - - - - - The four steps are shown below: - - - - -
- -
- Conclusion - - - Garbage collection in Nix is a powerful mechanism to clean up your system. - The nix-store commands allow us to know why a certain - derivation is present in the nix store, and whether or not it is eligible - for garbage collection. We also saw how to conduct more destructive deletion - and upgrade operations. - -
- -
- Next pill - - - In the next pill, we will package another project and introduce the "inputs" - design pattern. We've only played with a single derivation until now; - however we'd like to start organizing a small repository of software. The - "inputs" pattern is widely used in nixpkgs; it allows us to decouple - derivations from the repository itself and increase customization - opportunities. - -
-
diff --git a/pills/11/channel-update.txt b/pills/11/channel-update.txt deleted file mode 100644 index 27b82e0..0000000 --- a/pills/11/channel-update.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-channel --update -$ nix-env -u --always -$ rm /nix/var/nix/gcroots/auto/* -$ nix-collect-garbage -d diff --git a/pills/11/install-bsd-games.txt b/pills/11/install-bsd-games.txt deleted file mode 100644 index 1c068b2..0000000 --- a/pills/11/install-bsd-games.txt +++ /dev/null @@ -1,8 +0,0 @@ -$ nix-env -iA nixpkgs.bsdgames -$ readlink -f `which fortune` -/nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17/bin/fortune -$ nix-store -q --roots `which fortune` -/nix/var/nix/profiles/default-9-link -$ nix-env --list-generations -[...] - 9 2014-08-20 12:44:14 (current) diff --git a/pills/11/ls-gcroots-auto.txt b/pills/11/ls-gcroots-auto.txt deleted file mode 100644 index b66ef0c..0000000 --- a/pills/11/ls-gcroots-auto.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ ls -l /nix/var/nix/gcroots/auto/ -total 8 -drwxr-xr-x 2 nix nix 4096 Aug 20 10:24 ./ -drwxr-xr-x 3 nix nix 4096 Jul 24 10:38 ../ -lrwxrwxrwx 1 nix nix 16 Jul 31 10:51 xlgz5x2ppa0m72z5qfc78b8wlciwvgiz -> /home/nix/result/ diff --git a/pills/11/nix-collect-garbage.txt b/pills/11/nix-collect-garbage.txt deleted file mode 100644 index 9d2b406..0000000 --- a/pills/11/nix-collect-garbage.txt +++ /dev/null @@ -1,6 +0,0 @@ -$ nix-collect-garbage -finding garbage collector roots... -[...] -deleting unused links... -note: currently hard linking saves -0.00 MiB -1169 store paths deleted, 228.43 MiB freed diff --git a/pills/11/remove-bsd-games.txt b/pills/11/remove-bsd-games.txt deleted file mode 100644 index 49f0912..0000000 --- a/pills/11/remove-bsd-games.txt +++ /dev/null @@ -1,6 +0,0 @@ -$ nix-env -e bsd-games -uninstalling `bsd-games-2.17' -$ nix-collect-garbage -[...] -$ ls /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17 -bin share diff --git a/pills/11/remove-gen-9.txt b/pills/11/remove-gen-9.txt deleted file mode 100644 index a7084b1..0000000 --- a/pills/11/remove-gen-9.txt +++ /dev/null @@ -1,9 +0,0 @@ -$ rm /nix/var/nix/profiles/default-9-link -$ nix-env --list-generations -[...] - 8 2014-07-28 10:23:24 - 10 2014-08-20 12:47:16 (current) -$ nix-collect-garbage -[...] -$ ls /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17 -ls: cannot access /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17: No such file or directory diff --git a/pills/12-inputs-design-pattern.md b/pills/12-inputs-design-pattern.md new file mode 100644 index 0000000..0efcd59 --- /dev/null +++ b/pills/12-inputs-design-pattern.md @@ -0,0 +1,255 @@ +# Package Repositories and the Inputs Design Pattern {#inputs-design-pattern} + +Welcome to the 12th Nix pill. In the previous [11th pill](11-garbage-collector.md), we stopped packaging and cleaned up the system with the garbage collector. + +This time, we will resume packaging and improve different aspects of it. We will also demonstrate how to create a repository of multiple packages. + +## Repositories in Nix + +Package repositories in Nix arose naturally from the need to organize packages. There is no preset directory structure or packaging policy prescribed by Nix itself; Nix, as a full, functional programming language, is powerful enough to support multiple different repository formats. + +Over time, the `nixpkgs` repository evolved a particular structure. This structure reflects the history of Nix as well as the design patterns adopted by its users as useful tools in building and organizing packages. Below, we will examine some of these patterns in detail. + +## The single repository pattern + +Different operating system distributions have different opinions about how package repositories should be organized. Systems like Debian scatter packages in several small repositories (which tends to make tracking interdependent changes more difficult, and hinders contributions to the repositories), while systems like Gentoo put all package descriptions in a single repository. + +Nix follows the "single repository" pattern by placing all descriptions of all packages into [nixpkgs](https://github.com/NixOS/nixpkgs). This approach has proven natural and attractive for new contributions. + +For the rest of this pill, we will adopt the single repository pattern. The natural implementation in Nix is to create a top-level Nix expression, followed by one expression for each package. The top-level expression imports and combines all package expressions in an attribute set mapping names to packages. + +In some programming languages, such an approach \-- including every possible package description in a single data structure \-- would be untenable due to the language needing to load the entire data structure into memory before operating on it. Nix, however, is a lazy language and only evaluates what is needed. + +## Packaging `graphviz` + +We have already packaged GNU `hello`. Next, we will package a graph-drawing program called `graphviz` so that we can create a repository containing multiple packages. The `graphviz` package was selected because it uses the standard autotools build system and requires no patching. It also has optional dependencies, which will give us an opportunity to illustrate a technique to configure builds to a particular situation. + +First, we download `graphviz` from [gitlab](https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.49.3/graphviz-2.49.3.tar.gz). The `graphviz.nix` expression is straightforward: + +```nix +let + pkgs = import { }; + mkDerivation = import ./autotools.nix pkgs; +in +mkDerivation { + name = "graphviz"; + src = ./graphviz-2.49.3.tar.gz; +} +``` + +If we build the project with `nix-build graphviz.nix`, we will get runnable binaries under `result/bin`. Notice how we reused the same `autotools.nix` of `hello.nix.` + +By default, `graphviz` does not compile with the ability to produce `png` files. Thus, the derivation above will build a binary supporting only the native output formats, as we see below: + +```console +$ echo 'graph test { a -- b }'|result/bin/dot -Tpng -o test.png +Format: "png" not recognized. Use one of: canon cmap [...] +``` + +If we want to produce a `png` file with `graphviz`, we must add it to our derivation. The place to do so is in `autotools.nix`, where we created a `buildInputs` variable that gets concatenated to `baseInputs`. This is the exact reason for this variable: to allow users of `autotools.nix` to add additional inputs from package expressions. + +Version 2.49 of `graphviz` has several plugins to output `png`. For simplicity, we will use `libgd`. + +## Passing library information to `pkg-config` via environment variables + +The `graphviz` configuration script uses `pkg-config` to specify which flags are passed to the compiler. Since there is no global location for libraries, we need to tell `pkg-config` where to find its description files, which tell the configuration script where to find headers and libraries. + +In classic POSIX systems, `pkg-config` just finds the `.pc` files of all installed libraries in system folders like `/usr/lib/pkgconfig`. However, these files are not present in the isolated environments presented to Nix. + +As an alternative, we can inform `pkg-config` about the location of libraries via the `PKG_CONFIG_PATH` environment variable. We can populate this environment variable using the same trick we used for `PATH`: automatically filling the variables from `buildInputs`. This is the relevant snippet of `setup.sh`: + +```sh +for p in $baseInputs $buildInputs; do + if [ -d $p/bin ]; then + export PATH="$p/bin${PATH:+:}$PATH" + fi + if [ -d $p/lib/pkgconfig ]; then + export PKG_CONFIG_PATH="$p/lib/pkgconfig${PKG_CONFIG_PATH:+:}$PKG_CONFIG_PATH" + fi +done +``` + +Now if we add derivations to `buildInputs`, their `lib/pkgconfig` and `bin` paths are automatically added in `setup.sh`. + +## Completing graphviz with `gd` + +Below, we finish the expression for `graphviz` with `gd` support. Note the use of the `with` expression in `buildInputs` to avoid repeating `pkgs`: + +```nix +let + pkgs = import { }; + mkDerivation = import ./autotools.nix pkgs; +in +mkDerivation { + name = "graphviz"; + src = ./graphviz-2.49.3.tar.gz; + buildInputs = with pkgs; [ + pkg-config + (pkgs.lib.getLib gd) + (pkgs.lib.getDev gd) + ]; +} +``` + +We add `pkg-config` to the derivation to make this tool available for the configure script. As `gd` is a package with [split outputs](https://nixos.org/manual/nixpkgs/stable/#sec-multiple-outputs-), we need to add both the library and development outputs. + +After building, `graphviz` can now create `png`s. + +## The repository expression + +Now that we have two packages, we want to combine them into a single repository. To do so, we'll mimic what `nixpkgs` does: we will create a single attribute set containing derivations. This attribute set can then be imported, and derivations can be selected by accessing the top-level attribute set. + +Using this technique we are able to abstract from the file names. Instead of referring to a package by `REPO/some/sub/dir/package.nix`, this technique allows us to select a derivation as `importedRepo.package` (or `pkgs.package` in our examples). + +To begin, create a default.nix in the current directory: + +```nix +{ + hello = import ./hello.nix; + graphviz = import ./graphviz.nix; +} +``` + +This file is ready to use with `nix repl`: + +```console +$ nix repl +nix-repl> :l default.nix +Added 2 variables. +nix-repl> hello +«derivation /nix/store/dkib02g54fpdqgpskswgp6m7bd7mgx89-hello.drv» +nix-repl> graphviz +«derivation /nix/store/zqv520v9mk13is0w980c91z7q1vkhhil-graphviz.drv» +``` + +With `nix-build`, we can pass the -A option to access an attribute of the set from the given `.nix` expression: + +```console +$ nix-build default.nix -A hello +[...] +$ result/bin/hello +Hello, world! +``` + +The `default.nix` file is special. When a directory contains a `default.nix` file, it is used as the implicit nix expression of the directory. This, for example, allows us to run `nix-build -A hello` without specifying `default.nix` explicitly. + +We can now use `nix-env` to install the package into our user environment: + +```console +$ nix-env -f . -iA graphviz +[...] +$ dot -V +``` + +Taking a closer look at the above command, we see the following options: + +- The -f option is used to specify the expression to use. In this case, the expression is the `./default.nix` of the current directory. + +- The -i option stands for "installation". + +- The -A is the same as above for `nix-build`. + +We reproduced the very basic behavior of `nixpkgs`: combining multiple derivations into a single, top-level attribute set. + +## The inputs pattern + +The approach we've taken so far has a few problems: + +- First, `hello.nix` and `graphviz.nix` are dependent on `nixpkgs`, which they import directly. A better approach would be to pass in `nixpkgs` as an argument, as we did in `autotools.nix`. + +- Second, we don't have a straightforward way to compile different variants of the same software, such as `graphviz` with or without `libgd` support. + +- Third, we don't have a way to test `graphviz` with a particular `libgd` version. + +Until now, our approach to addressing the above problems has been inadequate and required changing the nix expression to match our needs. With the `inputs` pattern, we provide another answer: let the user change the `inputs` of the expression. + +When we talk about "the inputs of an expression", we are referring to the set of derivations needed to build that expression. In this case: + +- `mkDerivation` from `autotools`. Recall that `mkDerivation` has an implicit dependency on the toolchain. + +- `libgd` and its dependencies. + +The `./src` directory is also an input, but we wouldn't change the source from the caller. In `nixpkgs` we prefer to write another expression for version bumps (e.g. because patches or different inputs are needed). + +Our goal is to make package expressions independent of the repository. To achieve this, we use functions to declare inputs for a derivation. For example, with `graphviz.nix`, we make the following changes to make the derivation independent of the repository and customizable: + +```nix +{ mkDerivation, lib, gdSupport ? true, gd, pkg-config }: + +mkDerivation { + name = "graphviz"; + src = ./graphviz-2.49.3.tar.gz; + buildInputs = + if gdSupport + then [ + pkg-config + (lib.getLib gd) + (lib.getDev gd) + ] + else []; +} +``` + +Recall that "`{...}: ...`" is the syntax for defining functions accepting an attribute set as argument; the above snippet just defines a function. + +We made `gd` and its dependencies optional. If `gdSupport` is true (which it is by default), we will fill `buildInputs` and `graphviz` will be built with `gd` support. Otherwise, if an attribute set is passed with `gdSupport = false;`, the build will be completed without `gd` support. + +Going back to back to `default.nix`, we modify our expression to utilize the inputs pattern: + +```nix +let + pkgs = import { }; + mkDerivation = import ./autotools.nix pkgs; +in +with pkgs; +{ + hello = import ./hello.nix { inherit mkDerivation; }; + graphviz = import ./graphviz.nix { + inherit + mkDerivation + lib + gd + pkg-config + ; + }; + graphvizCore = import ./graphviz.nix { + inherit + mkDerivation + lib + gd + pkg-config + ; + gdSupport = false; + }; +} +``` + +We factorized the import of `nixpkgs` and `mkDerivation`, and also added a variant of `graphviz` with `gd` support disabled. The result is that both `hello.nix` (left as an exercise for the reader) and `graphviz.nix` are independent of the repository and customizable by passing specific inputs. + +If we wanted to build `graphviz` with a specific version of `gd`, it would suffice to pass `gd = ...;`. + +If we wanted to change the toolchain, we would simply pass a different `mkDerivation` function. + +Let's talk a closer look at the snippet and dissect the syntax: + +- The entire expression in `default.nix` returns an attribute set with the keys `hello`, `graphviz`, and `graphvizCore`. + +- With "`let`", we define some local variables. + +- We bring `pkgs` into the scope when defining the package set. This saves us from having to type `pkgs`" repeatedly. + +- We import `hello.nix` and `graphviz.nix`, which each return a function. We call the functions with a set of inputs to get back the derivation. + +- The "`inherit x`" syntax is equivalent to "`x = x`". This means that the "`inherit gd`" here, combined with the above "`with pkgs;`", is equivalent to "`gd = pkgs.gd`". + +The entire repository of this can be found at the [pill 12](https://gist.github.com/tfc/ca800a444b029e85a14e530c25f8e872) gist. + +## Conclusion + +The "`inputs`" pattern allows our expressions to be easily customizable through a set of arguments. These arguments could be flags, derivations, or any other customizations enabled by the nix language. Our package expressions are simply functions: there is no extra magic present. + +The "`inputs`" pattern also makes the expressions independent of the repository. Given that we pass all needed information through arguments, it is possible to use these expressions in any other context. + +## Next pill + +In the next pill, we will talk about the "`callPackage`" design pattern. This removes the tedium of specifying the names of the inputs twice: once in the top-level `default.nix`, and once in the package expression. With `callPackage`, we will implicitly pass the necessary inputs from the top-level expression. diff --git a/pills/12-inputs-design-pattern.xml b/pills/12-inputs-design-pattern.xml deleted file mode 100644 index d2fa330..0000000 --- a/pills/12-inputs-design-pattern.xml +++ /dev/null @@ -1,361 +0,0 @@ - - - Package Repositories and the Inputs Design Pattern - - Welcome to the 12th Nix pill. In the previous 11th - pill, we stopped packaging and cleaned up the system with the garbage collector. - - - This time, we will resume packaging and improve different aspects of it. We will also - demonstrate how to create a repository of multiple packages. - -
- Repositories in Nix - - Package repositories in Nix arose naturally from the need to organize packages. - There is no preset directory structure or packaging policy prescribed by Nix itself; - Nix, as a full, functional programming language, is powerful enough to support - multiple different repository formats. - - - - Over time, the nixpkgs repository evolved a particular - structure. This structure reflects the history of Nix as well as the design - patterns adopted by its users as useful tools in building and organizing - packages. Below, we will examine some of these patterns in detail. - -
-
- The single repository pattern - - Different operating system distributions have different opinions about how - package repositories should be organized. Systems like Debian scatter packages - in several small repositories (which tends to make tracking interdependent - changes more difficult, and hinders contributions to the repositories), - while systems like Gentoo put all package descriptions in a single repository. - - - Nix follows the "single repository" pattern by placing all descriptions of all - packages into nixpkgs. - This approach has proven natural and attractive for new contributions. - - - For the rest of this pill, we will adopt the single repository pattern. The - natural implementation in Nix is to create a top-level Nix expression, followed - by one expression for each package. The top-level expression imports and combines - all package expressions in an attribute set mapping names to packages. - - - In some programming languages, such an approach -- including every possible - package description in a single data structure -- would be untenable due - to the language needing to load the entire data structure into memory before - operating on it. Nix, however, is a lazy language and only evaluates what is - needed. - -
-
- Packaging <code>graphviz</code> - - We have already packaged GNU hello. Next, we will package a - graph-drawing program called graphviz so that we can - create a repository containing multiple packages. The graphviz - package was selected because it uses the standard autotools build system and - requires no patching. It also has optional dependencies, which will give us - an opportunity to illustrate a technique to configure builds to a particular - situation. - - - First, we download graphviz from gitlab. The graphviz.nix expression is straightforward: - - - - If we build the project with nix-build graphviz.nix, we will get runnable binaries under result/bin. Notice how we reused the same autotools.nix of hello.nix. - - - By default, graphviz does not compile with the ability to produce - png files. Thus, the derivation above will build a binary - supporting only the native output formats, as we see below: - - - - If we want to produce a png file with graphviz, we - must add it to our derivation. The place to do so is - in autotools.nix, where we created a - buildInputs variable that gets concatenated to - baseInputs. This is the exact reason for this variable: to - allow users of autotools.nix to add additional inputs - from package expressions. - - - Version 2.49 of graphviz has several plugins to output - png. For simplicity, we will use libgd. - -
-
- Passing library information to <command>pkg-config</command> via environment - variables - - The graphviz configuration script uses pkg-config - to specify which flags are passed to the compiler. Since there is no global location - for libraries, we need to tell pkg-config where to find - its description files, which tell the configuration script where to find - headers and libraries. - - - In classic POSIX systems, pkg-config just finds the - .pc files of all installed libraries in system folders - like /usr/lib/pkgconfig. However, these files - are not present in the isolated environments presented to Nix. - - - As an alternative, we can inform pkg-config about - the location of libraries via the PKG_CONFIG_PATH - environment variable. We can populate this environment variable - using the same trick we used for PATH: - automatically filling the variables from buildInputs. - This is the relevant snippet of setup.sh: - - - - Now if we add derivations to buildInputs, their - lib/pkgconfig and bin paths - are automatically added in setup.sh. - -
- -
- Completing graphviz with <code>gd</code> - - Below, we finish the expression for graphviz with gd support. - Note the use of the with expression in buildInputs to avoid repeating pkgs: - - - - We add pkg-config to the derivation to make this tool - available for the configure script. As gd is a package - with split outputs, - we need to add both the library and development outputs. - - - After building, graphviz can now create pngs. - -
-
- The repository expression - - Now that we have two packages, we want to combine them into a single repository. - To do so, we'll mimic what nixpkgs does: we will create - a single attribute set containing derivations. This attribute set can - then be imported, and derivations can be selected by accessing the - top-level attribute set. - - - Using this technique we are able to abstract from the file names. - Instead of referring to a package by REPO/some/sub/dir/package.nix, - this technique allows us to select a derivation as - importedRepo.package (or pkgs.package - in our examples). - - - To begin, create a default.nix in the current directory: - - - - This file is ready to use with nix repl: - - - - With nix-build, we can pass the -A option to - access an attribute of the set from the given .nix expression: - - - - The default.nix file is special. When a directory - contains a default.nix file, it is used as the implicit - nix expression of the directory. This, for example, allows us to run - nix-build -A hello without specifying - default.nix explicitly. - - - We can now use nix-env to install the package into our - user environment: - - - - Taking a closer look at the above command, we see the following options: - - - - The -f option is used to specify the expression to use. In this case, - the expression is the ./default.nix of the current directory. - - - The -i option stands for "installation". - - - The -A is the same as above for nix-build. - - - - - We reproduced the very basic behavior of nixpkgs: combining - multiple derivations into a single, top-level attribute set. - -
-
- The inputs pattern - - The approach we've taken so far has a few problems: - - - - First, hello.nix and graphviz.nix are - dependent on nixpkgs, which they import directly. - A better approach would be to pass in nixpkgs as an argument, - as we did in autotools.nix. - - - - Second, we don't have a straightforward way to compile different variants - of the same software, such as graphviz with or without - libgd support. - - - - Third, we don't have a way to test graphviz - with a particular libgd version. - - - - - Until now, our approach to addressing the above problems has been inadequate - and required changing the nix expression to match our needs. With the - inputs pattern, we provide another answer: let the user - change the inputs of the expression. - - - When we talk about "the inputs of an expression", we are referring to the - set of derivations needed to build that expression. In this case: - - - mkDerivation from autotools. Recall - that mkDerivation has an implicit dependency on - the toolchain. - - - libgd and its dependencies. - - - - - The ./src directory is also an input, - but we wouldn't change the source from the caller. - In nixpkgs we prefer to write another expression - for version bumps (e.g. because patches or different inputs are needed). - - - - Our goal is to make package expressions independent of the repository. To - achieve this, we use functions to declare inputs for a derivation. For example, - with graphviz.nix, we make the following changes to make - the derivation independent of the repository and customizable: - - - - Recall that "{...}: ..." is the syntax for defining functions - accepting an attribute set as argument; the above snippet just defines a function. - - - - We made gd and its dependencies optional. If gdSupport - is true (which it is by default), we will fill buildInputs and - graphviz will be built with gd support. Otherwise, if - an attribute set is passed with gdSupport = false;, the build - will be completed without gd support. - - - Going back to back to default.nix, we modify our expression - to utilize the inputs pattern: - - - - We factorized the import of nixpkgs and - mkDerivation, and also added a variant of graphviz - with gd support disabled. The result is that both - hello.nix (left as an exercise for the reader) and - graphviz.nix are independent of the repository and - customizable by passing specific inputs. - - - If we wanted to build graphviz with a specific version of - gd, it would suffice to pass gd = ...;. - - - If we wanted to change the toolchain, we would simply pass a different - mkDerivation function. - - - Let's talk a closer look at the snippet and dissect the syntax: - - The entire expression in default.nix - returns an attribute set with the keys hello, - graphviz, and graphvizCore. - - - - With "let", we define some local variables. - - - - We bring pkgs into the scope when defining the - package set. This saves us from having to type - pkgs" repeatedly. - - - - We import hello.nix and graphviz.nix, - which each return a function. We call the functions with a set of inputs to - get back the derivation. - - - - The "inherit x" syntax is equivalent to - "x = x". This means that the "inherit gd" - here, combined with the above "with pkgs;", - is equivalent to "gd = pkgs.gd". - - - - - The entire repository of this can be found at the pill 12 gist. - -
-
- Conclusion - - The "inputs" pattern allows our expressions to be easily - customizable through a set of arguments. These arguments could be flags, - derivations, or any other customizations enabled by the nix language. - Our package expressions are simply functions: there is no extra magic present. - - - The "inputs" pattern also makes the expressions - independent of the repository. Given that we pass all needed information - through arguments, it is possible to use these expressions in any other context. - -
-
- Next pill - - In the next pill, we will talk about the "callPackage" design - pattern. This removes the tedium of specifying the names of the inputs twice: - once in the top-level default.nix, and once in the package - expression. With callPackage, we will - implicitly pass the necessary inputs from the top-level expression. - -
-
diff --git a/pills/12/graphviz-derivation.txt b/pills/12/graphviz-derivation.txt deleted file mode 100644 index 93e83fc..0000000 --- a/pills/12/graphviz-derivation.txt +++ /dev/null @@ -1,8 +0,0 @@ -let - pkgs = import { }; - mkDerivation = import ./autotools.nix pkgs; -in -mkDerivation { - name = "graphviz"; - src = ./graphviz-2.49.3.tar.gz; -} diff --git a/pills/12/graphviz-gd-derivation.txt b/pills/12/graphviz-gd-derivation.txt deleted file mode 100644 index 4ef0a35..0000000 --- a/pills/12/graphviz-gd-derivation.txt +++ /dev/null @@ -1,13 +0,0 @@ -let - pkgs = import { }; - mkDerivation = import ./autotools.nix pkgs; -in -mkDerivation { - name = "graphviz"; - src = ./graphviz-2.49.3.tar.gz; - buildInputs = with pkgs; [ - pkg-config - (pkgs.lib.getLib gd) - (pkgs.lib.getDev gd) - ]; -} diff --git a/pills/12/graphviz-mkderivation.txt b/pills/12/graphviz-mkderivation.txt deleted file mode 100644 index e60c7c0..0000000 --- a/pills/12/graphviz-mkderivation.txt +++ /dev/null @@ -1,14 +0,0 @@ -{ mkDerivation, lib, gdSupport ? true, gd, pkg-config }: - -mkDerivation { - name = "graphviz"; - src = ./graphviz-2.49.3.tar.gz; - buildInputs = - if gdSupport - then [ - pkg-config - (lib.getLib gd) - (lib.getDev gd) - ] - else []; -} diff --git a/pills/12/nix-env-install-graphviz.txt b/pills/12/nix-env-install-graphviz.txt deleted file mode 100644 index 844c5f4..0000000 --- a/pills/12/nix-env-install-graphviz.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-env -f . -iA graphviz -[...] -$ dot -V diff --git a/pills/12/repository-mkderivation.txt b/pills/12/repository-mkderivation.txt deleted file mode 100644 index 4731bf8..0000000 --- a/pills/12/repository-mkderivation.txt +++ /dev/null @@ -1,25 +0,0 @@ -let - pkgs = import { }; - mkDerivation = import ./autotools.nix pkgs; -in -with pkgs; -{ - hello = import ./hello.nix { inherit mkDerivation; }; - graphviz = import ./graphviz.nix { - inherit - mkDerivation - lib - gd - pkg-config - ; - }; - graphvizCore = import ./graphviz.nix { - inherit - mkDerivation - lib - gd - pkg-config - ; - gdSupport = false; - }; -} diff --git a/pills/12/repository-test-nix-build.txt b/pills/12/repository-test-nix-build.txt deleted file mode 100644 index fa91e01..0000000 --- a/pills/12/repository-test-nix-build.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-build default.nix -A hello -[...] -$ result/bin/hello -Hello, world! diff --git a/pills/12/repository-test-nix-repl.txt b/pills/12/repository-test-nix-repl.txt deleted file mode 100644 index 7bb78e8..0000000 --- a/pills/12/repository-test-nix-repl.txt +++ /dev/null @@ -1,7 +0,0 @@ -$ nix repl -nix-repl> :l default.nix -Added 2 variables. -nix-repl> hello -«derivation /nix/store/dkib02g54fpdqgpskswgp6m7bd7mgx89-hello.drv» -nix-repl> graphviz -«derivation /nix/store/zqv520v9mk13is0w980c91z7q1vkhhil-graphviz.drv» diff --git a/pills/12/repository.txt b/pills/12/repository.txt deleted file mode 100644 index 3068e07..0000000 --- a/pills/12/repository.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ - hello = import ./hello.nix; - graphviz = import ./graphviz.nix; -} diff --git a/pills/12/setup-sh.txt b/pills/12/setup-sh.txt deleted file mode 100644 index bf9b24c..0000000 --- a/pills/12/setup-sh.txt +++ /dev/null @@ -1,8 +0,0 @@ -for p in $baseInputs $buildInputs; do - if [ -d $p/bin ]; then - export PATH="$p/bin${PATH:+:}$PATH" - fi - if [ -d $p/lib/pkgconfig ]; then - export PKG_CONFIG_PATH="$p/lib/pkgconfig${PKG_CONFIG_PATH:+:}$PKG_CONFIG_PATH" - fi -done diff --git a/pills/12/simple-png.txt b/pills/12/simple-png.txt deleted file mode 100644 index 9c33101..0000000 --- a/pills/12/simple-png.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ echo 'graph test { a -- b }'|result/bin/dot -Tpng -o test.png -Format: "png" not recognized. Use one of: canon cmap [...] diff --git a/pills/13-callpackage-design-pattern.md b/pills/13-callpackage-design-pattern.md new file mode 100644 index 0000000..9b04107 --- /dev/null +++ b/pills/13-callpackage-design-pattern.md @@ -0,0 +1,172 @@ +# Callpackage Design Pattern + +Welcome to the 13th Nix pill. In the previous [12th pill](12-inputs-design-pattern.md), we introduced the first basic design pattern for organizing a repository of software. In addition, we packaged `graphviz` so that we had two packages to bundle into an example repository. + +The next design pattern we will examine is called the `callPackage` pattern. This technique is extensively used in [nixpkgs](https://github.com/NixOS/nixpkgs), and it's the current de facto standard for importing packages in a repository. Its purpose is to reduce the duplication of identifiers between package derivation inputs and repository derivations. + +## The callPackage convenience + +In the previous pill, we demonstrated how the `inputs` pattern decouples packages from the repository. This allowed us to manually pass the inputs to the derivation; the derivation declares its inputs, and the caller passes the arguments. + +However, as with usual programming languages, there is some duplication of work: we declare parameter names and then we pass arguments, typically with the same name. For example, if we define a package derivation using the `inputs` pattern such as: + +```nix +{ input1, input2, ... }: +... +``` + +we would likely want to bundle that package derivation into a repository via a an attribute set defined as something like: + +```nix +rec { + lib1 = import package1.nix { inherit input1 input2; }; + program2 = import package2.nix { inherit inputX inputY lib1; }; +} +``` + +There are two things to note. First, that inputs often have the same name as attributes in the repository itself. Second, that (due to the `rec` keyword), the inputs to a package derivation may be other packages in the repository itself. + +Rather than passing the inputs twice, we would prefer to pass those inputs from the repository automatically and allow for manually overriding defaults. + +To achieve this, we will define a `callPackage` function with the following calling convention: + +```nix +{ + lib1 = callPackage package1.nix { }; + program2 = callPackage package2.nix { someoverride = overriddenDerivation; }; +} +``` + +We want `callPackage` to be a function of two arguments, with the following behavior: + +- Import the given expression contained in the file of the first argument, and return a function. This function returns a package derivation that uses the inputs pattern. + +- Determine the name of the arguments to the function (i.e., the names of the inputs to the package derivation). + +- Pass default arguments from the repository set, and let us override those arguments if we wish to customize the package derivation. + +## Implementing `callPackage` + +In this section, we will build up the `callPackages` pattern from scratch. To start, we need a way to obtain the argument names of a function (in this case, the function that takes "inputs" and produces a package derivation) at runtime. This is because we want to automatically pass such arguments. + +Nix provides a builtin function to do this: + +```console +nix-repl> add = { a ? 3, b }: a+b +nix-repl> builtins.functionArgs add +{ a = true; b = false; } +``` + +In addition to returning the argument names, the attribute set returned by `functionArgs` indicates whether or not the argument has a default value. For our purposes, we are only interested in the argument names; we do not care about the default values right now. + +The next step is to make `callPackage` automatically pass inputs to our package derivations based on the argument names we've just obtained with `functionArgs`. + +To do this, we need two things: + +- A package repository set containing package derivations that match the arguments names we've obtained + +- A way to obtain an auto-populated attribute set combining the package repository and the return value of `functionArgs`. + +The former is easy: we just have to set our package derivation's inputs to be package names in a repository, such as `nixpkgs`. For the latter, Nix provides another builtin function: + +```console +nix-repl> values = { a = 3; b = 5; c = 10; } +nix-repl> builtins.intersectAttrs values (builtins.functionArgs add) +{ a = true; b = false; } +nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values +{ a = 3; b = 5; } +``` + +The `intersectAttrs` returns an attribute set whose names are the intersection of both arguments' attribute names, with the attribute values taken from the second argument. + +This is all we need to do: we have obtained the argument names from a function, and populated these with an existing set of attributes. This is our simple implementation of `callPackage`: + +```console +nix-repl> callPackage = set: f: f (builtins.intersectAttrs (builtins.functionArgs f) set) +nix-repl> callPackage values add +8 +nix-repl> with values; add { inherit a b; } +8 +``` + +Let's dissect the above snippet: + +- We define a `callPackage` variable which is a function. + +- The first parameter to the `callPackage` function is a set of name-value pairs that may appear in the argument set of the function we wish to "autocall". + +- The second parameter is the function to "autocall" + +- We take the argument names of the function and intersect with the set of all values. + +- Finally, we call the passed function `f` with the resulting intersection. + +In the snippet above, we've also demonstrated that the `callPackage` call is equivalent to directly calling `add a b`. + +We achieved most of what we wanted: to automatically call functions given a set of possible arguments. If an argument is not found within the set we used to call the function, then we receive an error (unless the function has variadic arguments denoted with `...`, as explained in the [5th pill](05-functions-and-imports.md)). + +The last missing piece is allowing users to override some of the parameters. We may not want to always call functions with values taken from the big set. Thus, we add a third parameter which takes a set of overrides: + +```console +nix-repl> callPackage = set: f: overrides: f ((builtins.intersectAttrs (builtins.functionArgs f) set) // overrides) +nix-repl> callPackage values add { } +8 +nix-repl> callPackage values add { b = 12; } +15 +``` + +Apart from the increasing number of parentheses, it should be clear that we simply take a set union between the default arguments and the overriding set. + +## Using callPackage to simplify the repository + +Given our `callPackages`, we can simplify the repository expression in `default.nix`: + +```nix +let + nixpkgs = import { }; + allPkgs = nixpkgs // pkgs; + callPackage = + path: overrides: + let + f = import path; + in + f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides); + pkgs = with nixpkgs; { + mkDerivation = import ./autotools.nix nixpkgs; + hello = callPackage ./hello.nix { }; + graphviz = callPackage ./graphviz.nix { }; + graphvizCore = callPackage ./graphviz.nix { gdSupport = false; }; + }; +in +pkgs +``` + +Let's examine this in detail: + +- The expression above defines our own package repository, which we call `pkgs`, that contains `hello` along with our two variants of `graphviz`. + +- In the `let` expression, we import `nixpkgs`. Note that previously, we referred to this import with the variable `pkgs`, but now that name is taken by the repository we are creating ourselves. + +- We needed a way to pass `pkgs` to `callPackage` somehow. Instead of returning the set of packages directly from `default.nix`, we first assign it to a `let` variable and reuse it in `callPackage`. + +- For convenience, in `callPackage` we first import the file instead of calling it directly. Otherwise we would have to write the `import` for each package. + +- Since our expressions use packages from `nixpkgs`, in `callPackage` we use `allPkgs`, which is the union of `nixpkgs` and our packages. + +- We moved `mkDerivation` into `pkgs` itself, so that it also gets passed automatically. + +Note how easily we overrode arguments in the case of `graphviz` without `gd`. In addition, note how easy it was to merge two repositories: `nixpkgs` and our `pkgs`! + +The reader should notice a magic thing happening. We're defining `pkgs` in terms of `callPackage`, and `callPackage` in terms of `pkgs`. That magic is possible thanks to lazy evaluation: `builtins.intersectAttrs` doesn't need to know the values in `allPkgs` in order to perform intersection, only the keys that do not require `callPackage` evaluation. + +## Conclusion + +The "`callPackage`" pattern has simplified our repository considerably. We were able to import packages that require named arguments and call them automatically, given the set of all packages sourced from `nixpkgs`. + +We've also introduced some useful builtin functions that allows us to introspect Nix functions and manipulate attributes. These builtin functions are not usually used when packaging software, but rather act as tools for packaging. They are documented in the [Nix manual](https://nixos.org/manual/nix/stable/expressions/builtins.html). + +Writing a repository in Nix is an evolution of writing convenient functions for combining the packages. This pill demonstrates how Nix can be a generic tool to build and deploy software, and how suitable it is to create software repositories with our own conventions. + +## Next pill + +In the next pill, we will talk about the "`override`" design pattern. The `graphvizCore` seems straightforward. It starts from `graphviz.nix` and builds it without `gd`. In the next pill, we will consider another point of view: starting from `pkgs.graphviz` and disabling `gd`? diff --git a/pills/13-callpackage-design-pattern.xml b/pills/13-callpackage-design-pattern.xml deleted file mode 100644 index 95c085a..0000000 --- a/pills/13-callpackage-design-pattern.xml +++ /dev/null @@ -1,267 +0,0 @@ - - - Callpackage Design Pattern - - Welcome to the 13th Nix pill. In the previous 12th - pill, we introduced the first basic design pattern for organizing a repository of - software. In addition, we packaged graphviz so that we had two packages - to bundle into an example repository. - - - The next design pattern we will examine is called the callPackage - pattern. This technique is extensively used in nixpkgs, and it's the current - de facto standard for importing packages in a repository. Its purpose is to reduce - the duplication of identifiers between package derivation inputs and repository - derivations. - -
- The callPackage convenience - - In the previous pill, we demonstrated how the inputs - pattern decouples packages from the repository. This allowed us to - manually pass the inputs to the derivation; the derivation declares - its inputs, and the caller passes the arguments. - - - However, as with usual programming languages, there is some duplication of work: - we declare parameter names and then we pass arguments, typically with the same name. - For example, if we define a package derivation using the inputs - pattern such as: - - - - we would likely want to bundle that package derivation into a repository via a - an attribute set defined as something like: - - - - There are two things to note. First, that inputs often have the same name as - attributes in the repository itself. Second, that (due to the rec - keyword), the inputs to a package derivation may be other packages in the - repository itself. - - - Rather than passing the inputs twice, we would prefer to pass those inputs from - the repository automatically and allow for manually overriding defaults. - - - To achieve this, we will define a callPackage function with - the following calling convention: - - - - We want callPackage to be a function of two arguments, with the - following behavior: - - - Import the given expression contained in the file of the first argument, - and return a function. This function returns a package derivation that - uses the inputs pattern. - - - Determine the name of the arguments to the function (i.e., the names - of the inputs to the package derivation). - - - Pass default arguments from the repository set, and let us override those - arguments if we wish to customize the package derivation. - - - -
-
- Implementing <code>callPackage</code> - - In this section, we will build up the callPackages pattern - from scratch. To start, we need a way to obtain the argument names - of a function (in this case, the function that takes "inputs" and produces - a package derivation) at runtime. This is because we want to automatically pass - such arguments. - - - Nix provides a builtin function to do this: - - - - In addition to returning the argument names, the attribute set returned by - functionArgs indicates whether or not the argument has a default value. - For our purposes, we are only interested in the argument names; we do not care - about the default values right now. - - - - The next step is to make callPackage automatically pass inputs to our - package derivations based on the argument names we've just obtained with - functionArgs. - - - To do this, we need two things: - - - A package repository set containing package derivations that match the arguments - names we've obtained - - - A way to obtain an auto-populated attribute set combining the package repository - and the return value of functionArgs. - - - - - The former is easy: we just have to set our package derivation's inputs - to be package names in a repository, such as nixpkgs. For - the latter, Nix provides another builtin function: - - - - The intersectAttrs returns an attribute set whose names are - the intersection of both arguments' attribute names, with the attribute - values taken from the second argument. - - - This is all we need to do: we have obtained the argument names from a function, - and populated these with an existing set of attributes. This is our simple - implementation of callPackage: - - - - Let's dissect the above snippet: - - - We define a callPackage variable which is a - function. - - - The first parameter to the callPackage function - is a set of name-value pairs that may appear in the argument set of - the function we wish to "autocall". - - - The second parameter is the function to "autocall" - - - We take the argument names of the function and intersect with the set of all - values. - - - Finally, we call the passed function f with the resulting - intersection. - - - - - In the snippet above, we've also demonstrated that the callPackage - call is equivalent to directly calling add a b. - - - We achieved most of what we wanted: to automatically call functions given a set of - possible arguments. If an argument is not found within the set we used to call the - function, then we receive an error (unless the function has variadic arguments - denoted with ..., as explained in the 5th pill). - - - The last missing piece is allowing users to override some of the parameters. - We may not want to always call functions with values taken from the big set. - Thus, we add a third parameter which takes a set of overrides: - - - - Apart from the increasing number of parentheses, it should be clear that we simply - take a set union between the default arguments and the overriding set. - -
-
- Using callPackage to simplify the repository - - Given our callPackages, we can simplify the repository expression - in default.nix: - - - - Let's examine this in detail: - - - The expression above defines our own package repository, which we call - pkgs, that contains hello along - with our two variants of graphviz. - - - In the let expression, we import nixpkgs. - Note that previously, we referred to this import with the variable - pkgs, but now that name is taken by the repository - we are creating ourselves. - - - We needed a way to pass pkgs to callPackage - somehow. Instead of returning the set of packages directly from - default.nix, we first assign it to a let - variable and reuse it in callPackage. - - - For convenience, in callPackage we first - import the file instead of calling it directly. Otherwise we would have to - write the import for each package. - - - Since our expressions use packages from nixpkgs, in - callPackage we use allPkgs, which - is the union of nixpkgs and our packages. - - - We moved mkDerivation into pkgs itself, - so that it also gets passed automatically. - - - - Note how easily we overrode arguments in the case of graphviz - without gd. In addition, note how easy it was to merge - two repositories: nixpkgs and our pkgs! - - - The reader should notice a magic thing happening. We're defining - pkgs in terms of callPackage, and - callPackage in terms of pkgs. That magic is - possible thanks to lazy evaluation: builtins.intersectAttrs doesn't - need to know the values in allPkgs in order to perform intersection, - only the keys that do not require callPackage evaluation. - -
-
- Conclusion - - The "callPackage" pattern has simplified our repository - considerably. We were able to import packages that require named arguments - and call them automatically, given the set of all packages sourced from - nixpkgs. - - - We've also introduced some useful builtin functions that allows us to introspect Nix - functions and manipulate attributes. These builtin functions are not usually used when - packaging software, but rather act as tools for packaging. They are documented in the - Nix - manual. - - - Writing a repository in Nix is an evolution of writing convenient functions for - combining the packages. This pill demonstrates how Nix can be a generic tool - to build and deploy software, and how suitable it is to create software - repositories with our own conventions. - -
-
- Next pill - - In the next pill, we will talk about the "override" design - pattern. The graphvizCore seems straightforward. It starts from - graphviz.nix and builds it without gd. - In the next pill, we will consider another point of view: starting from - pkgs.graphviz and disabling gd? - -
-
diff --git a/pills/13/callpackage-function-call.txt b/pills/13/callpackage-function-call.txt deleted file mode 100644 index 89c9f75..0000000 --- a/pills/13/callpackage-function-call.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ - lib1 = callPackage package1.nix { }; - program2 = callPackage package2.nix { someoverride = overriddenDerivation; }; -} diff --git a/pills/13/callpackage-function-overrides.txt b/pills/13/callpackage-function-overrides.txt deleted file mode 100644 index aa77625..0000000 --- a/pills/13/callpackage-function-overrides.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> callPackage = set: f: overrides: f ((builtins.intersectAttrs (builtins.functionArgs f) set) // overrides) -nix-repl> callPackage values add { } -8 -nix-repl> callPackage values add { b = 12; } -15 diff --git a/pills/13/callpackage-function.txt b/pills/13/callpackage-function.txt deleted file mode 100644 index 8bfbe2f..0000000 --- a/pills/13/callpackage-function.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> callPackage = set: f: f (builtins.intersectAttrs (builtins.functionArgs f) set) -nix-repl> callPackage values add -8 -nix-repl> with values; add { inherit a b; } -8 diff --git a/pills/13/callpackage-usage.txt b/pills/13/callpackage-usage.txt deleted file mode 100644 index 43ad9e7..0000000 --- a/pills/13/callpackage-usage.txt +++ /dev/null @@ -1,17 +0,0 @@ -let - nixpkgs = import { }; - allPkgs = nixpkgs // pkgs; - callPackage = - path: overrides: - let - f = import path; - in - f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides); - pkgs = with nixpkgs; { - mkDerivation = import ./autotools.nix nixpkgs; - hello = callPackage ./hello.nix { }; - graphviz = callPackage ./graphviz.nix { }; - graphvizCore = callPackage ./graphviz.nix { gdSupport = false; }; - }; -in -pkgs diff --git a/pills/13/get-args-function.txt b/pills/13/get-args-function.txt deleted file mode 100644 index c4f22cd..0000000 --- a/pills/13/get-args-function.txt +++ /dev/null @@ -1,3 +0,0 @@ -nix-repl> add = { a ? 3, b }: a+b -nix-repl> builtins.functionArgs add -{ a = true; b = false; } diff --git a/pills/13/intersect-attr-values.txt b/pills/13/intersect-attr-values.txt deleted file mode 100644 index de39d7a..0000000 --- a/pills/13/intersect-attr-values.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> values = { a = 3; b = 5; c = 10; } -nix-repl> builtins.intersectAttrs values (builtins.functionArgs add) -{ a = true; b = false; } -nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values -{ a = 3; b = 5; } diff --git a/pills/13/package-derivation.txt b/pills/13/package-derivation.txt deleted file mode 100644 index 1d34fbb..0000000 --- a/pills/13/package-derivation.txt +++ /dev/null @@ -1,2 +0,0 @@ -{ input1, input2, ... }: -... diff --git a/pills/13/repository-derivation.txt b/pills/13/repository-derivation.txt deleted file mode 100644 index 5ce1460..0000000 --- a/pills/13/repository-derivation.txt +++ /dev/null @@ -1,4 +0,0 @@ -rec { - lib1 = import package1.nix { inherit input1 input2; }; - program2 = import package2.nix { inherit inputX inputY lib1; }; -} diff --git a/pills/14-override-design-pattern.md b/pills/14-override-design-pattern.md new file mode 100644 index 0000000..0080c57 --- /dev/null +++ b/pills/14-override-design-pattern.md @@ -0,0 +1,163 @@ +# Override Design Pattern + +Welcome to the 14th Nix pill. In the previous [13th](13-callpackage-design-pattern.md) pill, we introduced the `callPackage` pattern and used it to simplify the composition of software in a repository. + +The next design pattern is less necessary, but is useful in many cases and is a good exercise to learn more about Nix. + +## About composability + +Functional languages are known for being able to compose functions. In particular, these languages gain expressivity from functions that manipulate an original value into a new value having the same structure. This allows us to compose multiple functions to perform the desired modifications. + +In Nix, we mostly talk about **functions** that accept inputs in order to return **derivations**. In our world, we want utility functions that are able to manipulate those structures. These utilities add some useful properties to the original value, and we'd like to be able to apply more utilities on top of the result. + +For example, let's say we have an initial derivation `drv` and we want to transform it into a `drv` with debugging information and custom patches: + +```nix +debugVersion (applyPatches [ ./patch1.patch ./patch2.patch ] drv) +``` + +The final result should be the original derivation with some changes. This is both interesting and very different from other packaging approaches, which is a consequence of using a functional language to describe packages. + +Designing such utilities is not trivial in a functional language without static typing, because understanding what can or cannot be composed is difficult. But we try to do our best. + +## The override pattern + +In [pill 12](12-inputs-design-pattern.md) we introduced the inputs design pattern. We do not return a derivation picking dependencies directly from the repository; rather we declare the inputs and let the callers pass the necessary arguments. + +In our repository we have a set of attributes that import the expressions of the packages and pass these arguments, getting back a derivation. Let's take for example the `graphviz` attribute: + +```nix +graphviz = import ./graphviz.nix { inherit mkDerivation gd fontconfig libjpeg bzip2; }; +``` + +If we wanted to produce a derivation of `graphviz` with a customized `gd` version, we would have to repeat most of the above plus specifying an alternative `gd`: + +```nix +{ + mygraphviz = import ./graphviz.nix { + inherit + mkDerivation + fontconfig + libjpeg + bzip2 + ; + gd = customgd; + }; +} +``` + +That's hard to maintain. Using `callPackage` would be easier: + +```nix +mygraphviz = callPackage ./graphviz.nix { gd = customgd; }; +``` + +But we may still be diverging from the original graphviz in the repository. + +We would like to avoid specifying the nix expression again. Instead, we would like to reuse the original `graphviz` attribute in the repository and add our overrides like so: + +```nix +mygraphviz = graphviz.override { gd = customgd; }; +``` + +The difference is obvious, as well as the advantages of this approach. + +Note: that `.override` is not a "method" in the OO sense as you may think. Nix is a functional language. The`.override` is simply an attribute of a set. + +## The override implementation + +Recall that the `graphviz` attribute in the repository is the derivation returned by the function imported from `graphviz.nix`. We would like to add a further attribute named "`override`" to the returned set. + +Let's start by first creating a function "`makeOverridable`". This function will take two arguments: a function (that must return a set) and the set of original arguments to be passed to the function. + +We will put this function in a `lib.nix`: + +```nix +{ + makeOverridable = + f: origArgs: + let + origRes = f origArgs; + in + origRes // { override = newArgs: f (origArgs // newArgs); }; +} +``` + +`makeOverridable` takes a function and a set of original arguments. It returns the original returned set, plus a new `override` attribute. + +This `override` attribute is a function taking a set of new arguments, and returns the result of the original function called with the original arguments unified with the new arguments. This is admittedly somewhat confusing, but the examples below should make it clear. + +Let's try it with `nix repl`: + +```console +$ nix repl +nix-repl> :l lib.nix +Added 1 variables. +nix-repl> f = { a, b }: { result = a+b; } +nix-repl> f { a = 3; b = 5; } +{ result = 8; } +nix-repl> res = makeOverridable f { a = 3; b = 5; } +nix-repl> res +{ override = «lambda»; result = 8; } +nix-repl> res.override { a = 10; } +{ result = 15; } +``` + +Note that, as we specified above, the function `f` does not return the plain sum. Instead, it returns a set with the sum bound to the name `result`. + +The variable `res` contains the result of the function call without any override. It's easy to see in the definition of `makeOverridable`. In addition, you can see that the new `override` attribute is a function. + +Calling `res.override` with a set will invoke the original function with the overrides, as expected. + +This is a good start, but we can't override again! This is because the returned set (with `result = 15`) does not have an `override` attribute of its own. This is bad; it breaks further composition. + +The solution is simple: the `.override` function should make the result overridable again: + +```nix +rec { + makeOverridable = + f: origArgs: + let + origRes = f origArgs; + in + origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); }; +} +``` + +Please note the `rec` keyword. It's necessary so that we can refer to `makeOverridable` from `makeOverridable` itself. + +Now let's try overriding twice: + +```console +nix-repl> :l lib.nix +Added 1 variables. +nix-repl> f = { a, b }: { result = a+b; } +nix-repl> res = makeOverridable f { a = 3; b = 5; } +nix-repl> res2 = res.override { a = 10; } +nix-repl> res2 +{ override = «lambda»; result = 15; } +nix-repl> res2.override { b = 20; } +{ override = «lambda»; result = 30; } +``` + +Success! The result is 30 (as expected) because `a` is overridden to 10 in the first override, and `b` is overridden to 20 in the second. + +Now it would be nice if `callPackage` made our derivations overridable. This is an exercise for the reader. + +## Conclusion + +The "`override`" pattern simplifies the way we customize packages starting from an existing set of packages. This opens a world of possibilities for using a central repository like `nixpkgs` and defining overrides on our local machine without modifying the original package. + +We can dream of a custom, isolated `nix-shell` environment for testing `graphviz` with a custom `gd`: + +```nix +debugVersion (graphviz.override { gd = customgd; }) +``` + +Once a new version of the overridden package comes out in the repository, the customized package will make use of it automatically. + +The key in Nix is to find powerful yet simple abstractions in order to let the user customize their environment with highest consistency and lowest maintenance time, by using predefined composable components. + +## Next pill + +In the next pill, we will talk about Nix search paths. By "search path", we mean a place in the file system where Nix looks for expressions. This answers the question of where `` comes from. diff --git a/pills/14-override-design-pattern.xml b/pills/14-override-design-pattern.xml deleted file mode 100644 index 06db75f..0000000 --- a/pills/14-override-design-pattern.xml +++ /dev/null @@ -1,195 +0,0 @@ - - - Override Design Pattern - - Welcome to the 14th Nix pill. In the previous 13th pill, we introduced the - callPackage pattern and used it to simplify the composition - of software in a repository. - - - The next design pattern is less necessary, but is useful in many cases and is - a good exercise to learn more about Nix. - -
- About composability - - Functional languages are known for being able to compose functions. In particular, - these languages gain expressivity from functions that manipulate an original - value into a new value having the same structure. This allows us to compose - multiple functions to perform the desired modifications. - - - In Nix, we mostly talk about functions - that accept inputs in order to return derivations. - In our world, we want utility functions that are able to manipulate those structures. - These utilities add some useful properties to the original value, and we'd like to be - able to apply more utilities on top of the result. - - - For example, let's say we have an initial derivation drv and - we want to transform it into a drv with debugging information and - custom patches: - - debugVersion (applyPatches [ ./patch1.patch ./patch2.patch ] drv) - - The final result should be the original derivation with some changes. - This is both interesting and very different from other packaging approaches, - which is a consequence of using a functional language to describe packages. - - - Designing such utilities is not trivial in a functional language without static - typing, because understanding what can or cannot be composed is difficult. - But we try to do our best. - -
-
- The override pattern - - In pill 12 we introduced the inputs - design pattern. We do not return a derivation picking dependencies directly from the - repository; rather we declare the inputs and let the callers pass the necessary - arguments. - - - In our repository we have a set of attributes that import the expressions of the - packages and pass these arguments, getting back a derivation. Let's take for example - the graphviz attribute: - - graphviz = import ./graphviz.nix { inherit mkDerivation gd fontconfig libjpeg bzip2; }; - - If we wanted to produce a derivation of graphviz with a customized - gd version, we would have to repeat most of the above plus - specifying an alternative gd: - - - - That's hard to maintain. Using callPackage would be easier: - - mygraphviz = callPackage ./graphviz.nix { gd = customgd; }; - - But we may still be diverging from the original graphviz in the repository. - - - We would like to avoid specifying the nix expression again. Instead, we would - like to reuse the original graphviz attribute in the - repository and add our overrides like so: - - mygraphviz = graphviz.override { gd = customgd; }; - - The difference is obvious, as well as the advantages of this approach. - - - Note: that .override is - not a "method" in the OO sense as you may think. Nix is a functional language. - The.override is simply an attribute of a set. - -
-
- The override implementation - - Recall that the graphviz attribute in the repository is - the derivation returned by the function imported from - graphviz.nix. We would like to add a further attribute - named "override" to the returned set. - - - Let's start by first creating a function "makeOverridable". - This function will take two arguments: a function (that must return a set) - and the set of original arguments to be passed to the function. - - - We will put this function in a lib.nix: - - - - makeOverridable takes a function and a set of original arguments. - It returns the original returned set, plus a new override attribute. - - - This override attribute is a function taking a set of new - arguments, and returns the result of the original function called with the - original arguments unified with the new arguments. This is admittedly somewhat - confusing, but the examples below should make it clear. - - - Let's try it with nix repl: - - - - Note that, as we specified above, the function f does not return - the plain sum. Instead, it returns a set with the sum bound to the name - result. - - - The variable res contains the result of the function call without - any override. It's easy to see in the definition of makeOverridable. - In addition, you can see that the new override attribute is a function. - - - Calling res.override with a set will invoke the original function - with the overrides, as expected. - - - This is a good start, but we can't override again! This is because the returned - set (with result = 15) does not have an override - attribute of its own. This is bad; it breaks further composition. - - - The solution is simple: the .override function should make the - result overridable again: - - - - Please note the rec keyword. It's necessary so that we can refer - to makeOverridable from makeOverridable itself. - - - Now let's try overriding twice: - - - - Success! The result is 30 (as expected) because a is overridden - to 10 in the first override, and b is overridden to 20 in the - second. - - - Now it would be nice if callPackage made our - derivations overridable. This is an exercise for the reader. - -
-
- Conclusion - - The "override" pattern simplifies the way we customize packages - starting from an existing set of packages. This opens a world of possibilities for - using a central repository like nixpkgs and defining overrides - on our local machine without modifying the original package. - - - We can dream of a custom, isolated nix-shell environment for - testing graphviz with a custom gd: - - debugVersion (graphviz.override { gd = customgd; }) - - Once a new version of the overridden package comes out in the repository, the - customized package will make use of it automatically. - - - The key in Nix is to find powerful yet simple abstractions in order to let the user - customize their environment with highest consistency and lowest maintenance time, by - using predefined composable components. - -
-
- Next pill - - In the next pill, we will talk about Nix search paths. By "search path", we mean a - place in the file system where Nix looks for expressions. This answers the - question of where <nixpkgs> comes from. - -
-
diff --git a/pills/14/make-overridable-lib.txt b/pills/14/make-overridable-lib.txt deleted file mode 100644 index 9b60b67..0000000 --- a/pills/14/make-overridable-lib.txt +++ /dev/null @@ -1,8 +0,0 @@ -{ - makeOverridable = - f: origArgs: - let - origRes = f origArgs; - in - origRes // { override = newArgs: f (origArgs // newArgs); }; -} diff --git a/pills/14/mygraphviz.txt b/pills/14/mygraphviz.txt deleted file mode 100644 index f0eca70..0000000 --- a/pills/14/mygraphviz.txt +++ /dev/null @@ -1,11 +0,0 @@ -{ - mygraphviz = import ./graphviz.nix { - inherit - mkDerivation - fontconfig - libjpeg - bzip2 - ; - gd = customgd; - }; -} diff --git a/pills/14/nix-repl-make-overridable-test.txt b/pills/14/nix-repl-make-overridable-test.txt deleted file mode 100644 index 230c7ea..0000000 --- a/pills/14/nix-repl-make-overridable-test.txt +++ /dev/null @@ -1,11 +0,0 @@ -$ nix repl -nix-repl> :l lib.nix -Added 1 variables. -nix-repl> f = { a, b }: { result = a+b; } -nix-repl> f { a = 3; b = 5; } -{ result = 8; } -nix-repl> res = makeOverridable f { a = 3; b = 5; } -nix-repl> res -{ override = «lambda»; result = 8; } -nix-repl> res.override { a = 10; } -{ result = 15; } diff --git a/pills/14/nix-repl-make-overridable-twice.txt b/pills/14/nix-repl-make-overridable-twice.txt deleted file mode 100644 index 0e64dab..0000000 --- a/pills/14/nix-repl-make-overridable-twice.txt +++ /dev/null @@ -1,9 +0,0 @@ -nix-repl> :l lib.nix -Added 1 variables. -nix-repl> f = { a, b }: { result = a+b; } -nix-repl> res = makeOverridable f { a = 3; b = 5; } -nix-repl> res2 = res.override { a = 10; } -nix-repl> res2 -{ override = «lambda»; result = 15; } -nix-repl> res2.override { b = 20; } -{ override = «lambda»; result = 30; } diff --git a/pills/14/rec-make-overridable.txt b/pills/14/rec-make-overridable.txt deleted file mode 100644 index f2869a8..0000000 --- a/pills/14/rec-make-overridable.txt +++ /dev/null @@ -1,8 +0,0 @@ -rec { - makeOverridable = - f: origArgs: - let - origRes = f origArgs; - in - origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); }; -} diff --git a/pills/15-nix-search-paths.md b/pills/15-nix-search-paths.md new file mode 100644 index 0000000..5be3992 --- /dev/null +++ b/pills/15-nix-search-paths.md @@ -0,0 +1,138 @@ +# Nix Search Paths + +Welcome to the 15th Nix pill. In the previous [14th](14-override-design-pattern.md) pill we have introduced the "override" pattern, useful for writing variants of derivations by passing different inputs. + +Assuming you followed the previous posts, I hope you are now ready to understand `nixpkgs`. But we have to find `nixpkgs` in our system first! So this is the step: introducing some options and environment variables used by nix tools. + +## The NIX_PATH + +The [NIX_PATH environment variable](https://nixos.org/manual/nix/stable/command-ref/env-common.html) is very important. It's very similar to the `PATH` environment variable. The syntax is similar, several paths are separated by a colon `:`. Nix will then search for something in those paths from left to right. + +Who uses `NIX_PATH`? The nix expressions! Yes, `NIX_PATH` is not of much use by the nix tools themselves, rather it's used when writing nix expressions. + +In the shell for example, when you execute the command `ping`, it's being searched in the `PATH` directories. The first one found is the one being used. + +In nix it's exactly the same, however the syntax is different. Instead of just typing `ping` you have to type ``. Yes, I know... you are already thinking of ``. However, don't stop reading here, let's keep going. + +What's `NIX_PATH` good for? Nix expressions may refer to an "abstract" path such as ``, and it's possible to override it from the command line. + +For ease we will use `nix-instantiate --eval` to do our tests. I remind you, [nix-instantiate](https://nixos.org/manual/nix/stable/command-ref/nix-instantiate.html) is used to evaluate nix expressions and generate the .drv files. Here we are not interested in building derivations, so evaluation is enough. It can be used for one-shot expressions. + +## Fake it a little + +It's useless from a nix view point, but I think it's useful for your own understanding. Let's use `PATH` itself as `NIX_PATH`, and try to locate `ping` (or another binary if you don't have it). + +```console +$ nix-instantiate --eval -E '' +error: file `ping' was not found in the Nix search path (add it using $NIX_PATH or -I) +$ NIX_PATH=$PATH nix-instantiate --eval -E '' +/bin/ping +$ nix-instantiate -I /bin --eval -E '' +/bin/ping +``` + +Great. At first attempt nix obviously said could not be found anywhere in the search path. Note that the -I option accepts a single directory. Paths added with -I take precedence over `NIX_PATH`. + +The `NIX_PATH` also accepts a different yet very handy syntax: "`somename=somepath`". That is, instead of searching inside a directory for a name, we specify exactly the value of that name. + +```console +$ NIX_PATH="ping=/bin/ping" nix-instantiate --eval -E '' +/bin/ping +$ NIX_PATH="ping=/bin/foo" nix-instantiate --eval -E '' +error: file `ping' was not found in the Nix search path (add it using $N +``` + +Note in the second case how Nix checks whether the path exists or not. + +## The path to repository + +You are out of curiosity, right? + +```console +$ nix-instantiate --eval -E '' +/home/nix/.nix-defexpr/channels/nixpkgs +$ echo $NIX_PATH +nixpkgs=/home/nix/.nix-defexpr/channels/nixpkgs +``` + +You may have a different path, depending on how you added channels etc.. Anyway that's the whole point. The `` stranger that we used in our nix expressions, is referring to a path in the filesystem specified by `NIX_PATH`. + +You can list that directory and realize it's simply a checkout of the nixpkgs repository at a specific commit (hint: `.version-suffix`). + +The `NIX_PATH` variable is exported by `nix.sh`, and that's the reason why I always asked you to [source nix.sh](https://nixos.org/manual/nix/stable/installation/env-variables.html) at the beginning of my posts. + +You may wonder: then I can also specify a different [nixpkgs](https://github.com/NixOS/nixpkgs) path to, e.g., a `git checkout` of `nixpkgs`? Yes, you can and I encourage doing that. We'll talk about this in the next pill. + +Let's define a path for our repository, then! Let's say all the `default.nix`, `graphviz.nix` etc. are under `/home/nix/mypkgs`: + +```console +$ export NIX_PATH=mypkgs=/home/nix/mypkgs:$NIX_PATH +$ nix-instantiate --eval '' +{ graphviz = ; graphvizCore = ; hello = ; mkDerivation = ; } +``` + +Yes, `nix-build` also accepts paths with angular brackets. We first evaluate the whole repository (`default.nix`) and then pick the `graphviz` attribute. + +## A big word about nix-env + +The [nix-env](https://nixos.org/manual/nix/stable/command-ref/nix-env.html) command is a little different than `nix-instantiate` and `nix-build`. Whereas `nix-instantiate` and `nix-build` require a starting nix expression, `nix-env` does not. + +You may be crippled by this concept at the beginning, you may think `nix-env` uses `NIX_PATH` to find the `nixpkgs` repository. But that's not it. + +The `nix-env` command uses `~/.nix-defexpr`, which is also part of `NIX_PATH` by default, but that's only a coincidence. If you empty `NIX_PATH`, `nix-env` will still be able to find derivations because of `~/.nix-defexpr`. + +So if you run `nix-env -i graphviz` inside your repository, it will install the nixpkgs one. Same if you set `NIX_PATH` to point to your repository. + +In order to specify an alternative to `~/.nix-defexpr` it's possible to use the -f option: + +```console +$ nix-env -f '' -i graphviz +warning: there are multiple derivations named `graphviz'; using the first one +replacing old `graphviz' +installing `graphviz' +``` + +Oh why did it say there's another derivation named `graphviz`? Because both `graphviz` and `graphvizCore` attributes in our repository have the name "graphviz" for the derivation: + +```console +$ nix-env -f '' -qaP +graphviz graphviz +graphvizCore graphviz +hello hello +``` + +By default `nix-env` parses all derivations and uses the derivation names to interpret the command line. So in this case "graphviz" matched two derivations. Alternatively, like for `nix-build`, one can use -A to specify an attribute name instead of a derivation name: + +```console +$ nix-env -f '' -i -A graphviz +replacing old `graphviz' +installing `graphviz' +``` + +This form, other than being more precise, it's also faster because `nix-env` does not have to parse all the derivations. + +For completeness: you must install `graphvizCore` with -A, since without the -A switch it's ambiguous. + +In summary, it may happen when playing with nix that `nix-env` picks a different derivation than `nix-build`. In that case you probably specified `NIX_PATH`, but `nix-env` is instead looking into `~/.nix-defexpr`. + +Why is `nix-env` having this different behavior? I don't know specifically by myself either, but the answers could be: + +- `nix-env` tries to be generic, thus it does not look for `nixpkgs` in `NIX_PATH`, rather it looks in `~/.nix-defexpr`. + +- `nix-env` is able to merge multiple trees in `~/.nix-defexpr` by looking at all the possible derivations + +It may also happen to you **that you cannot match a derivation name when installing**, because of the derivation name vs -A switch described above. Maybe `nix-env` wanted to be more friendly in this case for default user setups. + +It may or may not make sense for you, or it's like that for historical reasons, but that's how it works currently, unless somebody comes up with a better idea. + +## Conclusion + +The `NIX_PATH` variable is the search path used by nix when using the angular brackets syntax. It's possible to refer to "abstract" paths inside nix expressions and define the "concrete" path by means of `NIX_PATH`, or the usual -I flag in nix tools. + +We've also explained some of the uncommon `nix-env` behaviors for newcomers. The `nix-env` tool does not use `NIX_PATH` to search for packages, but rather for `~/.nix-defexpr`. Beware of that! + +In general do not abuse `NIX_PATH`, when possible use relative paths when writing your own nix expressions. Of course, in the case of `` in our repository, that's a perfectly fine usage of `NIX_PATH`. Instead, inside our repository itself, refer to expressions with relative paths like `./hello.nix`. + +## Next pill + +...we will finally dive into `nixpkgs`. Most of the techniques we have developed in this series are already in `nixpkgs`, like `mkDerivation`, `callPackage`, `override`, etc., but of course better. With time, those base utilities get enhanced by the community with more features in order to handle more and more use cases and in a more general way. diff --git a/pills/15-nix-search-paths.xml b/pills/15-nix-search-paths.xml deleted file mode 100644 index b5d110f..0000000 --- a/pills/15-nix-search-paths.xml +++ /dev/null @@ -1,146 +0,0 @@ - - - Nix Search Paths - - Welcome to the 15th Nix pill. In the previous 14th pill we have introduced the "override" pattern, useful for writing variants of derivations by passing different inputs. - - - Assuming you followed the previous posts, I hope you are now ready to understand nixpkgs. But we have to find nixpkgs in our system first! So this is the step: introducing some options and environment variables used by nix tools. - -
- The NIX_PATH - - The NIX_PATH environment variable is very important. It's very similar to the PATH environment variable. The syntax is similar, several paths are separated by a colon :. Nix will then search for something in those paths from left to right. - - - Who uses NIX_PATH? The nix expressions! Yes, NIX_PATH is not of much use by the nix tools themselves, rather it's used when writing nix expressions. - - - In the shell for example, when you execute the command ping, it's being searched in the PATH directories. The first one found is the one being used. - - - In nix it's exactly the same, however the syntax is different. Instead of just typing ping you have to type <ping>. Yes, I know... you are already thinking of <nixpkgs>. However, don't stop reading here, let's keep going. - - - What's NIX_PATH good for? Nix expressions may refer to an "abstract" path such as <nixpkgs>, and it's possible to override it from the command line. - - - For ease we will use nix-instantiate --eval to do our tests. I remind you, nix-instantiate is used to evaluate nix expressions and generate the .drv files. Here we are not interested in building derivations, so evaluation is enough. It can be used for one-shot expressions. - -
-
- Fake it a little - - It's useless from a nix view point, but I think it's useful for your own understanding. Let's use PATH itself as NIX_PATH, and try to locate ping (or another binary if you don't have it). - - - - Great. At first attempt nix obviously said could not be found anywhere in the search path. Note that the -I option accepts a single directory. Paths added with -I take precedence over NIX_PATH. - - - The NIX_PATH also accepts a different yet very handy syntax: "somename=somepath". That is, instead of searching inside a directory for a name, we specify exactly the value of that name. - - - - Note in the second case how Nix checks whether the path exists or not. - -
-
- The path to repository - - You are out of curiosity, right? - - - - You may have a different path, depending on how you added channels etc.. Anyway that's the whole point. The <nixpkgs> stranger that we used in our nix expressions, is referring to a path in the filesystem specified by NIX_PATH. - - - You can list that directory and realize it's simply a checkout of the nixpkgs repository at a specific commit (hint: .version-suffix). - - - The NIX_PATH variable is exported by nix.sh, and that's the reason why I always asked you to source nix.sh at the beginning of my posts. - - - You may wonder: then I can also specify a different nixpkgs path to, e.g., a git checkout of nixpkgs? Yes, you can and I encourage doing that. We'll talk about this in the next pill. - - - Let's define a path for our repository, then! Let's say all the default.nix, graphviz.nix etc. are under /home/nix/mypkgs: - - - - Yes, nix-build also accepts paths with angular brackets. We first evaluate the whole repository (default.nix) and then pick the graphviz attribute. - -
-
- A big word about nix-env - - The nix-env command is a little different than nix-instantiate and nix-build. Whereas nix-instantiate and nix-build require a starting nix expression, nix-env does not. - - - You may be crippled by this concept at the beginning, you may think nix-env uses NIX_PATH to find the nixpkgs repository. But that's not it. - - - The nix-env command uses ~/.nix-defexpr, which is also part of NIX_PATH by default, but that's only a coincidence. If you empty NIX_PATH, nix-env will still be able to find derivations because of ~/.nix-defexpr. - - - So if you run nix-env -i graphviz inside your repository, it will install the nixpkgs one. Same if you set NIX_PATH to point to your repository. - - - In order to specify an alternative to ~/.nix-defexpr it's possible to use the -f option: - - - - Oh why did it say there's another derivation named graphviz? Because both graphviz and graphvizCore attributes in our repository have the name "graphviz" for the derivation: - - - - By default nix-env parses all derivations and uses the derivation names to interpret the command line. So in this case "graphviz" matched two derivations. Alternatively, like for nix-build, one can use -A to specify an attribute name instead of a derivation name: - - - - This form, other than being more precise, it's also faster because nix-env does not have to parse all the derivations. - - - For completeness: you must install graphvizCore with -A, since without the -A switch it's ambiguous. - - - In summary, it may happen when playing with nix that nix-env picks a different derivation than nix-build. In that case you probably specified NIX_PATH, but nix-env is instead looking into ~/.nix-defexpr. - - - Why is nix-env having this different behavior? I don't know specifically by myself either, but the answers could be: - - nix-env tries to be generic, thus it does not look for nixpkgs in NIX_PATH, rather it looks in ~/.nix-defexpr. - nix-env is able to merge multiple trees in ~/.nix-defexpr by looking at all the possible derivations - - - - It may also happen to you that you cannot match a derivation name when installing, because of the derivation name vs -A switch described above. Maybe nix-env wanted to be more friendly in this case for default user setups. - - - It may or may not make sense for you, or it's like that for historical reasons, but that's how it works currently, unless somebody comes up with a better idea. - -
-
- Conclusion - - The NIX_PATH variable is the search path used by nix when using the angular brackets syntax. It's possible to refer to "abstract" paths inside nix expressions and define the "concrete" path by means of NIX_PATH, or the usual -I flag in nix tools. - - - We've also explained some of the uncommon nix-env behaviors for newcomers. The nix-env tool does not use NIX_PATH to search for packages, but rather for ~/.nix-defexpr. Beware of that! - - - In general do not abuse NIX_PATH, when possible use relative paths when writing your own nix expressions. Of course, in the case of <nixpkgs> in our repository, that's a perfectly fine usage of NIX_PATH. Instead, inside our repository itself, refer to expressions with relative paths like ./hello.nix. - -
-
- Next pill - - ...we will finally dive into nixpkgs. Most of the techniques we have developed in this series are already in nixpkgs, like mkDerivation, callPackage, override, etc., but of course better. With time, those base utilities get enhanced by the community with more features in order to handle more and more use cases and in a more general way. - - -
-
diff --git a/pills/15/mypkgs-graphviz-multiple.txt b/pills/15/mypkgs-graphviz-multiple.txt deleted file mode 100644 index a541991..0000000 --- a/pills/15/mypkgs-graphviz-multiple.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-env -f '' -i graphviz -warning: there are multiple derivations named `graphviz'; using the first one -replacing old `graphviz' -installing `graphviz' diff --git a/pills/15/mypkgs-install-attr-graphviz.txt b/pills/15/mypkgs-install-attr-graphviz.txt deleted file mode 100644 index 55bc05e..0000000 --- a/pills/15/mypkgs-install-attr-graphviz.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix-env -f '' -i -A graphviz -replacing old `graphviz' -installing `graphviz' diff --git a/pills/15/mypkgs-path.txt b/pills/15/mypkgs-path.txt deleted file mode 100644 index 26d07be..0000000 --- a/pills/15/mypkgs-path.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ export NIX_PATH=mypkgs=/home/nix/mypkgs:$NIX_PATH -$ nix-instantiate --eval '' -{ graphviz = ; graphvizCore = ; hello = ; mkDerivation = ; } - diff --git a/pills/15/mypkgs-query-all.txt b/pills/15/mypkgs-query-all.txt deleted file mode 100644 index 0a9f65c..0000000 --- a/pills/15/mypkgs-query-all.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-env -f '' -qaP -graphviz graphviz -graphvizCore graphviz -hello hello diff --git a/pills/15/nix-instantiate-ping.txt b/pills/15/nix-instantiate-ping.txt deleted file mode 100644 index d39b6b4..0000000 --- a/pills/15/nix-instantiate-ping.txt +++ /dev/null @@ -1,6 +0,0 @@ -$ nix-instantiate --eval -E '' -error: file `ping' was not found in the Nix search path (add it using $NIX_PATH or -I) -$ NIX_PATH=$PATH nix-instantiate --eval -E '' -/bin/ping -$ nix-instantiate -I /bin --eval -E '' -/bin/ping diff --git a/pills/15/nixpkgs-path.txt b/pills/15/nixpkgs-path.txt deleted file mode 100644 index 4e09280..0000000 --- a/pills/15/nixpkgs-path.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix-instantiate --eval -E '' -/home/nix/.nix-defexpr/channels/nixpkgs -$ echo $NIX_PATH -nixpkgs=/home/nix/.nix-defexpr/channels/nixpkgs diff --git a/pills/15/ping-custom-path.txt b/pills/15/ping-custom-path.txt deleted file mode 100644 index 4298d0e..0000000 --- a/pills/15/ping-custom-path.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ NIX_PATH="ping=/bin/ping" nix-instantiate --eval -E '' -/bin/ping -$ NIX_PATH="ping=/bin/foo" nix-instantiate --eval -E '' -error: file `ping' was not found in the Nix search path (add it using $N diff --git a/pills/16-nixpkgs-parameters.md b/pills/16-nixpkgs-parameters.md new file mode 100644 index 0000000..68de903 --- /dev/null +++ b/pills/16-nixpkgs-parameters.md @@ -0,0 +1,108 @@ +# Nixpkgs Parameters + +Welcome to the 16th Nix pill. In the previous [15th](15-nix-search-paths.md) pill we've realized how nix finds expressions with the angular brackets syntax, so that we finally know where `` is located on our system. + +We can start diving into the [nixpkgs repository](https://github.com/NixOS/nixpkgs), through all the various tools and design patterns. Please note that also `nixpkgs` has its own manual, underlying the difference between the general `nix` language and the `nixpkgs` repository. + +## The default.nix expression + +We will not start inspecting packages at the beginning, rather the general structure of `nixpkgs`. + +In our custom repository we created a `default.nix` which composed the expressions of the various packages. + +Also `nixpkgs` has its own [default.nix](https://github.com/NixOS/nixpkgs/blob/master/default.nix), which is the one being loaded when referring to ``. It does a simple thing: check whether the `nix` version is at least 1.7 (at the time of writing this blog post). Then import [pkgs/top-level/all-packages.nix](https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/all-packages.nix). From now on, we will refer to this set of packages as **pkgs**. + +The `all-packages.nix` is then the file that composes all the packages. Note the `pkgs/` subdirectory, while nixos is in the `nixos/` subdirectory. + +The `all-packages.nix` is a bit contrived. First of all, it's a function. It accepts a couple of interesting parameters: + +- `system`: defaults to the current system + +- `config`: defaults to null + +- others... + +The **system** parameter, as per comment in the expression, it's the system for which the packages will be built. It allows for example to install i686 packages on amd64 machines. + +The **config** parameter is a simple attribute set. Packages can read some of its values and change the behavior of some derivations. + +## The system parameter + +You will find this parameter in many other .nix expressions (e.g. release expressions). The reason is that, given pkgs accepts a system parameter, then whenever you want to import pkgs you also want to pass through the value of system. E.g.: + +`myrelease.nix`: + +```nix +{ system ? builtins.currentSystem }: + +let pkgs = import { inherit system; }; +... +``` + +Why is it useful? With this parameter it's very easy to select a set of packages for a particular system. For example: + +```console +nix-build -A psmisc --argstr system i686-linux +``` + +This will build the `psmisc` derivation for i686-linux instead of x86_64-linux. This concept is very similar to multi-arch of Debian. + +The setup for cross compiling is also in `nixpkgs`, however it's a little contrived to talk about it and I don't know much of it either. + +## The config parameter + +I'm sure on the wiki or other manuals you've read about `~/.config/nixpkgs/config.nix` (previously `~/.nixpkgs/config.nix`) and I'm sure you've wondered whether that's hardcoded in nix. It's not, it's in [nixpkgs](https://github.com/NixOS/nixpkgs/blob/32c523914fdb8bf9cc7912b1eba023a8daaae2e8/pkgs/top-level/impure.nix#L28). + +The `all-packages.nix` expression accepts the `config` parameter. If it's `null`, then it reads the `NIXPKGS_CONFIG` environment variable. If not specified, `nixpkgs` will pick `$HOME/.config/nixpkgs/config.nix`. + +After determining `config.nix`, it will be imported as a nix expression, and that will be the value of `config` (in case it hasn't been passed as parameter to import ``). + +The `config` is available in the resulting repository: + +```console +$ nix repl +nix-repl> pkgs = import {} +nix-repl> pkgs.config +{ } +nix-repl> pkgs = import { config = { foo = "bar"; }; } +nix-repl> pkgs.config +{ foo = "bar"; } +``` + +What attributes go in `config` is a matter of convenience and conventions. + +For example, `config.allowUnfree` is an attribute that forbids building packages that have an unfree license by default. The `config.pulseaudio` setting tells whether to build packages with `pulseaudio` support or not where applicable and when the derivation obeys to the setting. + +## About .nix functions + +A `.nix` file contains a nix expression. Thus it can also be a function. I remind you that `nix-build` expects the expression to return a derivation. Therefore it's natural to return straight a derivation from a `.nix` file. However, it's also very natural for the `.nix` file to accept some parameters, in order to tweak the derivation being returned. + +In this case, nix does a trick: + +- If the expression is a derivation, build it. + +- If the expression is a function, call it and build the resulting derivation. + +For example you can nix-build the `.nix` file below: + +```nix +{ pkgs ? import {} }: + +pkgs.psmisc +``` + +Nix is able to call the function because the pkgs parameter has a default value. This allows you to pass a different value for pkgs using the `--arg` option. + +Does it work if you have a function returning a function that returns a derivation? No, Nix only calls the function it encounters once. + +## Conclusion + +We've unleashed the `` repository. It's a function that accepts some parameters, and returns the set of all packages. Due to laziness, only the accessed derivations will be built. + +You can use this repository to build your own packages as we've seen in the previous pill when creating our own repository. + +Lately I'm a little busy with the NixOS 14.11 release and other stuff, and I'm also looking toward migrating from blogger to a more coder-oriented blogging platform. So sorry for the delayed and shorter pills :) + +## Next pill + +...we will talk about overriding packages in the `nixpkgs` repository. What if you want to change some options of a library and let all other packages pick the new library? One possibility is to use, like described above, the `config` parameter when applicable. The other possibility is to override derivations. diff --git a/pills/16-nixpkgs-parameters.xml b/pills/16-nixpkgs-parameters.xml deleted file mode 100644 index 081a316..0000000 --- a/pills/16-nixpkgs-parameters.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - Nixpkgs Parameters - - Welcome to the 16th Nix pill. In the previous 15th pill we've realized how nix finds expressions with the angular brackets syntax, so that we finally know where <nixpkgs> is located on our system. - - - We can start diving into the nixpkgs repository, through all the various tools and design patterns. Please note that also nixpkgs has its own manual, underlying the difference between the general nix language and the nixpkgs repository. - -
- The default.nix expression - - We will not start inspecting packages at the beginning, rather the general structure of nixpkgs. - - - In our custom repository we created a default.nix which composed the expressions of the various packages. - - - Also nixpkgs has its own default.nix, which is the one being loaded when referring to <nixpkgs>. It does a simple thing: check whether the nix version is at least 1.7 (at the time of writing this blog post). Then import pkgs/top-level/all-packages.nix. From now on, we will refer to this set of packages as pkgs. - - - The all-packages.nix is then the file that composes all the packages. Note the pkgs/ subdirectory, while nixos is in the nixos/ subdirectory. - - - The all-packages.nix is a bit contrived. First of all, it's a function. It accepts a couple of interesting parameters: - - system: defaults to the current system - config: defaults to null - others... - - - - The system parameter, as per comment in the expression, it's the system for which the packages will be built. It allows for example to install i686 packages on amd64 machines. - - - The config parameter is a simple attribute set. Packages can read some of its values and change the behavior of some derivations. - -
-
- The system parameter - - You will find this parameter in many other .nix expressions (e.g. release expressions). The reason is that, given pkgs accepts a system parameter, then whenever you want to import pkgs you also want to pass through the value of system. E.g.: - - - myrelease.nix: - - - - Why is it useful? With this parameter it's very easy to select a set of packages for a particular system. For example: - - nix-build -A psmisc --argstr system i686-linux - - This will build the psmisc derivation for i686-linux instead of x86_64-linux. This concept is very similar to multi-arch of Debian. - - - The setup for cross compiling is also in nixpkgs, however it's a little contrived to talk about it and I don't know much of it either. - -
-
- The config parameter - - I'm sure on the wiki or other manuals you've read about ~/.config/nixpkgs/config.nix (previously ~/.nixpkgs/config.nix) and I'm sure you've wondered whether that's hardcoded in nix. It's not, it's in nixpkgs. - - - The all-packages.nix expression accepts the config parameter. If it's null, then it reads the NIXPKGS_CONFIG environment variable. If not specified, nixpkgs will pick $HOME/.config/nixpkgs/config.nix. - - - After determining config.nix, it will be imported as a nix expression, and that will be the value of config (in case it hasn't been passed as parameter to import <nixpkgs>). - - - The config is available in the resulting repository: - - - - What attributes go in config is a matter of convenience and conventions. - - - For example, config.allowUnfree is an attribute that forbids building packages that have an unfree license by default. The config.pulseaudio setting tells whether to build packages with pulseaudio support or not where applicable and when the derivation obeys to the setting. - -
-
- About .nix functions - - A .nix file contains a nix expression. Thus it can also be a function. I remind you that nix-build expects the expression to return a derivation. Therefore it's natural to return straight a derivation from a .nix file. However, it's also very natural for the .nix file to accept some parameters, in order to tweak the derivation being returned. - - - In this case, nix does a trick: - - If the expression is a derivation, build it. - If the expression is a function, call it and build the resulting derivation. - - - - For example you can nix-build the .nix file below: - - - - Nix is able to call the function because the pkgs parameter has a default value. This allows you to pass a different value for pkgs using the --arg option. - - - Does it work if you have a function returning a function that returns a derivation? No, Nix only calls the function it encounters once. - -
-
- Conclusion - - We've unleashed the <nixpkgs> repository. It's a function that accepts some parameters, and returns the set of all packages. Due to laziness, only the accessed derivations will be built. - - - You can use this repository to build your own packages as we've seen in the previous pill when creating our own repository. - - - Lately I'm a little busy with the NixOS 14.11 release and other stuff, and I'm also looking toward migrating from blogger to a more coder-oriented blogging platform. So sorry for the delayed and shorter pills :) - -
-
- Next pill - - ...we will talk about overriding packages in the nixpkgs repository. What if you want to change some options of a library and let all other packages pick the new library? One possibility is to use, like described above, the config parameter when applicable. The other possibility is to override derivations. - -
-
diff --git a/pills/16/config-foo-bar.txt b/pills/16/config-foo-bar.txt deleted file mode 100644 index f58c281..0000000 --- a/pills/16/config-foo-bar.txt +++ /dev/null @@ -1,7 +0,0 @@ -$ nix repl -nix-repl> pkgs = import {} -nix-repl> pkgs.config -{ } -nix-repl> pkgs = import { config = { foo = "bar"; }; } -nix-repl> pkgs.config -{ foo = "bar"; } diff --git a/pills/16/myrelease-nix.txt b/pills/16/myrelease-nix.txt deleted file mode 100644 index 993ce5f..0000000 --- a/pills/16/myrelease-nix.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ system ? builtins.currentSystem }: - -let pkgs = import { inherit system; }; -... diff --git a/pills/16/pkgs-psmisc.txt b/pills/16/pkgs-psmisc.txt deleted file mode 100644 index fc7f650..0000000 --- a/pills/16/pkgs-psmisc.txt +++ /dev/null @@ -1,3 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.psmisc diff --git a/pills/17-nixpkgs-overriding-packages.md b/pills/17-nixpkgs-overriding-packages.md new file mode 100644 index 0000000..b4cf7cd --- /dev/null +++ b/pills/17-nixpkgs-overriding-packages.md @@ -0,0 +1,141 @@ +# Nixpkgs Overriding Packages + +Welcome to the 17th Nix pill. In the previous [16th](16-nixpkgs-parameters.md) pill we have started to dive into the [nixpkgs repository](http://github.com/NixOS/nixpkgs). `Nixpkgs` is a function, and we've looked at some parameters like `system` and `config`. + +Today we'll talk about a special attribute: `config.packageOverrides`. Overriding packages in a set with fixed point can be considered another design pattern in nixpkgs. + +## Overriding a package + +Recall the override design pattern from the [nix pill 14](14-override-design-pattern.md). Instead of calling a function with parameters directly, we make the call (function + parameters) overridable. + +We put the override function in the returned attribute set of the original function call. + +Take for example `graphviz`. It has an input parameter `xorg`. If it's null, then `graphviz` will build without X support. + +```console +$ nix repl +nix-repl> :l +Added 4360 variables. +nix-repl> :b graphviz.override { withXorg = false; } +``` + +This will build `graphviz` without X support, it's as simple as that. + +However, let's say a package `P` depends on `graphviz`, how do we make `P` depend on the new `graphviz` without X support? + +## In an imperative world... + +...you could do something like this: + +```nix +pkgs = import {}; +pkgs.graphviz = pkgs.graphviz.override { withXorg = false; }; +build(pkgs.P) +``` + +Given `pkgs.P` depends on `pkgs.graphviz`, it's easy to build `P` with the replaced `graphviz`. In a pure functional language it's not that easy because you can assign to variables only once. + +## Fixed point + +The fixed point with lazy evaluation is crippling but about necessary in a language like Nix. It lets us achieve something similar to what we'd do imperatively. + +Follows the definition of fixed point in [nixpkgs](https://github.com/NixOS/nixpkgs/blob/f224a4f1b32b3e813783d22de54e231cd8ea2448/lib/fixed-points.nix#L19): + +```nix +{ + # Take a function and evaluate it with its own returned value. + fix = + f: + let + result = f result; + in + result; +} +``` + +It's a function that accepts a function `f`, calls `f result` on the result just returned by `f result` and returns it. In other words it's `f(f(f(....` + +At first sight, it's an infinite loop. With lazy evaluation it isn't, because the call is done only when needed. + +```console +nix-repl> fix = f: let result = f result; in result +nix-repl> pkgs = self: { a = 3; b = 4; c = self.a+self.b; } +nix-repl> fix pkgs +{ a = 3; b = 4; c = 7; } +``` + +Without the `rec` keyword, we were able to refer to `a` and `b` of the same set. + +- First `pkgs` gets called with an unevaluated thunk `(pkgs(pkgs(...)` + +- To set the value of `c` then `self.a` and `self.b` are evaluated. + +- The `pkgs` function gets called again to get the value of `a` and `b`. + +The trick is that `c` is not needed to be evaluated in the inner call, thus it doesn't go in an infinite loop. + +Won't go further with the explanation here. A good post about fixed point and Nix can be [found here](http://r6.ca/blog/20140422T142911Z.html). + +### Overriding a set with fixed point + +Given that `self.a` and `self.b` refer to the passed set and not to the literal set in the function, we're able to override both `a` and `b` and get a new value for `c`: + +```console +nix-repl> overrides = { a = 1; b = 2; } +nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs +{ a = 3; b = 4; c = 3; } +nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs // overrides +{ a = 1; b = 2; c = 3; } +``` + +In the first case we computed pkgs with the overrides, in the second case we also included the overridden attributes in the result. + +## Overriding nixpkgs packages + +We've seen how to override attributes in a set such that they get recursively picked by dependent attributes. This approach can be used for derivations too, after all `nixpkgs` is a giant set of attributes that depend on each other. + +To do this, `nixpkgs` offers `config.packageOverrides`. So `nixpkgs` returns a fixed point of the package set, and `packageOverrides` is used to inject the overrides. + +Create a `config.nix` file like this somewhere: + +```nix +{ + packageOverrides = pkgs: { + graphviz = pkgs.graphviz.override { + # disable xorg support + withXorg = false; + }; + }; +} +``` + +Now we can build e.g. `asciidoc-full` and it will automatically use the overridden `graphviz`: + +```console +nix-repl> pkgs = import { config = import ./config.nix; } +nix-repl> :b pkgs.asciidoc-full +``` + +Note how we pass the `config` with `packageOverrides` when importing `nixpkgs`. Then `pkgs.asciidoc-full` is a derivation that has `graphviz` input (`pkgs.asciidoc` is the lighter version and doesn't use `graphviz` at all). + +Since there's no version of `asciidoc` with `graphviz` without X support in the binary cache, Nix will recompile the needed stuff for you. + +## The \~/.config/nixpkgs/config.nix file + +In the previous pill we already talked about this file. The above `config.nix` that we just wrote could be the content of `~/.config/nixpkgs/config.nix` (or the deprecated location `~/.nixpkgs/config.nix`). + +Instead of passing it explicitly whenever we import `nixpkgs`, it will be automatically [imported by nixpkgs](https://github.com/NixOS/nixpkgs/blob/32c523914fdb8bf9cc7912b1eba023a8daaae2e8/pkgs/top-level/impure.nix#L28). + +## Conclusion + +We've learned about a new design pattern: using fixed point for overriding packages in a package set. + +Whereas in an imperative setting, like with other package managers, a library is installed replacing the old version and applications will use it, in Nix it's not that straight and simple. But it's more precise. + +Nix applications will depend on specific versions of libraries, hence the reason why we have to recompile `asciidoc` to use the new `graphviz` library. + +The newly built `asciidoc` will depend on the new `graphviz`, and old `asciidoc` will keep using the old `graphviz` undisturbed. + +## Next pill + +...we will stop studying `nixpkgs` for a moment and talk about store paths. How does Nix compute the path in the store where to place the result of builds? How to add files to the store for which we have an integrity hash? diff --git a/pills/17-nixpkgs-overriding-packages.xml b/pills/17-nixpkgs-overriding-packages.xml deleted file mode 100644 index 5d8fd36..0000000 --- a/pills/17-nixpkgs-overriding-packages.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - Nixpkgs Overriding Packages - - Welcome to the 17th Nix pill. In the previous 16th pill we have started to dive into the nixpkgs repository. Nixpkgs is a function, and we've looked at some parameters like system and config. - - - Today we'll talk about a special attribute: config.packageOverrides. Overriding packages in a set with fixed point can be considered another design pattern in nixpkgs. - -
- Overriding a package - - Recall the override design pattern from the nix pill 14. Instead of calling a function with parameters directly, we make the call (function + parameters) overridable. - - - We put the override function in the returned attribute set of the original function call. - - - Take for example graphviz. It has an input parameter xorg. If it's null, then graphviz will build without X support. - - - - This will build graphviz without X support, it's as simple as that. - - - However, let's say a package P depends on graphviz, how do we make P depend on the new graphviz without X support? - - -
-
- In an imperative world... - - ...you could do something like this: - - - - Given pkgs.P depends on pkgs.graphviz, it's easy to build P with the replaced graphviz. In a pure functional language it's not that easy because you can assign to variables only once. - -
-
- Fixed point - - The fixed point with lazy evaluation is crippling but about necessary in a language like Nix. It lets us achieve something similar to what we'd do imperatively. - - - Follows the definition of fixed point in nixpkgs: - - - - It's a function that accepts a function f, calls f result on the result just returned by f result and returns it. In other words it's f(f(f(.... - - - At first sight, it's an infinite loop. With lazy evaluation it isn't, because the call is done only when needed. - - - - Without the rec keyword, we were able to refer to a and b of the same set. - - First pkgs gets called with an unevaluated thunk (pkgs(pkgs(...) - To set the value of c then self.a and self.b are evaluated. - The pkgs function gets called again to get the value of a and b. - - - - The trick is that c is not needed to be evaluated in the inner call, thus it doesn't go in an infinite loop. - - - Won't go further with the explanation here. A good post about fixed point and Nix can be found here. - - -
- Overriding a set with fixed point - - Given that self.a and self.b refer to the passed set and not to the literal set in the function, we're able to override both a and b and get a new value for c: - - - - In the first case we computed pkgs with the overrides, in the second case we also included the overridden attributes in the result. - -
- -
-
- Overriding nixpkgs packages - - We've seen how to override attributes in a set such that they get recursively picked by dependent attributes. This approach can be used for derivations too, after all nixpkgs is a giant set of attributes that depend on each other. - - - To do this, nixpkgs offers config.packageOverrides. So nixpkgs returns a fixed point of the package set, and packageOverrides is used to inject the overrides. - - - Create a config.nix file like this somewhere: - - - - Now we can build e.g. asciidoc-full and it will automatically use the overridden graphviz: - - - - Note how we pass the config with packageOverrides when importing nixpkgs. Then pkgs.asciidoc-full is a derivation that has graphviz input (pkgs.asciidoc is the lighter version and doesn't use graphviz at all). - - - Since there's no version of asciidoc with graphviz without X support in the binary cache, Nix will recompile the needed stuff for you. - -
-
- The ~/.config/nixpkgs/config.nix file - - In the previous pill we already talked about this file. The above config.nix that we just wrote could be the content of ~/.config/nixpkgs/config.nix (or the deprecated location ~/.nixpkgs/config.nix). - - - Instead of passing it explicitly whenever we import nixpkgs, it will be automatically imported by nixpkgs. - -
-
- Conclusion - - We've learned about a new design pattern: using fixed point for overriding packages in a package set. - - - Whereas in an imperative setting, like with other package managers, a library is installed replacing the old version and applications will use it, in Nix it's not that straight and simple. But it's more precise. - - - Nix applications will depend on specific versions of libraries, hence the reason why we have to recompile asciidoc to use the new graphviz library. - - - The newly built asciidoc will depend on the new graphviz, and old asciidoc will keep using the old graphviz undisturbed. - -
-
- Next pill - - ...we will stop studying nixpkgs for a moment and talk about store paths. How does Nix compute the path in the store where to place the result of builds? How to add files to the store for which we have an integrity hash? - - -
-
diff --git a/pills/17/build-asciidoc-graphviz-override.txt b/pills/17/build-asciidoc-graphviz-override.txt deleted file mode 100644 index c1c883d..0000000 --- a/pills/17/build-asciidoc-graphviz-override.txt +++ /dev/null @@ -1,2 +0,0 @@ -nix-repl> pkgs = import { config = import ./config.nix; } -nix-repl> :b pkgs.asciidoc-full diff --git a/pills/17/config-nix.txt b/pills/17/config-nix.txt deleted file mode 100644 index 078afad..0000000 --- a/pills/17/config-nix.txt +++ /dev/null @@ -1,8 +0,0 @@ -{ - packageOverrides = pkgs: { - graphviz = pkgs.graphviz.override { - # disable xorg support - withXorg = false; - }; - }; -} diff --git a/pills/17/fix-function.txt b/pills/17/fix-function.txt deleted file mode 100644 index d1a3b5c..0000000 --- a/pills/17/fix-function.txt +++ /dev/null @@ -1,9 +0,0 @@ -{ - # Take a function and evaluate it with its own returned value. - fix = - f: - let - result = f result; - in - result; -} diff --git a/pills/17/fix-pkgs-function.txt b/pills/17/fix-pkgs-function.txt deleted file mode 100644 index 4dc34f7..0000000 --- a/pills/17/fix-pkgs-function.txt +++ /dev/null @@ -1,4 +0,0 @@ -nix-repl> fix = f: let result = f result; in result -nix-repl> pkgs = self: { a = 3; b = 4; c = self.a+self.b; } -nix-repl> fix pkgs -{ a = 3; b = 4; c = 7; } diff --git a/pills/17/graphviz-override.txt b/pills/17/graphviz-override.txt deleted file mode 100644 index 5ecc6c2..0000000 --- a/pills/17/graphviz-override.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ nix repl -nix-repl> :l -Added 4360 variables. -nix-repl> :b graphviz.override { withXorg = false; } diff --git a/pills/17/newpkgs-override-set.txt b/pills/17/newpkgs-override-set.txt deleted file mode 100644 index 50210c9..0000000 --- a/pills/17/newpkgs-override-set.txt +++ /dev/null @@ -1,5 +0,0 @@ -nix-repl> overrides = { a = 1; b = 2; } -nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs -{ a = 3; b = 4; c = 3; } -nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs // overrides -{ a = 1; b = 2; c = 3; } diff --git a/pills/17/p-graphviz-override.txt b/pills/17/p-graphviz-override.txt deleted file mode 100644 index 91984ac..0000000 --- a/pills/17/p-graphviz-override.txt +++ /dev/null @@ -1,3 +0,0 @@ -pkgs = import {}; -pkgs.graphviz = pkgs.graphviz.override { withXorg = false; }; -build(pkgs.P) diff --git a/pills/18-nix-store-paths.md b/pills/18-nix-store-paths.md new file mode 100644 index 0000000..c08f82f --- /dev/null +++ b/pills/18-nix-store-paths.md @@ -0,0 +1,184 @@ +# Nix Store Paths + +Welcome to the 18th Nix pill. In the previous [17th](17-nixpkgs-overriding-packages.md) pill we have scratched the surface of the `nixpkgs` repository structure. It is a set of packages, and it's possible to override such packages so that all other packages will use the overrides. + +Before reading existing derivations, I'd like to talk about store paths and how they are computed. In particular we are interested in fixed store paths that depend on an integrity hash (e.g. a sha256), which is usually applied to source tarballs. + +The way store paths are computed is a little contrived, mostly due to historical reasons. Our reference will be the [Nix source code](https://github.com/NixOS/nix/blob/07f992a74b64f4376d5b415d0042babc924772f3/src/libstore/store-api.cc#L197). + +## Source paths + +Let's start simple. You know nix allows relative paths to be used, such that the file or directory is stored in the nix store, that is `./myfile` gets stored into `/nix/store/.......` We want to understand how is the store path generated for such a file: + +```console +$ echo mycontent > myfile +``` + +I remind you, the simplest derivation you can write has a `name`, a `builder` and the `system`: + +```console +$ nix repl +nix-repl> derivation { system = "x86_64-linux"; builder = ./myfile; name = "foo"; } +«derivation /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv» +``` + +Now inspect the .drv to see where is `./myfile` being stored: + +```console +$ nix derivation show /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv +{ + "/nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv": { + "outputs": { + "out": { + "path": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo" + } + }, + "inputSrcs": [ + "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile" + ], + "inputDrvs": {}, + "platform": "x86_64-linux", + "builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile", + "args": [], + "env": { + "builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile", + "name": "foo", + "out": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo", + "system": "x86_64-linux" + } + } +} +``` + +Great, how did nix decide to use `xv2iccirbrvklck36f1g7vldn5v58vck` ? Keep looking at the nix comments. + +**Note:** doing `nix-store --add myfile` will store the file in the same store path. + +### Step 1, compute the hash of the file + +The comments tell us to first compute the sha256 of the NAR serialization of the file. Can be done in two ways: + +```console +$ nix-hash --type sha256 myfile +2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 +``` + +Or: + +```console +$ nix-store --dump myfile|sha256sum +2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 +``` + +In general, Nix understands two contents: flat for regular files, or recursive for NAR serializations which can be anything. + +### Step 2, build the string description + +Then nix uses a special string which includes the hash, the path type and the file name. We store this in another file: + +```console +$ echo -n "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile" > myfile.str +``` + +### Step 3, compute the final hash + +Finally the comments tell us to compute the base-32 representation of the first 160 bits (truncation) of a sha256 of the above string: + +```console +$ nix-hash --type sha256 --truncate --base32 --flat myfile.str +xv2iccirbrvklck36f1g7vldn5v58vck +``` + +## Output paths + +Output paths are usually generated for derivations. We use the above example because it's simple. Even if we didn't build the derivation, nix knows the out path `hs0yi5n5nw6micqhy8l1igkbhqdkzqa1`. This is because the out path only depends on inputs. + +It's computed in a similar way to source paths, except that the .drv is hashed and the type of derivation is `output:out`. In case of multiple outputs, we may have different `output:`. + +At the time nix computes the out path, the .drv contains an empty string for each out path. So what we do is getting our .drv and replacing the out path with an empty string: + +```console +$ cp -f /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv myout.drv +$ sed -i 's,/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo,,g' myout.drv +``` + +The `myout.drv` is the .drv state in which nix is when computing the out path for our derivation: + +```console +$ sha256sum myout.drv +1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5 myout.drv +$ echo -n "output:out:sha256:1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5:/nix/store:foo" > myout.str +$ nix-hash --type sha256 --truncate --base32 --flat myout.str +hs0yi5n5nw6micqhy8l1igkbhqdkzqa1 +``` + +Then nix puts that out path in the .drv, and that's it. + +In case the .drv has input derivations, that is it references other .drv, then such .drv paths are replaced by this same algorithm which returns a hash. + +In other words, you get a final .drv where every other .drv path is replaced by its hash. + +## Fixed-output paths + +Finally, the other most used kind of path is when we know beforehand an integrity hash of a file. This is usual for tarballs. + +A derivation can take three special attributes: `outputHashMode`, `outputHash` and `outputHashAlgo` which are well documented in the [nix manual](https://nixos.org/manual/nix/stable/expressions/advanced-attributes.html). + +The builder must create the out path and make sure its hash is the same as the one declared with `outputHash`. + +Let's say our builder should create a file whose contents is `mycontent`: + +```console +$ echo mycontent > myfile +$ sha256sum myfile +f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb myfile +nix-repl> derivation { name = "bar"; system = "x86_64-linux"; builder = "none"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb"; } +«derivation /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv» +``` + +Inspect the .drv and see that it also stored the fact that it's a fixed-output derivation with sha256 algorithm, compared to the previous examples: + +```console +$ nix derivation show /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv +{ + "/nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv": { + "outputs": { + "out": { + "path": "/nix/store/a00d5f71k0vp5a6klkls0mvr1f7sx6ch-bar", + "hashAlgo": "sha256", + "hash": "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb" + } + }, +[...] +} +``` + +It doesn't matter which input derivations are being used, the final out path must only depend on the declared hash. + +What nix does is to create an intermediate string representation of the fixed-output content: + +```console +$ echo -n "fixed:out:sha256:f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb:" > mycontent.str +$ sha256sum mycontent.str +423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639 myfile.str +``` + +Then proceed as it was a normal derivation output path: + +```console +$ echo -n "output:out:sha256:423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639:/nix/store:bar" > myfile.str +$ nix-hash --type sha256 --truncate --base32 --flat myfile.str +a00d5f71k0vp5a6klkls0mvr1f7sx6ch +``` + +Hence, the store path only depends on the declared fixed-output hash. + +## Conclusion + +There are other types of store paths, but you get the idea. Nix first hashes the contents, then creates a string description, and the final store path is the hash of this string. + +Also we've introduced some fundamentals, in particular the fact that Nix knows beforehand the out path of a derivation since it only depends on the inputs. We've also introduced fixed-output derivations which are especially used by the nixpkgs repository for downloading and verifying source tarballs. + +## Next pill + +...we will introduce `stdenv`. In the previous pills we rolled our own `mkDerivation` convenience function for wrapping the builtin derivation, but the `nixpkgs` repository also has its own convenience functions for dealing with `autotools` projects and other build systems. diff --git a/pills/18-nix-store-paths.xml b/pills/18-nix-store-paths.xml deleted file mode 100644 index ee61666..0000000 --- a/pills/18-nix-store-paths.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - Nix Store Paths - - Welcome to the 18th Nix pill. In the previous 17th pill we have scratched the surface of the nixpkgs repository structure. It is a set of packages, and it's possible to override such packages so that all other packages will use the overrides. - - - Before reading existing derivations, I'd like to talk about store paths and how they are computed. In particular we are interested in fixed store paths that depend on an integrity hash (e.g. a sha256), which is usually applied to source tarballs. - - - The way store paths are computed is a little contrived, mostly due to historical reasons. Our reference will be the Nix source code. - -
- Source paths - - Let's start simple. You know nix allows relative paths to be used, such that the file or directory is stored in the nix store, that is ./myfile gets stored into /nix/store/....... We want to understand how is the store path generated for such a file: - - $ echo mycontent > myfile - - I remind you, the simplest derivation you can write has a name, a builder and the system: - - - - Now inspect the .drv to see where is ./myfile being stored: - - - - Great, how did nix decide to use xv2iccirbrvklck36f1g7vldn5v58vck ? Keep looking at the nix comments. - - - Note: doing nix-store --add myfile will store the file in the same store path. - - -
- Step 1, compute the hash of the file - - The comments tell us to first compute the sha256 of the NAR serialization of the file. Can be done in two ways: - - - - Or: - - - - In general, Nix understands two contents: flat for regular files, or recursive for NAR serializations which can be anything. - -
-
- Step 2, build the string description - - Then nix uses a special string which includes the hash, the path type and the file name. We store this in another file: - - $ echo -n "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile" > myfile.str -
-
- Step 3, compute the final hash - - Finally the comments tell us to compute the base-32 representation of the first 160 bits (truncation) of a sha256 of the above string: - - -
-
-
- Output paths - - Output paths are usually generated for derivations. We use the above example because it's simple. Even if we didn't build the derivation, nix knows the out path hs0yi5n5nw6micqhy8l1igkbhqdkzqa1. This is because the out path only depends on inputs. - - - It's computed in a similar way to source paths, except that the .drv is hashed and the type of derivation is output:out. In case of multiple outputs, we may have different output:<id>. - - - At the time nix computes the out path, the .drv contains an empty string for each out path. So what we do is getting our .drv and replacing the out path with an empty string: - - - - The myout.drv is the .drv state in which nix is when computing the out path for our derivation: - - - - Then nix puts that out path in the .drv, and that's it. - - - In case the .drv has input derivations, that is it references other .drv, then such .drv paths are replaced by this same algorithm which returns a hash. - - - In other words, you get a final .drv where every other .drv path is replaced by its hash. - -
-
- Fixed-output paths - - Finally, the other most used kind of path is when we know beforehand an integrity hash of a file. This is usual for tarballs. - - - A derivation can take three special attributes: outputHashMode, outputHash and outputHashAlgo which are well documented in the nix manual. - - - The builder must create the out path and make sure its hash is the same as the one declared with outputHash. - - - Let's say our builder should create a file whose contents is mycontent: - - - - Inspect the .drv and see that it also stored the fact that it's a fixed-output derivation with sha256 algorithm, compared to the previous examples: - - - - It doesn't matter which input derivations are being used, the final out path must only depend on the declared hash. - - - What nix does is to create an intermediate string representation of the fixed-output content: - - - - Then proceed as it was a normal derivation output path: - - - - Hence, the store path only depends on the declared fixed-output hash. - -
-
- Conclusion - - There are other types of store paths, but you get the idea. Nix first hashes the contents, then creates a string description, and the final store path is the hash of this string. - - - Also we've introduced some fundamentals, in particular the fact that Nix knows beforehand the out path of a derivation since it only depends on the inputs. We've also introduced fixed-output derivations which are especially used by the nixpkgs repository for downloading and verifying source tarballs. - -
-
- Next pill - - ...we will introduce stdenv. In the previous pills we rolled our own mkDerivation convenience function for wrapping the builtin derivation, but the nixpkgs repository also has its own convenience functions for dealing with autotools projects and other build systems. - - -
-
diff --git a/pills/18/bar-derivation.xml b/pills/18/bar-derivation.xml deleted file mode 100644 index aeb5217..0000000 --- a/pills/18/bar-derivation.xml +++ /dev/null @@ -1,12 +0,0 @@ -$ nix derivation show /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv -{ - "/nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv": { - "outputs": { - "out": { - "path": "/nix/store/a00d5f71k0vp5a6klkls0mvr1f7sx6ch-bar", - "hashAlgo": "sha256", - "hash": "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb" - } - }, -[...] -} diff --git a/pills/18/derivation-simple-content.xml b/pills/18/derivation-simple-content.xml deleted file mode 100644 index 9be1bad..0000000 --- a/pills/18/derivation-simple-content.xml +++ /dev/null @@ -1,23 +0,0 @@ -$ nix derivation show /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv -{ - "/nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv": { - "outputs": { - "out": { - "path": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo" - } - }, - "inputSrcs": [ - "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile" - ], - "inputDrvs": {}, - "platform": "x86_64-linux", - "builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile", - "args": [], - "env": { - "builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile", - "name": "foo", - "out": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo", - "system": "x86_64-linux" - } - } -} diff --git a/pills/18/derivation-simple.txt b/pills/18/derivation-simple.txt deleted file mode 100644 index 716e921..0000000 --- a/pills/18/derivation-simple.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ nix repl -nix-repl> derivation { system = "x86_64-linux"; builder = ./myfile; name = "foo"; } -«derivation /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv» diff --git a/pills/18/mycontent-string-representation-hash.txt b/pills/18/mycontent-string-representation-hash.txt deleted file mode 100644 index e7a5d09..0000000 --- a/pills/18/mycontent-string-representation-hash.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ echo -n "output:out:sha256:423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639:/nix/store:bar" > myfile.str -$ nix-hash --type sha256 --truncate --base32 --flat myfile.str -a00d5f71k0vp5a6klkls0mvr1f7sx6ch diff --git a/pills/18/mycontent-string-representation.txt b/pills/18/mycontent-string-representation.txt deleted file mode 100644 index d30da2d..0000000 --- a/pills/18/mycontent-string-representation.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ echo -n "fixed:out:sha256:f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb:" > mycontent.str -$ sha256sum mycontent.str -423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639 myfile.str - diff --git a/pills/18/mycontent.txt b/pills/18/mycontent.txt deleted file mode 100644 index 7d13e80..0000000 --- a/pills/18/mycontent.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ echo mycontent > myfile -$ sha256sum myfile -f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb myfile -nix-repl> derivation { name = "bar"; system = "x86_64-linux"; builder = "none"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb"; } -«derivation /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv» diff --git a/pills/18/myfile-final-hash.txt b/pills/18/myfile-final-hash.txt deleted file mode 100644 index a5b6c10..0000000 --- a/pills/18/myfile-final-hash.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-hash --type sha256 --truncate --base32 --flat myfile.str -xv2iccirbrvklck36f1g7vldn5v58vck diff --git a/pills/18/myfile-hash-alternate.txt b/pills/18/myfile-hash-alternate.txt deleted file mode 100644 index a5dca21..0000000 --- a/pills/18/myfile-hash-alternate.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-store --dump myfile|sha256sum -2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 diff --git a/pills/18/myfile-hash.txt b/pills/18/myfile-hash.txt deleted file mode 100644 index cbaf8f1..0000000 --- a/pills/18/myfile-hash.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ nix-hash --type sha256 myfile -2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3 diff --git a/pills/18/myfile-string-hash.txt b/pills/18/myfile-string-hash.txt deleted file mode 100644 index e7a5d09..0000000 --- a/pills/18/myfile-string-hash.txt +++ /dev/null @@ -1,3 +0,0 @@ -$ echo -n "output:out:sha256:423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639:/nix/store:bar" > myfile.str -$ nix-hash --type sha256 --truncate --base32 --flat myfile.str -a00d5f71k0vp5a6klkls0mvr1f7sx6ch diff --git a/pills/18/myout-drv-hash.txt b/pills/18/myout-drv-hash.txt deleted file mode 100644 index fa9d2a4..0000000 --- a/pills/18/myout-drv-hash.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ sha256sum myout.drv -1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5 myout.drv -$ echo -n "output:out:sha256:1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5:/nix/store:foo" > myout.str -$ nix-hash --type sha256 --truncate --base32 --flat myout.str -hs0yi5n5nw6micqhy8l1igkbhqdkzqa1 diff --git a/pills/18/output-path-replace-empty.txt b/pills/18/output-path-replace-empty.txt deleted file mode 100644 index 6a7eebf..0000000 --- a/pills/18/output-path-replace-empty.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ cp -f /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv myout.drv -$ sed -i 's,/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo,,g' myout.drv diff --git a/pills/19-fundamentals-of-stdenv.md b/pills/19-fundamentals-of-stdenv.md new file mode 100644 index 0000000..ca96d52 --- /dev/null +++ b/pills/19-fundamentals-of-stdenv.md @@ -0,0 +1,214 @@ +# Fundamentals of Stdenv + +Welcome to the 19th Nix pill. In the previous [18th](18-nix-store-paths.md) pill we dived into the algorithm used by Nix to compute the store paths, and also introduced fixed-output store paths. + +This time we will instead look into `nixpkgs`, in particular one of its core derivations: `stdenv`. + +The `stdenv` is not treated as a special derivation by Nix, but it's very important for the `nixpkgs` repository. It serves as a base for packaging software. It is used to pull in dependencies such as the GCC toolchain, GNU make, core utilities, patch and diff utilities, and so on: basic tools needed to compile a huge pile of software currently present in `nixpkgs`. + +## What is stdenv? + +First of all, `stdenv` is a derivation, and it's a very simple one: + +```console +$ nix-build '' -A stdenv +/nix/store/k4jklkcag4zq4xkqhkpy156mgfm34ipn-stdenv +$ ls -R result/ +result/: +nix-support/ setup + +result/nix-support: +propagated-user-env-packages +``` + +It has just two files: `/setup` and `/nix-support/propagated-user-env-packages`. Don't worry about the latter. It's empty, in fact. The important file is `/setup`. + +How can this simple derivation pull in all of the toolchain and basic tools needed to compile packages? Let's look at the runtime dependencies: + +```console +$ nix-store -q --references result +/nix/store/3a45nb37s0ndljp68228snsqr3qsyp96-bzip2-1.0.6 +/nix/store/a457ywa1haa0sgr9g7a1pgldrg3s798d-coreutils-8.24 +/nix/store/zmd4jk4db5lgxb8l93mhkvr3x92g2sx2-bash-4.3-p39 +/nix/store/47sfpm2qclpqvrzijizimk4md1739b1b-gcc-wrapper-4.9.3 +... +``` + +How can it be? The package must be referring to those other packages somehow. In fact, they are hardcoded in the `/setup` file: + +```console +$ head result/setup +export SHELL=/nix/store/zmd4jk4db5lgxb8l93mhkvr3x92g2sx2-bash-4.3-p39/bin/bash +initialPath="/nix/store/a457ywa1haa0sgr9g7a1pgldrg3s798d-coreutils-8.24 ..." +defaultNativeBuildInputs="/nix/store/sgwq15xg00xnm435gjicspm048rqg9y6-patchelf-0.8 ..." +``` + +## The setup file + +Remember our generic `builder.sh` in [Pill 8](08-generic-builders.md)? It sets up a basic `PATH`, unpacks the source and runs the usual `autotools` commands for us. + +The [stdenv setup file](https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh) is exactly that. It sets up several environment variables like `PATH` and creates some helper bash functions to build a package. I invite you to read it. + +The hardcoded toolchain and utilities are used to initially fill up the environment variables so that it's more pleasant to run common commands, similar to what we did with our builder with `baseInputs` and `buildInputs`. + +The build with `stdenv` works in phases. Phases are like `unpackPhase`, `configurePhase`, `buildPhase`, `checkPhase`, `installPhase`, `fixupPhase`. You can see the default list in the `genericBuild` function. + +What `genericBuild` does is just run these phases. Default phases are just bash functions. You can easily read them. + +Every phase has hooks to run commands before and after the phase has been executed. Phases can be overwritten, reordered, whatever, it's just bash code. + +How to use this file? Like our old builder. To test it, we enter a fake empty derivation, source the `stdenv` `setup`, unpack the hello sources and build it: + +```console +$ nix-shell -E 'derivation { name = "fake"; builder = "fake"; system = "x86_64-linux"; }' +nix-shell$ unset PATH +nix-shell$ source /nix/store/k4jklkcag4zq4xkqhkpy156mgfm34ipn-stdenv/setup +nix-shell$ tar -xf hello-2.10.tar.gz +nix-shell$ cd hello-2.10 +nix-shell$ configurePhase +... +nix-shell$ buildPhase +... +``` + +_I unset `PATH` to further show that the `stdenv` is sufficiently self-contained to build autotools packages that have no other dependencies._ + +So we ran the `configurePhase` function and `buildPhase` function and they worked. These bash functions should be self-explanatory. You can read the code in the `setup` file. + +## How the setup file is built + +Until now we worked with plain bash scripts. What about the Nix side? The `nixpkgs` repository offers a useful function, like we did with our old builder. It is a wrapper around the raw derivation function which pulls in the `stdenv` for us, and runs `genericBuild`. It's [stdenv.mkDerivation](https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/make-derivation.nix). + +Note how `stdenv` is a derivation but it's also an attribute set which contains some other attributes, like `mkDerivation`. Nothing fancy here, just convenience. + +Let's write a `hello.nix` expression using this newly discovered `stdenv`: + +```nix +with import { }; +stdenv.mkDerivation { + name = "hello"; + src = ./hello-2.10.tar.gz; +} +``` + +Don't be scared by the `with` expression. It pulls the `nixpkgs` repository into scope, so we can directly use `stdenv`. It looks very similar to the hello expression in [Pill 8](08-generic-builders.md). + +It builds, and runs fine: + +```console +$ nix-build hello.nix +... +/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello +$ result/bin/hello +Hello, world! +``` + +## The stdenv.mkDerivation builder + +Let's take a look at the builder used by `mkDerivation`. You can read the code [here in nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/make-derivation.nix): + +```nix +{ + # ... + builder = attrs.realBuilder or shell; + args = + attrs.args or [ + "-e" + (attrs.builder or ./default-builder.sh) + ]; + stdenv = result; + # ... +} +``` + +Also take a look at our old derivation wrapper in previous pills! The builder is bash (that shell variable), the argument to the builder (bash) is `default-builder.sh`, and then we add the environment variable `$stdenv` in the derivation which is the `stdenv` derivation. + +You can open [default-builder.sh](https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/default-builder.sh) and see what it does: + +```sh +source $stdenv/setup +genericBuild +``` + +It's what we did in [Pill 10](10-developing-with-nix-shell.md) to make the derivations `nix-shell` friendly. When entering the shell, the setup file only sets up the environment without building anything. When doing `nix-build`, it actually runs the build process. + +To get a clear understanding of the environment variables, look at the .drv of the hello derivation: + +```console +$ nix derivation show $(nix-instantiate hello.nix) +warning: you did not specify '--add-root'; the result might be removed by the garbage collector +{ + "/nix/store/abwj50lycl0m515yblnrvwyydlhhqvj2-hello.drv": { + "outputs": { + "out": { + "path": "/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello" + } + }, + "inputSrcs": [ + "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh", + "/nix/store/svc70mmzrlgq42m9acs0prsmci7ksh6h-hello-2.10.tar.gz" + ], + "inputDrvs": { + "/nix/store/hcgwbx42mcxr7ksnv0i1fg7kw6jvxshb-bash-4.4-p19.drv": [ + "out" + ], + "/nix/store/sfxh3ybqh97cgl4s59nrpi78kgcc8f3d-stdenv-linux.drv": [ + "out" + ] + }, + "platform": "x86_64-linux", + "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", + "args": [ + "-e", + "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh" + ], + "env": { + "buildInputs": "", + "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", + "configureFlags": "", + "depsBuildBuild": "", + "depsBuildBuildPropagated": "", + "depsBuildTarget": "", + "depsBuildTargetPropagated": "", + "depsHostBuild": "", + "depsHostBuildPropagated": "", + "depsTargetTarget": "", + "depsTargetTargetPropagated": "", + "name": "hello", + "nativeBuildInputs": "", + "out": "/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello", + "propagatedBuildInputs": "", + "propagatedNativeBuildInputs": "", + "src": "/nix/store/svc70mmzrlgq42m9acs0prsmci7ksh6h-hello-2.10.tar.gz", + "stdenv": "/nix/store/6kz2vbh98s2r1pfshidkzhiy2s2qdw0a-stdenv-linux", + "system": "x86_64-linux" + } + } +} +``` + +It's so short I decided to paste it entirely above. The builder is bash, with `-e default-builder.sh` arguments. Then you can see the `src` and `stdenv` environment variables. + +The last bit, the `unpackPhase` in the setup, is used to unpack the sources and enter the directory. Again, like we did in our old builder. + +## Conclusion + +The `stdenv` is the core of the `nixpkgs` repository. All packages use the `stdenv.mkDerivation` wrapper instead of the raw derivation. It does a bunch of operations for us and also sets up a pleasant build environment. + +The overall process is simple: + +- `nix-build` + +- `bash -e default-builder.sh` + +- `source $stdenv/setup` + +- `genericBuild` + +That's it. Everything you need to know about the stdenv phases is in the [setup file](https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh). + +Really, take your time to read that file. Don't forget that juicy docs are also available in the [nixpkgs manual](http://nixos.org/nixpkgs/manual/#chap-stdenv). + +## Next pill... + +...we will talk about how to add dependencies to our packages with `buildInputs` and `propagatedBuildInputs`, and influence downstream builds with setup hooks and env hooks. These concepts are crucial to how `nixpkgs` packages are composed. diff --git a/pills/19-fundamentals-of-stdenv.xml b/pills/19-fundamentals-of-stdenv.xml deleted file mode 100644 index cea606a..0000000 --- a/pills/19-fundamentals-of-stdenv.xml +++ /dev/null @@ -1,187 +0,0 @@ - - - Fundamentals of Stdenv - - - Welcome to the 19th Nix pill. In the previous 18th pill we dived into the algorithm used by Nix to compute the store paths, and also introduced fixed-output store paths. - - - - This time we will instead look into nixpkgs, in particular one of its core derivations: stdenv. - - - - The stdenv is not treated as a special derivation by Nix, but it's very important for the nixpkgs repository. It serves as a base for packaging software. It is used to pull in dependencies such as the GCC toolchain, GNU make, core utilities, patch and diff utilities, and so on: basic tools needed to compile a huge pile of software currently present in nixpkgs. - -
- What is stdenv? - - - First of all, stdenv is a derivation, and it's a very simple one: - - - - - It has just two files: /setup and /nix-support/propagated-user-env-packages. Don't worry about the latter. It's empty, in fact. The important file is /setup. - - - - How can this simple derivation pull in all of the toolchain and basic tools needed to compile packages? Let's look at the runtime dependencies: - - - - - How can it be? The package must be referring to those other packages somehow. In fact, they are hardcoded in the /setup file: - - - -
-
- The setup file - - - Remember our generic builder.sh in Pill 8? It sets up a basic PATH, unpacks the source and runs the usual autotools commands for us. - - - - The stdenv setup file is exactly that. It sets up several environment variables like PATH and creates some helper bash functions to build a package. I invite you to read it. - - - - The hardcoded toolchain and utilities are used to initially fill up the environment variables so that it's more pleasant to run common commands, similar to what we did with our builder with baseInputs and buildInputs. - - - - The build with stdenv works in phases. Phases are like unpackPhase, configurePhase, buildPhase, checkPhase, installPhase, fixupPhase. You can see the default list in the genericBuild function. - - - - What genericBuild does is just run these phases. Default phases are just bash functions. You can easily read them. - - - - Every phase has hooks to run commands before and after the phase has been executed. - Phases can be overwritten, reordered, whatever, it's just bash code. - - - - How to use this file? Like our old builder. To test it, we enter a fake empty derivation, source the stdenv setup, unpack the hello sources and build it: - - - - - - I unset PATH to further show that the stdenv is sufficiently self-contained to build autotools packages that have no other dependencies. - - - - So we ran the configurePhase function and buildPhase function and they worked. These bash functions should be self-explanatory. You can read the code in the setup file. - - -
-
- How the setup file is built - - - Until now we worked with plain bash scripts. What about the Nix side? The nixpkgs repository offers a useful function, like we did with our old builder. It is a wrapper around the raw derivation function which pulls in the stdenv for us, and runs genericBuild. It's stdenv.mkDerivation. - - - - Note how stdenv is a derivation but it's also an attribute set which contains some other attributes, like mkDerivation. Nothing fancy here, just convenience. - - - - Let's write a hello.nix expression using this newly discovered stdenv: - - - - - - Don't be scared by the with expression. It pulls the nixpkgs repository into scope, so we can directly use stdenv. It looks very similar to the hello expression in Pill 8. - - - - It builds, and runs fine: - - - - -
-
- The stdenv.mkDerivation builder - - - Let's take a look at the builder used by mkDerivation. You can read the code here in nixpkgs: - - - - - - Also take a look at our old derivation wrapper in previous pills! The builder is bash (that shell variable), the argument to the builder (bash) is default-builder.sh, and then we add the environment variable $stdenv in the derivation which is the stdenv derivation. - - - - You can open default-builder.sh and see what it does: - - - - - - It's what we did in Pill 10 to make the derivations nix-shell friendly. When entering the shell, the setup file only sets up the environment without building anything. When doing nix-build, it actually runs the build process. - - - - To get a clear understanding of the environment variables, look at the .drv of the hello derivation: - - - - - It's so short I decided to paste it entirely above. The builder is bash, with -e default-builder.sh arguments. Then you can see the src and stdenv environment variables. - - - - The last bit, the unpackPhase in the setup, is used to unpack the sources and enter the directory. Again, like we did in our old builder. - - -
- -
- Conclusion - - - The stdenv is the core of the nixpkgs repository. All packages use the stdenv.mkDerivation wrapper instead of the raw derivation. It does a bunch of operations for us and also sets up a pleasant build environment. - - - - The overall process is simple: - - nix-build - bash -e default-builder.sh - source $stdenv/setup - genericBuild - - - - - That's it. Everything you need to know about the stdenv phases is in the setup file. - - - - Really, take your time to read that file. Don't forget that juicy docs are also available in the nixpkgs manual. - - -
-
- Next pill... - - - ...we will talk about how to add dependencies to our packages with buildInputs and propagatedBuildInputs, and influence downstream builds with setup hooks and env hooks. - These concepts are crucial to how nixpkgs packages are composed. - - -
-
diff --git a/pills/19/default-builder.txt b/pills/19/default-builder.txt deleted file mode 100644 index 273fc55..0000000 --- a/pills/19/default-builder.txt +++ /dev/null @@ -1,2 +0,0 @@ -source $stdenv/setup -genericBuild diff --git a/pills/19/hello-derivation.xml b/pills/19/hello-derivation.xml deleted file mode 100644 index e0eddc0..0000000 --- a/pills/19/hello-derivation.xml +++ /dev/null @@ -1,50 +0,0 @@ -$ nix derivation show $(nix-instantiate hello.nix) -warning: you did not specify '--add-root'; the result might be removed by the garbage collector -{ - "/nix/store/abwj50lycl0m515yblnrvwyydlhhqvj2-hello.drv": { - "outputs": { - "out": { - "path": "/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello" - } - }, - "inputSrcs": [ - "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh", - "/nix/store/svc70mmzrlgq42m9acs0prsmci7ksh6h-hello-2.10.tar.gz" - ], - "inputDrvs": { - "/nix/store/hcgwbx42mcxr7ksnv0i1fg7kw6jvxshb-bash-4.4-p19.drv": [ - "out" - ], - "/nix/store/sfxh3ybqh97cgl4s59nrpi78kgcc8f3d-stdenv-linux.drv": [ - "out" - ] - }, - "platform": "x86_64-linux", - "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", - "args": [ - "-e", - "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh" - ], - "env": { - "buildInputs": "", - "builder": "/nix/store/q1g0rl8zfmz7r371fp5p42p4acmv297d-bash-4.4-p19/bin/bash", - "configureFlags": "", - "depsBuildBuild": "", - "depsBuildBuildPropagated": "", - "depsBuildTarget": "", - "depsBuildTargetPropagated": "", - "depsHostBuild": "", - "depsHostBuildPropagated": "", - "depsTargetTarget": "", - "depsTargetTargetPropagated": "", - "name": "hello", - "nativeBuildInputs": "", - "out": "/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello", - "propagatedBuildInputs": "", - "propagatedNativeBuildInputs": "", - "src": "/nix/store/svc70mmzrlgq42m9acs0prsmci7ksh6h-hello-2.10.tar.gz", - "stdenv": "/nix/store/6kz2vbh98s2r1pfshidkzhiy2s2qdw0a-stdenv-linux", - "system": "x86_64-linux" - } - } -} diff --git a/pills/19/stdenv-derivation.txt b/pills/19/stdenv-derivation.txt deleted file mode 100644 index 83fa0f4..0000000 --- a/pills/19/stdenv-derivation.txt +++ /dev/null @@ -1,8 +0,0 @@ -$ nix-build '' -A stdenv -/nix/store/k4jklkcag4zq4xkqhkpy156mgfm34ipn-stdenv -$ ls -R result/ -result/: -nix-support/ setup - -result/nix-support: -propagated-user-env-packages diff --git a/pills/19/stdenv-hello-build.txt b/pills/19/stdenv-hello-build.txt deleted file mode 100644 index 722f999..0000000 --- a/pills/19/stdenv-hello-build.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ nix-build hello.nix -... -/nix/store/6y0mzdarm5qxfafvn2zm9nr01d1j0a72-hello -$ result/bin/hello -Hello, world! diff --git a/pills/19/stdenv-hello.txt b/pills/19/stdenv-hello.txt deleted file mode 100644 index 9557c00..0000000 --- a/pills/19/stdenv-hello.txt +++ /dev/null @@ -1,5 +0,0 @@ -with import { }; -stdenv.mkDerivation { - name = "hello"; - src = ./hello-2.10.tar.gz; -} diff --git a/pills/19/stdenv-mkderivation.txt b/pills/19/stdenv-mkderivation.txt deleted file mode 100644 index 5722254..0000000 --- a/pills/19/stdenv-mkderivation.txt +++ /dev/null @@ -1,11 +0,0 @@ -{ - # ... - builder = attrs.realBuilder or shell; - args = - attrs.args or [ - "-e" - (attrs.builder or ./default-builder.sh) - ]; - stdenv = result; - # ... -} diff --git a/pills/19/stdenv-references.txt b/pills/19/stdenv-references.txt deleted file mode 100644 index 3f99154..0000000 --- a/pills/19/stdenv-references.txt +++ /dev/null @@ -1,6 +0,0 @@ -$ nix-store -q --references result -/nix/store/3a45nb37s0ndljp68228snsqr3qsyp96-bzip2-1.0.6 -/nix/store/a457ywa1haa0sgr9g7a1pgldrg3s798d-coreutils-8.24 -/nix/store/zmd4jk4db5lgxb8l93mhkvr3x92g2sx2-bash-4.3-p39 -/nix/store/47sfpm2qclpqvrzijizimk4md1739b1b-gcc-wrapper-4.9.3 -... diff --git a/pills/19/stdenv-setup-fake-builder.txt b/pills/19/stdenv-setup-fake-builder.txt deleted file mode 100644 index febb32c..0000000 --- a/pills/19/stdenv-setup-fake-builder.txt +++ /dev/null @@ -1,9 +0,0 @@ -$ nix-shell -E 'derivation { name = "fake"; builder = "fake"; system = "x86_64-linux"; }' -nix-shell$ unset PATH -nix-shell$ source /nix/store/k4jklkcag4zq4xkqhkpy156mgfm34ipn-stdenv/setup -nix-shell$ tar -xf hello-2.10.tar.gz -nix-shell$ cd hello-2.10 -nix-shell$ configurePhase -... -nix-shell$ buildPhase -... diff --git a/pills/19/stdenv-setup-head.txt b/pills/19/stdenv-setup-head.txt deleted file mode 100644 index 5b294c7..0000000 --- a/pills/19/stdenv-setup-head.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ head result/setup -export SHELL=/nix/store/zmd4jk4db5lgxb8l93mhkvr3x92g2sx2-bash-4.3-p39/bin/bash -initialPath="/nix/store/a457ywa1haa0sgr9g7a1pgldrg3s798d-coreutils-8.24 ..." -defaultNativeBuildInputs="/nix/store/sgwq15xg00xnm435gjicspm048rqg9y6-patchelf-0.8 ..." - diff --git a/pills/20-basic-dependencies-and-hooks.md b/pills/20-basic-dependencies-and-hooks.md new file mode 100644 index 0000000..b9600ad --- /dev/null +++ b/pills/20-basic-dependencies-and-hooks.md @@ -0,0 +1,279 @@ +# Basic Dependencies and Hooks + +Welcome to the 20th Nix pill. In the previous [19th](19-fundamentals-of-stdenv.md) pill we introduced Nixpkgs' stdenv, including `setup.sh` script, `default-builder.sh` helper script, and `stdenv.mkDerivation` builder. We focused on how stdenv is put together, and how it's used, and a bit about the phases of `genericBuild`. + +This time, we'll focus on the interaction of packages built with `stdenv.mkDerivation`. Packages need to depend on each other, of course. For this we have `buildInputs` and `propagatedBuildInputs` attributes. We've also found that dependencies sometimes need to influence their dependents in ways the dependents can't or shouldn't predict. For this we have setup hooks and env hooks. Together, these 4 concepts support almost all build-time package interactions. + +
+ +Note: The complexity of the dependencies and hooks infrastructure has increased, over time, to support cross compilation. Once you learn the core concepts, you will be able to understand the extra complexity. As a starting point, you might want to refer to nixpkgs commit [6675f0a5](https://github.com/nixos/nixpkgs/tree/6675f0a52c0962042a1000c7f20e887d0d26ae25), the last version of stdenv without cross-compilation complexity. + +
+ +## The `buildInputs` Attribute + +For the simplest dependencies where the current package directly needs another, we use the `buildInputs` attribute. This is exactly the pattern used in our builder in [Pill 8](08-generic-builders.html). To demo this, let's build GNU Hello, and then another package which provides a shell script that `exec`s it. + +```nix +let + + nixpkgs = import { }; + + inherit (nixpkgs) stdenv fetchurl which; + + actualHello = stdenv.mkDerivation { + name = "hello-2.3"; + + src = fetchurl { + url = "mirror://gnu/hello/hello-2.3.tar.bz2"; + sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; + }; + }; + + wrappedHello = stdenv.mkDerivation { + name = "hello-wrapper"; + + buildInputs = [ + actualHello + which + ]; + + unpackPhase = "true"; + + installPhase = '' + mkdir -p "$out/bin" + echo "#! ${stdenv.shell}" >> "$out/bin/hello" + echo "exec $(which hello)" >> "$out/bin/hello" + chmod 0755 "$out/bin/hello" + ''; + }; +in +wrappedHello +``` + +Notice that the wrappedHello derivation finds the `hello` binary from the `PATH`. This works because stdenv contains something like: + +```sh +pkgs="" +for i in $buildInputs; do + findInputs $i +done +``` + +where `findInputs` is defined like: + +```sh +findInputs() { + local pkg=$1 + + ## Don't need to repeat already processed package + case $pkgs in + *\ $pkg\ *) + return 0 + ;; + esac + + pkgs="$pkgs $pkg " + + ## More goes here in reality that we can ignore for now. +} +``` + +then after this is run: + +```sh +for i in $pkgs; do + addToEnv $i +done +``` + +where `addToEnv` is defined like: + +```sh +addToEnv() { + local pkg=$1 + + if test -d $1/bin; then + addToSearchPath _PATH $1/bin + fi + + ## More goes here in reality that we can ignore for now. +} +``` + +The `addToSearchPath` call adds `$1/bin` to `_PATH` if the former exists (code [here](https://github.com/NixOS/nixpkgs/blob/6675f0a52c0962042a1000c7f20e887d0d26ae25/pkgs/stdenv/generic/setup.sh#L60-L73)). Once all the packages in `buildInputs` have been processed, then content of `_PATH` is added to `PATH`, as follows: + +```sh +PATH="${_PATH-}${_PATH:+${PATH:+:}}$PATH" +``` + +With the real `hello` on the `PATH`, the `installPhase` should hopefully make sense. + +## The `propagatedBuildInputs` Attribute + +The `buildInputs` covers direct dependencies, but what about indirect dependencies where one package needs a second package which needs a third? Nix itself handles this just fine, understanding various dependency closures as covered in previous builds. But what about the conveniences that `buildInputs` provides, namely accumulating in `pkgs` environment variable and inclusion of `«pkg»/bin` directories on the `PATH`? For this, stdenv provides the `propagatedBuildInputs`: + +```nix +let + + nixpkgs = import { }; + + inherit (nixpkgs) stdenv fetchurl which; + + actualHello = stdenv.mkDerivation { + name = "hello-2.3"; + + src = fetchurl { + url = "mirror://gnu/hello/hello-2.3.tar.bz2"; + sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; + }; + }; + + intermediary = stdenv.mkDerivation { + name = "middle-man"; + + propagatedBuildInputs = [ actualHello ]; + + unpackPhase = "true"; + + installPhase = '' + mkdir -p "$out" + ''; + }; + + wrappedHello = stdenv.mkDerivation { + name = "hello-wrapper"; + + buildInputs = [ + intermediary + which + ]; + + unpackPhase = "true"; + + installPhase = '' + mkdir -p "$out/bin" + echo "#! ${stdenv.shell}" >> "$out/bin/hello" + echo "exec $(which hello)" >> "$out/bin/hello" + chmod 0755 "$out/bin/hello" + ''; + }; +in +wrappedHello +``` + +See how the intermediate package has a `propagatedBuildInputs` dependency, but the wrapper only needs a `buildInputs` dependency on the intermediary. + +How does this work? You might think we do something in Nix, but actually it's done not at eval time but at build time in bash. let's look at part of the `fixupPhase` of stdenv: + +```sh +fixupPhase() { + + ## Elided + + if test -n "$propagatedBuildInputs"; then + mkdir -p "$out/nix-support" + echo "$propagatedBuildInputs" > "$out/nix-support/propagated-build-inputs" + fi + + ## Elided + +} +``` + +This dumps the propagated build inputs in a so-named file in `$out/nix-support/`. Then, back in `findInputs` look at the lines at the bottom we elided before: + +```sh +findInputs() { + local pkg=$1 + + ## More goes here in reality that we can ignore for now. + + if test -f $pkg/nix-support/propagated-build-inputs; then + for i in $(cat $pkg/nix-support/propagated-build-inputs); do + findInputs $i + done + fi +} +``` + +See how `findInputs` is actually recursive, looking at the propagated build inputs of each dependency, and those dependencies' propagated build inputs, etc. + +We actually simplified the `findInputs` call site from before; `propagatedBuildInputs` is also looped over in reality: + +```sh +pkgs="" +for i in $buildInputs $propagatedBuildInputs; do + findInputs $i +done +``` + +This demonstrates an important point. For the _current_ package alone, it doesn't matter whether a dependency is propagated or not. It will be processed the same way: called with `findInputs` and `addToEnv`. (The packages discovered by `findInputs`, which are also accumulated in `pkgs` and passed to `addToEnv`, are also the same in both cases.) Downstream however, it certainly does matter because only the propagated immediate dependencies are put in the `$out/nix-support/propagated-build-inputs`. + +## Setup Hooks + +As we mentioned above, sometimes dependencies need to influence the packages that use them in ways other than just _being_ a dependency. [^1] `propagatedBuildInputs` can actually be seen as an example of this: packages using that are effectively "injecting" those dependencies as extra `buildInputs` in their downstream dependents. But in general, a dependency might affect the packages it depends on in arbitrary ways. _Arbitrary_ is the key word here. We could teach `setup.sh` things about upstream packages like `«pkg»/nix-support/propagated-build-inputs`, but not arbitrary interactions. + +Setup hooks are the basic building block we have for this. In nixpkgs, a "hook" is basically a bash callback, and a setup hook is no exception. Let's look at the last part of `findInputs` we haven't covered: + +```sh +findInputs() { + local pkg=$1 + + ## More goes here in reality that we can ignore for now. + + if test -f $pkg/nix-support/setup-hook; then + source $pkg/nix-support/setup-hook + fi + + ## More goes here in reality that we can ignore for now. + +} +``` + +If a package includes the path `«pkg»/nix-support/setup-hook`, it will be sourced by any stdenv-based build including that as a dependency. + +This is strictly more general than any of the other mechanisms introduced in this chapter. For example, try writing a setup hook that has the same effect as a _propagatedBuildInputs_ entry. One can almost think of this as an escape hatch around Nix's normal isolation guarantees, and the principle that dependencies are immutable and inert. We're not actually doing something unsafe or modifying dependencies, but we are allowing arbitrary ad-hoc behavior. For this reason, setup-hooks should only be used as a last resort. + +## Environment Hooks + +As a final convenience, we have environment hooks. Recall in [Pill 12](12-inputs-design-pattern.md) how we created `NIX_CFLAGS_COMPILE` for `-I` flags and `NIX_LDFLAGS` for `-L` flags, in a similar manner to how we prepared the `PATH`. One point of ugliness was how anti-modular this was. It makes sense to build the `PATH` in a generic builder, because the `PATH` is used by the shell, and the generic builder is intrinsically tied to the shell. But `-I` and `-L` flags are only relevant to the C compiler. The stdenv isn't wedded to including a C compiler (though it does by default), and there are other compilers too which may take completely different flags. + +As a first step, we can move that logic to a setup hook on the C compiler; indeed that's just what we do in CC Wrapper. [^2] But this pattern comes up fairly often, so somebody decided to add some helper support to reduce boilerplate. + +The other half of `addToEnv` is: + +```sh +addToEnv() { + local pkg=$1 + + ## More goes here in reality that we can ignore for now. + + # Run the package-specific hooks set by the setup-hook scripts. + for i in "${envHooks[@]}"; do + $i $pkg + done +} +``` + +Functions listed in `envHooks` are applied to every package passed to `addToEnv`. One can write a setup hook like: + +```sh +anEnvHook() { + local pkg=$1 + + echo "I'm depending on \"$pkg\"" +} + +envHooks+=(anEnvHook) +``` + +and if one dependency has that setup hook then all of them will be so `echo`ed. Allowing dependencies to learn about their _sibling_ dependencies is exactly what compilers need. + +## Next pill... + +...I'm not sure! We could talk about the additional dependency types and hooks which cross compilation necessitates, building on our knowledge here to cover stdenv as it works today. We could talk about how nixpkgs is bootstrapped. Or we could talk about how `localSystem` and `crossSystem` are elaborated into the `buildPlatform`, `hostPlatform`, and `targetPlatform` each bootstrapping stage receives. Let us know which most interests you! + +[^1]: We can now be precise and consider what `addToEnv` does alone the minimal treatment of a dependency: i.e. a package that is _just_ a dependency would _only_ have `addToEnv` applied to it. + +[^2]: It was called [GCC Wrapper](https://github.com/NixOS/nixpkgs/tree/6675f0a52c0962042a1000c7f20e887d0d26ae25/pkgs/build-support/gcc-wrapper) in the version of nixpkgs suggested for following along in this pill; Darwin and Clang support hadn't yet motivated the rename. diff --git a/pills/20-basic-dependencies-and-hooks.xml b/pills/20-basic-dependencies-and-hooks.xml deleted file mode 100644 index 0c29729..0000000 --- a/pills/20-basic-dependencies-and-hooks.xml +++ /dev/null @@ -1,180 +0,0 @@ - - - Basic Dependencies and Hooks - - - Welcome to the 20th Nix pill. - In the previous 19th pill we introduced Nixpkgs' stdenv, including setup.sh script, default-builder.sh helper script, and stdenv.mkDerivation builder. - We focused on how stdenv is put together, and how it's used, and a bit about the phases of genericBuild. - - - - This time, we'll focus on the interaction of packages built with stdenv.mkDerivation. - Packages need to depend on each other, of course. - For this we have buildInputs and propagatedBuildInputs attributes. - We've also found that dependencies sometimes need to influence their dependents in ways the dependents can't or shouldn't predict. - For this we have setup hooks and env hooks. - Together, these 4 concepts support almost all build-time package interactions. - - - - The complexity of the dependencies and hooks infrastructure has increased, over time, to support cross compilation. - Once you learn the core concepts, you will be able to understand the extra complexity. - As a starting point, you might want to refer to nixpkgs commit 6675f0a5, the last version of stdenv without cross-compilation complexity. - - -
- The <varname>buildInputs</varname> Attribute - - - For the simplest dependencies where the current package directly needs another, we use the buildInputs attribute. - This is exactly the pattern used in our builder in Pill 8. - To demo this, let's build GNU Hello, and then another package which provides a shell script that execs it. - - - - - Notice that the wrappedHello derivation finds the hello binary from the PATH. - This works because stdenv contains something like: - - where findInputs is defined like: - - then after this is run: - - where addToEnv is defined like: - - - The addToSearchPath call adds $1/bin to _PATH if the former exists (code here). - Once all the packages in buildInputs have been processed, then content of _PATH is added to PATH, as follows: - - - With the real hello on the PATH, the installPhase should hopefully make sense. - -
- -
- The <varname>propagatedBuildInputs</varname> Attribute - - - The buildInputs covers direct dependencies, but what about indirect dependencies where one package needs a second package which needs a third? - Nix itself handles this just fine, understanding various dependency closures as covered in previous builds. - But what about the conveniences that buildInputs provides, namely accumulating in pkgs environment variable and inclusion of pkg/bin directories on the PATH? - For this, stdenv provides the propagatedBuildInputs: - - See how the intermediate package has a propagatedBuildInputs dependency, but the wrapper only needs a buildInputs dependency on the intermediary. - - - - How does this work? - You might think we do something in Nix, but actually it's done not at eval time but at build time in bash. - let's look at part of the fixupPhase of stdenv: - - - - This dumps the propagated build inputs in a so-named file in $out/nix-support/. - Then, back in findInputs look at the lines at the bottom we elided before: - - - - See how findInputs is actually recursive, looking at the propagated build inputs of each dependency, and those dependencies' propagated build inputs, etc. - - - - We actually simplified the findInputs call site from before; propagatedBuildInputs is also looped over in reality: - - This demonstrates an important point. For the current package alone, it doesn't matter whether a dependency is propagated or not. - It will be processed the same way: called with findInputs and addToEnv. - (The packages discovered by findInputs, which are also accumulated in pkgs and passed to addToEnv, are also the same in both cases.) - Downstream however, it certainly does matter because only the propagated immediate dependencies are put in the $out/nix-support/propagated-build-inputs. - -
- -
- Setup Hooks - - - As we mentioned above, sometimes dependencies need to influence the packages that use them in ways other than just being a dependency. - - - - We can now be precise and consider what addToEnv does alone the minimal treatment of a dependency: - i.e. a package that is just a dependency would only have addToEnv applied to it. - - - - propagatedBuildInputs can actually be seen as an example of this: - packages using that are effectively "injecting" those dependencies as extra buildInputs in their downstream dependents. - - But in general, a dependency might affect the packages it depends on in arbitrary ways. - Arbitrary is the key word here. - We could teach setup.sh things about upstream packages like pkg/nix-support/propagated-build-inputs, but not arbitrary interactions. - - - - Setup hooks are the basic building block we have for this. - In nixpkgs, a "hook" is basically a bash callback, and a setup hook is no exception. - Let's look at the last part of findInputs we haven't covered: - - If a package includes the path pkg/nix-support/setup-hook, it will be sourced by any stdenv-based build including that as a dependency. - - - - This is strictly more general than any of the other mechanisms introduced in this chapter. - For example, try writing a setup hook that has the same effect as a propagatedBuildInputs entry. - One can almost think of this as an escape hatch around Nix's normal isolation guarantees, and the principle that dependencies are immutable and inert. - We're not actually doing something unsafe or modifying dependencies, but we are allowing arbitrary ad-hoc behavior. - For this reason, setup-hooks should only be used as a last resort. - -
- -
- Environment Hooks - - - As a final convenience, we have environment hooks. - Recall in Pill 12 how we created NIX_CFLAGS_COMPILE for -I flags and NIX_LDFLAGS for -L flags, in a similar manner to how we prepared the PATH. - One point of ugliness was how anti-modular this was. - It makes sense to build the PATH in a generic builder, because the PATH is used by the shell, and the generic builder is intrinsically tied to the shell. - But -I and -L flags are only relevant to the C compiler. - The stdenv isn't wedded to including a C compiler (though it does by default), and there are other compilers too which may take completely different flags. - - - - As a first step, we can move that logic to a setup hook on the C compiler; - indeed that's just what we do in CC Wrapper. - - - It was called GCC Wrapper in the version of nixpkgs suggested for following along in this pill; Darwin and Clang support hadn't yet motivated the rename. - - - But this pattern comes up fairly often, so somebody decided to add some helper support to reduce boilerplate. - - - - The other half of addToEnv is: - - Functions listed in envHooks are applied to every package passed to addToEnv. - One can write a setup hook like: - - and if one dependency has that setup hook then all of them will be so echoed. - Allowing dependencies to learn about their sibling dependencies is exactly what compilers need. - - -
- -
- Next pill... - - ...I'm not sure! - We could talk about the additional dependency types and hooks which cross compilation necessitates, building on our knowledge here to cover stdenv as it works today. - We could talk about how nixpkgs is bootstrapped. - Or we could talk about how localSystem and crossSystem are elaborated into the buildPlatform, hostPlatform, and targetPlatform each bootstrapping stage receives. - Let us know which most interests you! - -
- -
diff --git a/pills/20/build-inputs-0.bash b/pills/20/build-inputs-0.bash deleted file mode 100644 index 1188faf..0000000 --- a/pills/20/build-inputs-0.bash +++ /dev/null @@ -1,4 +0,0 @@ -pkgs="" -for i in $buildInputs; do - findInputs $i -done diff --git a/pills/20/build-inputs-1.bash b/pills/20/build-inputs-1.bash deleted file mode 100644 index 1b09b42..0000000 --- a/pills/20/build-inputs-1.bash +++ /dev/null @@ -1,14 +0,0 @@ -findInputs() { - local pkg=$1 - - ## Don't need to repeat already processed package - case $pkgs in - *\ $pkg\ *) - return 0 - ;; - esac - - pkgs="$pkgs $pkg " - - ## More goes here in reality that we can ignore for now. -} diff --git a/pills/20/build-inputs-2.bash b/pills/20/build-inputs-2.bash deleted file mode 100644 index d3db58c..0000000 --- a/pills/20/build-inputs-2.bash +++ /dev/null @@ -1,3 +0,0 @@ -for i in $pkgs; do - addToEnv $i -done diff --git a/pills/20/build-inputs-3.bash b/pills/20/build-inputs-3.bash deleted file mode 100644 index 80e3207..0000000 --- a/pills/20/build-inputs-3.bash +++ /dev/null @@ -1,9 +0,0 @@ -addToEnv() { - local pkg=$1 - - if test -d $1/bin; then - addToSearchPath _PATH $1/bin - fi - - ## More goes here in reality that we can ignore for now. -} diff --git a/pills/20/build-inputs-4.bash b/pills/20/build-inputs-4.bash deleted file mode 100644 index ff883ce..0000000 --- a/pills/20/build-inputs-4.bash +++ /dev/null @@ -1 +0,0 @@ -PATH="${_PATH-}${_PATH:+${PATH:+:}}$PATH" diff --git a/pills/20/env-hooks-0.bash b/pills/20/env-hooks-0.bash deleted file mode 100644 index 68c0de6..0000000 --- a/pills/20/env-hooks-0.bash +++ /dev/null @@ -1,10 +0,0 @@ -addToEnv() { - local pkg=$1 - - ## More goes here in reality that we can ignore for now. - - # Run the package-specific hooks set by the setup-hook scripts. - for i in "${envHooks[@]}"; do - $i $pkg - done -} diff --git a/pills/20/env-hooks-1.bash b/pills/20/env-hooks-1.bash deleted file mode 100644 index f64daf9..0000000 --- a/pills/20/env-hooks-1.bash +++ /dev/null @@ -1,7 +0,0 @@ -anEnvHook() { - local pkg=$1 - - echo "I'm depending on \"$pkg\"" -} - -envHooks+=(anEnvHook) diff --git a/pills/20/propagated-build-inputs-0.bash b/pills/20/propagated-build-inputs-0.bash deleted file mode 100644 index b2ca8af..0000000 --- a/pills/20/propagated-build-inputs-0.bash +++ /dev/null @@ -1,12 +0,0 @@ -fixupPhase() { - - ## Elided - - if test -n "$propagatedBuildInputs"; then - mkdir -p "$out/nix-support" - echo "$propagatedBuildInputs" > "$out/nix-support/propagated-build-inputs" - fi - - ## Elided - -} diff --git a/pills/20/propagated-build-inputs-1.bash b/pills/20/propagated-build-inputs-1.bash deleted file mode 100644 index 5c4b217..0000000 --- a/pills/20/propagated-build-inputs-1.bash +++ /dev/null @@ -1,11 +0,0 @@ -findInputs() { - local pkg=$1 - - ## More goes here in reality that we can ignore for now. - - if test -f $pkg/nix-support/propagated-build-inputs; then - for i in $(cat $pkg/nix-support/propagated-build-inputs); do - findInputs $i - done - fi -} diff --git a/pills/20/propagated-build-inputs-2.bash b/pills/20/propagated-build-inputs-2.bash deleted file mode 100644 index a5e5e76..0000000 --- a/pills/20/propagated-build-inputs-2.bash +++ /dev/null @@ -1,4 +0,0 @@ -pkgs="" -for i in $buildInputs $propagatedBuildInputs; do - findInputs $i -done diff --git a/pills/20/setup-hooks-0.bash b/pills/20/setup-hooks-0.bash deleted file mode 100644 index a0b5de9..0000000 --- a/pills/20/setup-hooks-0.bash +++ /dev/null @@ -1,12 +0,0 @@ -findInputs() { - local pkg=$1 - - ## More goes here in reality that we can ignore for now. - - if test -f $pkg/nix-support/setup-hook; then - source $pkg/nix-support/setup-hook - fi - - ## More goes here in reality that we can ignore for now. - -} diff --git a/pills/20/three-hellos.nix b/pills/20/three-hellos.nix deleted file mode 100644 index fe59c44..0000000 --- a/pills/20/three-hellos.nix +++ /dev/null @@ -1,46 +0,0 @@ -let - - nixpkgs = import { }; - - inherit (nixpkgs) stdenv fetchurl which; - - actualHello = stdenv.mkDerivation { - name = "hello-2.3"; - - src = fetchurl { - url = "mirror://gnu/hello/hello-2.3.tar.bz2"; - sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; - }; - }; - - intermediary = stdenv.mkDerivation { - name = "middle-man"; - - propagatedBuildInputs = [ actualHello ]; - - unpackPhase = "true"; - - installPhase = '' - mkdir -p "$out" - ''; - }; - - wrappedHello = stdenv.mkDerivation { - name = "hello-wrapper"; - - buildInputs = [ - intermediary - which - ]; - - unpackPhase = "true"; - - installPhase = '' - mkdir -p "$out/bin" - echo "#! ${stdenv.shell}" >> "$out/bin/hello" - echo "exec $(which hello)" >> "$out/bin/hello" - chmod 0755 "$out/bin/hello" - ''; - }; -in -wrappedHello diff --git a/pills/20/two-hellos.nix b/pills/20/two-hellos.nix deleted file mode 100644 index 4a49f3c..0000000 --- a/pills/20/two-hellos.nix +++ /dev/null @@ -1,35 +0,0 @@ -let - - nixpkgs = import { }; - - inherit (nixpkgs) stdenv fetchurl which; - - actualHello = stdenv.mkDerivation { - name = "hello-2.3"; - - src = fetchurl { - url = "mirror://gnu/hello/hello-2.3.tar.bz2"; - sha256 = "0c7vijq8y68bpr7g6dh1gny0bff8qq81vnp4ch8pjzvg56wb3js1"; - }; - }; - - wrappedHello = stdenv.mkDerivation { - name = "hello-wrapper"; - - buildInputs = [ - actualHello - which - ]; - - unpackPhase = "true"; - - installPhase = '' - mkdir -p "$out/bin" - echo "#! ${stdenv.shell}" >> "$out/bin/hello" - echo "exec $(which hello)" >> "$out/bin/hello" - chmod 0755 "$out/bin/hello" - ''; - }; -in -wrappedHello - diff --git a/pills/SUMMARY.md b/pills/SUMMARY.md new file mode 100644 index 0000000..b579454 --- /dev/null +++ b/pills/SUMMARY.md @@ -0,0 +1,24 @@ +# Summary + +[Preface](00-preface.md) + +- [Why You Should Give it a Try](01-why-you-should-give-it-a-try.md) +- [Install on Your Running System](02-install-on-your-running-system.md) +- [Enter the Environment](03-enter-environment.md) +- [The Basics of the Language](04-basics-of-language.md) +- [Functions and Imports](05-functions-and-imports.md) +- [Our First Derivation](06-our-first-derivation.md) +- [Working Derivation](07-working-derivation.md) +- [Generic Builders](08-generic-builders.md) +- [Automatic Runtime Dependencies](09-automatic-runtime-dependencies.md) +- [Developing with `nix-shell`](10-developing-with-nix-shell.md) +- [The Garbage Collector](11-garbage-collector.md) +- [Package Repositories and the Inputs Design Pattern](12-inputs-design-pattern.md) +- [Callpackage Design Pattern](13-callpackage-design-pattern.md) +- [Override Design Pattern](14-override-design-pattern.md) +- [Nix Search Paths](15-nix-search-paths.md) +- [Nixpkgs Parameters](16-nixpkgs-parameters.md) +- [Nixpkgs Overriding Packages](17-nixpkgs-overriding-packages.md) +- [Nix Store Paths](18-nix-store-paths.md) +- [Fundamentals of Stdenv](19-fundamentals-of-stdenv.md) +- [Basic Dependencies and Hooks](20-basic-dependencies-and-hooks.md) diff --git a/release.nix b/release.nix index 7e2bf10..c1f81fc 100644 --- a/release.nix +++ b/release.nix @@ -1,5 +1,5 @@ -{ nix-pills ? { outPath = ./.; revCount = 1234; shortRev = "abcdef"; } -, nixpkgs ? { outPath = ; revCount = 1234; shortRev = "abcdef"; } +{ nix-pills ? { outPath = ./.; } +, nixpkgs ? { outPath = ; } , officialRelease ? false }: @@ -8,8 +8,6 @@ let pills = import ./default.nix { inherit pkgs; - - inherit (nix-pills) revCount shortRev; }; in rec { inherit (pills) html-split epub; diff --git a/style.css b/style.css deleted file mode 100644 index e329eb4..0000000 --- a/style.css +++ /dev/null @@ -1,268 +0,0 @@ -/* Copied from http://bakefile.sourceforge.net/, which appears - licensed under the GNU GPL. */ - - -/*************************************************************************** - Basic headers and text: - ***************************************************************************/ - -body -{ - font-family: "Nimbus Sans L", sans-serif; - background: white; - margin: 2em 1em 2em 1em; -} - -h1, h2, h3, h4 -{ - color: #005aa0; -} - -h1 /* title */ -{ - font-size: 200%; -} - -h2 /* chapters, appendices, subtitle */ -{ - font-size: 180%; -} - -/* Extra space between chapters, appendices. */ -div.chapter > div.titlepage h2, div.appendix > div.titlepage h2 -{ - margin-top: 1.5em; -} - -div.section > div.titlepage h2 /* sections */ -{ - font-size: 150%; - margin-top: 1.5em; -} - -h3 /* subsections */ -{ - font-size: 125%; -} - -div.simplesect h2 -{ - font-size: 110%; -} - -div.appendix h3 -{ - font-size: 150%; - margin-top: 1.5em; -} - -div.refnamediv h2, div.refsynopsisdiv h2, div.refsection h2 /* refentry parts */ -{ - margin-top: 1.4em; - font-size: 125%; -} - -div.refsection h3 -{ - font-size: 110%; -} - - -/*************************************************************************** - Examples: - ***************************************************************************/ - -div.example -{ - border: 1px solid #b0b0b0; - padding: 6px 6px; - margin-left: 1.5em; - margin-right: 1.5em; - background: #f4f4f8; - border-radius: 0.4em; - box-shadow: 0.4em 0.4em 0.5em #e0e0e0; -} - -div.example p.title -{ - margin-top: 0em; -} - -div.example pre -{ - box-shadow: none; -} - - -/*************************************************************************** - Screen dumps: - ***************************************************************************/ - -pre.screen, pre.programlisting -{ - border: 1px solid #b0b0b0; - padding: 3px 3px; - margin-left: 1.5em; - margin-right: 1.5em; - color: #600000; - background: #f4f4f8; - font-family: monospace; - border-radius: 0.4em; - box-shadow: 0.4em 0.4em 0.5em #e0e0e0; -} - - -div.example pre.programlisting -{ - border: 0px; - padding: 0 0; - margin: 0 0 0 0; -} - - -/*************************************************************************** - Notes, warnings etc: - ***************************************************************************/ - -.note, .warning -{ - border: 1px solid #b0b0b0; - padding: 3px 3px; - margin-left: 1.5em; - margin-right: 1.5em; - margin-bottom: 1em; - padding: 0.3em 0.3em 0.3em 0.3em; - background: #fffff5; - border-radius: 0.4em; - box-shadow: 0.4em 0.4em 0.5em #e0e0e0; -} - -div.note, div.warning -{ - font-style: italic; -} - -div.note h3, div.warning h3 -{ - color: red; - font-size: 100%; - padding-right: 0.5em; - display: inline; -} - -div.note p, div.warning p -{ - margin-bottom: 0em; -} - -div.note h3 + p, div.warning h3 + p -{ - display: inline; -} - -div.note h3 -{ - color: blue; - font-size: 100%; -} - -div.navfooter * -{ - font-size: 90%; -} - - -/*************************************************************************** - Links colors and highlighting: - ***************************************************************************/ - -a { text-decoration: none; } -a:hover { text-decoration: underline; } -a:link { color: #0048b3; } -a:visited { color: #002a6a; } - - -/*************************************************************************** - Table of contents: - ***************************************************************************/ - -div.toc -{ - font-size: 90%; -} - -div.toc dl -{ - margin-top: 0em; - margin-bottom: 0em; -} - - -/*************************************************************************** - Special elements: - ***************************************************************************/ - -tt, code -{ - color: #400000; -} - -.term -{ - font-weight: bold; - -} - -div.variablelist dd p, div.glosslist dd p -{ - margin-top: 0em; -} - -div.variablelist dd, div.glosslist dd -{ - margin-left: 1.5em; -} - -div.glosslist dt -{ - font-style: italic; -} - -.varname -{ - color: #400000; -} - -span.command strong -{ - font-weight: normal; - color: #400000; -} - -div.calloutlist table -{ - box-shadow: none; -} - -table -{ - border-collapse: collapse; - box-shadow: 0.4em 0.4em 0.5em #e0e0e0; -} - -table.simplelist -{ - text-align: left; - color: #005aa0; - border: 0; - padding: 5px; - background: #fffff5; - font-weight: normal; - font-style: italic; - box-shadow: none; - margin-bottom: 1em; -} - -div.navheader table, div.navfooter table { - box-shadow: none; -} diff --git a/theme/highlight.js b/theme/highlight.js new file mode 100644 index 0000000..57b5fb4 --- /dev/null +++ b/theme/highlight.js @@ -0,0 +1,384 @@ +/*! + Highlight.js v11.9.0 (git: b7ec4bfafc) + (c) 2006-2024 undefined and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";function e(t){ +return t instanceof Map?t.clear=t.delete=t.set=()=>{ +throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{ +const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i) +})),t}class t{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function n(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope +;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const r=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class a{constructor(){ +this.rootNode=r(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=r({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,t){const n=e.root +;t&&(n.scope="language:"+t),this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function l(e){ +return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")} +function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")} +function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break} +s+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0], +"("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)} +const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_="\\b(0b[01]+)",O={ +begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t, +contains:[]},n);s.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return s.contains.push({begin:h(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s +},S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({ +__proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{ +scope:"number",begin:_,relevance:0},BINARY_NUMBER_RE:_,COMMENT:N, +C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number", +begin:y,relevance:0},C_NUMBER_RE:y,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0}, +NUMBER_MODE:{scope:"number",begin:w,relevance:0},NUMBER_RE:w, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function L(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function P(e,t){ +void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},H=["of","and","for","in","not","or","if","then","parent","list","value"],C="keyword" +;function $(e,t,n=C){const i=Object.create(null) +;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function s(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const z={},W=e=>{ +console.error(e)},X=(e,...t)=>{console.log("WARN: "+e,...t)},G=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},K=Error();function F(e,t,{key:n}){let i=0;const s=e[n],o={},r={} +;for(let e=1;e<=t.length;e++)r[e+i]=s[e],o[e+i]=!0,i+=p(t[e-1]) +;e[n]=r,e[n]._emit=o,e[n]._multi=!0}function Z(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw W("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +K +;if("object"!=typeof e.beginScope||null===e.beginScope)throw W("beginScope must be object"), +K;F(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw W("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +K +;if("object"!=typeof e.endScope||null===e.endScope)throw W("endScope must be object"), +K;F(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=i(e.classNameAliases||{}),function n(o,r){const a=o +;if(o.isCompiled)return a +;[I,B,Z,D].forEach((e=>e(o,r))),e.compilerExtensions.forEach((e=>e(o,r))), +o.__beforeBegin=null,[T,L,P].forEach((e=>e(o,r))),o.isCompiled=!0;let c=null +;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords), +c=o.keywords.$pattern, +delete o.keywords.$pattern),c=c||/\w+/,o.keywords&&(o.keywords=$(o.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +r&&(o.begin||(o.begin=/\B|\b/),a.beginRe=t(a.begin),o.end||o.endsWithParent||(o.end=/\B|\b/), +o.end&&(a.endRe=t(a.end)), +a.terminatorEnd=l(a.end)||"",o.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+r.terminatorEnd)), +o.illegal&&(a.illegalRe=t(o.illegal)), +o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?i(e,{ +starts:e.starts?i(e.starts):null +}):Object.isFrozen(e)?i(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,a) +})),o.starts&&n(o.starts,r),a.matcher=(e=>{const t=new s +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=n,Q=i,ee=Symbol("nomatch"),te=n=>{ +const i=Object.create(null),s=Object.create(null),o=[];let r=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",l={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:c};function b(e){ +return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,s=t.language):(G("10.7.0","highlight(lang, code, ...args) has been deprecated."), +G("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +s=e,i=t),void 0===n&&(n=!0);const o={code:i,language:s};N("before:highlight",o) +;const r=o.result?o.result:E(o.language,o.code,n) +;return r.code=o.code,N("after:highlight",r),r}function E(e,n,s,o){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n="" +;for(;t;){n+=R.substring(e,t.index) +;const s=_.case_insensitive?t[0].toLowerCase():t[0],o=(i=s,N.keywords[i]);if(o){ +const[e,i]=o +;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{ +const n=_.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i +;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{ +if(""===R)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(R) +;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top +}else e=x(R,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language) +})():l(),R=""}function u(e,t){ +""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1 +;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue} +const i=_.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}} +function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(_.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(u(R,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{ +value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e) +;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return f(e.parent,n,i)}function b(e){ +return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){ +const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return ee;const o=N +;N.endScope&&N.endScope._wrap?(g(), +u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(), +d(N.endScope,e)):o.skip?R+=t:(o.returnEnd||o.excludeEnd||(R+=t), +g(),o.excludeEnd&&(R=t));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent +}while(N!==s.parent);return s.starts&&h(s.starts,e),o.returnEnd?0:t.length} +let w={};function y(i,o){const a=o&&o[0];if(R+=i,null==a)return g(),0 +;if("begin"===w.type&&"end"===o.type&&w.index===o.index&&""===a){ +if(R+=n.slice(o.index,o.index+1),!r){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=w.rule,t}return 1} +if(w=o,"begin"===o.type)return(e=>{ +const n=e[0],i=e.rule,s=new t(i),o=[i.__beforeBegin,i["on:begin"]] +;for(const t of o)if(t&&(t(e,s),s.isMatchIgnored))return b(n) +;return i.skip?R+=n:(i.excludeBegin&&(R+=n), +g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(o) +;if("illegal"===o.type&&!s){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===o.type){const e=m(o);if(e!==ee)return e} +if("illegal"===o.type&&""===a)return 1 +;if(I>1e5&&I>3*o.index)throw Error("potential infinite loop, way more iterations than matches") +;return R+=a,a.length}const _=O(e) +;if(!_)throw W(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const v=V(_);let k="",N=o||v;const S={},M=new p.__emitter(p);(()=>{const e=[] +;for(let t=N;t!==_;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{ +if(_.__emitTokens)_.__emitTokens(n,M);else{for(N.matcher.considerAll();;){ +I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A +;const e=N.matcher.exec(n);if(!e)break;const t=y(n.substring(A,e.index),e) +;A=e.index+t}y(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e, +value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){ +if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), +illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A, +context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(r)return{ +language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N} +;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)} +;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1))) +;s.unshift(n);const o=s.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[r,a]=o,c=r +;return c.secondBest=a,c}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||(X(a.replace("{}",n[1])), +X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(N("before:highlightElement",{el:e,language:n +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,o=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=o.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,o.language),e.result={language:o.language,re:o.relevance, +relevance:o.relevance},o.secondBest&&(e.secondBest={ +language:o.secondBest.language,relevance:o.secondBest.relevance +}),N("after:highlightElement",{el:e,result:o,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +s[e.toLowerCase()]=t}))}function k(e){const t=O(e) +;return t&&!t.disableAutodetect}function N(e,t){const n=e;o.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(G("10.7.0","highlightBlock will be removed entirely in v12.0"), +G("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{p=Q(p,e)}, +initHighlighting:()=>{ +_(),G("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),G("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){ +if(W("Language definition for '{}' could not be registered.".replace("{}",e)), +!r)throw t;W(t),s=l} +s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(s))s[t]===e&&delete s[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:k,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),o.push(e)}, +removePlugin:e=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}),n.debugMode=()=>{ +r=!1},n.safeMode=()=>{r=!0},n.versionString="11.9.0",n.regex={concat:h, +lookahead:g,either:f,optional:d,anyNumberOfTimes:u} +;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n +},ne=te({});return ne.newInstance=()=>te({}),ne}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `bash` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/, +end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{ +className:"variable",variants:[{ +begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE] +},i=e.inherit(e.COMMENT(),{match:[/(^|\s)/,/#.*$/],scope:{2:"comment"}}),c={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},o={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(o);const r={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),m={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[l,e.SHEBANG(),m,r,i,c,{match:/(\/[a-z._-]+)+/},o,{match:/\\"/},{ +className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}})() +;hljs.registerLanguage("bash",e)})();/*! `c` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),s="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",r="("+s+"|"+n.optional(a)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",i={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},l={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(l,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(a)+e.IDENT_RE,relevance:0 +},g=n.optional(a)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},m=[c,i,t,e.C_BLOCK_COMMENT_MODE,o,l],_={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:m.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:s,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})], +relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/, +keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,l,o,i,{begin:/\(/, +end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,l,o,i] +}]},i,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c, +strings:l,keywords:u}}}})();hljs.registerLanguage("c",e)})();/*! `nix` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={ +keyword:["rec","with","let","in","inherit","assert","if","else","then"], +literal:["true","false","or","and","null"], +built_in:["import","abort","baseNameOf","dirOf","isNull","builtins","map","removeAttrs","throw","toString","derivation"] +},s={className:"subst",begin:/\$\{/,end:/\}/,keywords:n},a={className:"string", +contains:[{className:"char.escape",begin:/''\$/},s],variants:[{begin:"''", +end:"''"},{begin:'"',end:'"'}] +},i=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{ +begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{ +className:"attr",begin:/\S+/,relevance:.2}]}];return s.contains=i,{name:"Nix", +aliases:["nixos"],keywords:n,contains:i}}})();hljs.registerLanguage("nix",e) +})();/*! `shell` grammar compiled for Highlight.js 11.9.0 */ +(()=>{var s=(()=>{"use strict";return s=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]})})();hljs.registerLanguage("shell",s)})(); \ No newline at end of file