From cfe0f780369bdc779a70f57ff24de3494c7cc726 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 22 May 2024 03:41:07 +0200 Subject: [PATCH] simplify, deduplicate - move introduction to the overview - use nix-shell and nix-instantiate - be a bit more precise where it seems important - add section headings - add more links - cut redundant parts from the deep dive --- _redirects | 1 + .../module-system/a-basic-module/default.nix | 10 ++ .../module-system/a-basic-module/eval.nix | 7 -- .../module-system/a-basic-module/eval.sh | 1 + .../module-system/a-basic-module/index.md | 104 ++++++++---------- .../module-system/a-basic-module/run.sh | 3 - .../{module-system.md => deep-dive.md} | 64 ++--------- source/tutorials/module-system/index.md | 29 ++++- 8 files changed, 92 insertions(+), 127 deletions(-) create mode 100644 source/tutorials/module-system/a-basic-module/default.nix delete mode 100644 source/tutorials/module-system/a-basic-module/eval.nix create mode 100644 source/tutorials/module-system/a-basic-module/eval.sh delete mode 100644 source/tutorials/module-system/a-basic-module/run.sh rename source/tutorials/module-system/{module-system.md => deep-dive.md} (92%) diff --git a/_redirects b/_redirects index 338dfdf04..039c9040c 100644 --- a/_redirects +++ b/_redirects @@ -30,5 +30,6 @@ /tutorials/learning-journey/sharing-dependencies /guides/recipes/sharing-dependencies 301 /tutorials/learning-journey/packaging-existing-software /tutorials/packaging-existing-software 301 /tutorials/file-sets /tutorials/working-with-local-files 301 +/tutorials/module-system/module-system /tutorials/module-system/deep-dive 301 /permalink/stub-ld /guides/faq#how-to-run-non-nix-executables 301 diff --git a/source/tutorials/module-system/a-basic-module/default.nix b/source/tutorials/module-system/a-basic-module/default.nix new file mode 100644 index 000000000..7f0e91cb7 --- /dev/null +++ b/source/tutorials/module-system/a-basic-module/default.nix @@ -0,0 +1,10 @@ +{ pkgs ? import { } }: +let + result = pkgs.lib.evalModules { + modules = [ + ./options.nix + ./config.nix + ]; + }; +in +result.config diff --git a/source/tutorials/module-system/a-basic-module/eval.nix b/source/tutorials/module-system/a-basic-module/eval.nix deleted file mode 100644 index 2a0e09417..000000000 --- a/source/tutorials/module-system/a-basic-module/eval.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ pkgs }: -(pkgs.lib.evalModules { - modules = [ - ./options.nix - ./config.nix - ]; -}).config diff --git a/source/tutorials/module-system/a-basic-module/eval.sh b/source/tutorials/module-system/a-basic-module/eval.sh new file mode 100644 index 000000000..772479a07 --- /dev/null +++ b/source/tutorials/module-system/a-basic-module/eval.sh @@ -0,0 +1 @@ +nix-shell -p jq --run "nix-instantiate --eval --json | jq" diff --git a/source/tutorials/module-system/a-basic-module/index.md b/source/tutorials/module-system/a-basic-module/index.md index c7ad23b5c..4fe01d7a1 100644 --- a/source/tutorials/module-system/a-basic-module/index.md +++ b/source/tutorials/module-system/a-basic-module/index.md @@ -2,107 +2,89 @@ What is a module? -* A module is a function that takes an attrset and returns an attrset. -* It *may* declare options. -* It *may* define option values. -* When evaluated, it produces a configuration based on the declarations and definitions. +* A module is a function that takes an attribute set and returns an attribute set. +* It may declare options, telling which attributes are allowed in the final outcome. +* It may define values, for options declared by itself or other modules. +* When evaluated by the module system, it produces an attribute set based on the declarations and definitions. -The format is like so: +The simplest possible module is a function that takes any attributes and returns an empty attribute set: ```{code-block} nix -:caption: useless.nix -{...}: { +:caption: options.nix +{ ... }: +{ } ``` -This, as the filename suggests, is completely useless. -It takes no arguments and returns an empty attrset. -However, it is a valid module. -Let us add to this to make it a bit more useful. - To define any values, the module system first has to know which ones are allowed. -This is done by declaring options that specify which values can be set and used elsewhere. -Options are declared by adding an attribute under the top-level `options` attribute. -The most general way to declare an option is using `lib.mkOption`. +This is done by declaring *options* that specify which attributes can be set and used elsewhere. + +## Declaring options + +Options are declared under the top-level `options` attribute with [`lib.mkOption`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.options.mkOption). ```{literalinclude} options.nix :language: nix :caption: options.nix ``` -While many attributes for customizing options are available, -the most important one is `type`, -which specifies which values are valid for an option. -There are several types available under [`lib.types`][option-types-basic] in the Nixpkgs library. +:::{note} +The `lib` argument is passed automatically by the module system. +This makes [Nixpkgs library functions](https://nixos.org/manual/nixpkgs/stable/#chap-functions) available in each module's function body. -As you can see, we have declared an option `name`. -We have specificied that the `name` option will be of type `str`, so the module system will expect a string when we set a value. +The ellipsis `...` is necessary because the module system can pass arbitrary arguments to modules. -You may have noticed that we also changed the function arguments. -Now the module is a function which takes *at least* one argument, `lib`, -and may accept other arguments (expressed by the ellipsis `...`). -This will make Nixpkgs library functions available within the function body. -We needed this to get access to `mkOption` and `types`. +::: -:::{note} -The ellipsis `...` is necessary because arbitrary arguments can be passed to modules. -Every module should have this. +The attribute `type` in the argument to `lib.mkOption` specifies which values are valid for an option. +There are several types available under [`lib.types`](https://nixos.org/manual/nixos/stable/#sec-option-types-basic). -The `lib` argument is passed automatically by the module system. -It is absolutely vital for modules that have option declarations, as you will need `lib` for defining options and their types. -It is one of several arguments that are automatically provided by the module system. -The full list of arguments is discussed later. -::: +Here we have declared an option `name` of type `str`: +The module system will expect a string when a value is defined. Now that we have declared an option, we would naturally want to give it a value. -Options can be set or *defined* using another top-level attribute, `config`. + +## Defining values + +Options are set or *defined* under the top-level `config` attribute: ```{literalinclude} config.nix :language: nix :caption: config.nix ``` -Previously, in our option declaration, we created an option, `name`, with a string type. +In our option declaration, we created an option `name` with a string type. Here, in our option definition, we have set that same option to a string. -:::{note} -`options` and `config` and have formal names — -that is ***option declarations*** and ***option definitions*** respectively. -The rest of these lessons will use them interchangeably. -::: +Option declarations and option definitions don't need to be in the same file. +Which modules will contribute to the resulting attribute set is specified when setting up module system evaluation. -:::{note} -Our option declarations and option definitions do not need to exist in the same file. -When we evaluate our modules, we can simply include both files. -As long as every definition has a declaration, we can successfully evaluate our modules. -If there is an option definition that has not been declared, the module system will throw an error. -::: +## Evaluating modules + +Modules are evaluated by [`lib.evalModules`](https://nixos.org/manual/nixpkgs/stable/#module-system-lib-evalModules) from the Nixpkgs library. +It takes an attribute set as an argument, where the `modules` attribute is a list of modules to merge and evaluate. -Now that we have our declaration and definition, how do we evaluate them? -There is a function provided by the Nixpkgs library, `evalModules`. -It takes an attrset as an argument and one of the attributes is `modules` which is a list of modules you want to merge and evaluate. -The output of `evalModules` is a rather large attrset with a information about all the modules. -For now, the attribute we care about is `config` which is where the final configuration values appear. +The output of `evalModules` contains information about all evaluated modules, and the final values appear in the attribute `config`. -```{literalinclude} eval.nix +```{literalinclude} default.nix :language: nix -:caption: eval.nix +:caption: default.nix ``` -We can create a helper script to parse and evaluate our `eval.nix` file and print the output in a nice format. +Here's a helper script to parse and evaluate our `default.nix` file with [`nix-instantiate --eval`](https://nixos.org/manual/nix/stable/command-ref/nix-instantiate) and print the output as JSON: -```{literalinclude} run.sh +```{literalinclude} eval.sh :language: bash -:caption: run.sh +:caption: eval.sh ``` -If you execute the run file (`./run.sh`), you should see an output that matches what we have configured. +As long as every definition has a corresponding declaration, evaluation will be successful. +If there is an option definition that has not been declared, or the defined value has the wrong type, the module system will throw an error. + +Running the script (`./eval.sh`) should show an output that matches what we have configured: ```{code-block} { "name": "Boaty McBoatface" } ``` - -[option-types-basic]: https://nixos.org/manual/nixos/stable/#sec-option-types-basic - diff --git a/source/tutorials/module-system/a-basic-module/run.sh b/source/tutorials/module-system/a-basic-module/run.sh deleted file mode 100644 index 66d9980ed..000000000 --- a/source/tutorials/module-system/a-basic-module/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -nix eval -f eval.nix \ - --apply 'x: x {pkgs = import {};}' \ - --json | nix run nixpkgs#jq -- . diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/deep-dive.md similarity index 92% rename from source/tutorials/module-system/module-system.md rename to source/tutorials/module-system/deep-dive.md index 4e215c1ef..6aefbd277 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/deep-dive.md @@ -3,8 +3,7 @@ Or: *Wrapping the world in modules* -Much of the power in Nixpkgs and NixOS comes from the module system. -It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. +In this tutorial you will follow an extensive demonstration of how to wrap an existing API with Nix modules. ## Overview @@ -14,25 +13,12 @@ It may help playing it alongside this tutorial to better keep track of changes t ### What will you learn? -In this tutorial you'll learn -- what a module is -- how to define one -- what options are -- how to declare them -- how to express dependencies between modules - -and follow an extensive demonstration of how to wrap an existing API with Nix modules. - -Concretely, you'll write modules to interact with the [Google Maps API](https://developers.google.com/maps/documentation/maps-static), declaring options which represent map geometry, location pins, and more. +You'll write modules to interact with the [Google Maps API](https://developers.google.com/maps/documentation/maps-static), declaring module options which represent map geometry, location pins, and more. During the tutorial, you will first write some *incorrect* configurations, creating opportunities to discuss the resulting error messages and how to resolve them, particularly when discussing type checking. ### What do you need? -- Familiarity with data types and general programming concepts -- A {ref}`Nix installation ` to run the examples -- Intermediate proficiency in reading and writing the Nix language - You will use two helper scripts for this exercise. Download {download}`map.sh ` and {download}`geocode.sh ` to your working directory. @@ -40,16 +26,9 @@ Download {download}`map.sh ` and {download}`geocode.sh ` to run the examples +- Intermediate proficiency in reading and writing the {ref}`Nix language ` + +## How long will it take? + +This is a very long tutorial. +Prepare for at least 3 hours of work. ```{toctree} :maxdepth: 1 +:caption: Lessons +:numbered: a-basic-module/index.md -module-system.md +deep-dive.md ```