From 5c013bb249fd9aae4ae5b88c0c0f8fce60fe0907 Mon Sep 17 00:00:00 2001 From: Dan Baker Date: Mon, 12 Feb 2024 09:09:03 -0800 Subject: [PATCH] lesson/improve getting started section (#14) * Started reworking the lesson that introduces what modules are. Working state. * Added an attribution section to the README. * Move module evaluation discussion to a new lesson. * Moved a link that got accidently transfered. * Added a note about modules being attrsets. * Added the function arguments section content. * Updated the beginning and added a definition section. --- README.md | 12 ++ lessons/001-a-module/lesson.md | 166 +++++++++++++++--- .../eval.nix | 0 lessons/module-evaluation/lesson.md | 38 ++++ .../{001-a-module => module-evaluation}/run | 0 site/mkdocs.yml | 1 + 6 files changed, 193 insertions(+), 24 deletions(-) rename lessons/{001-a-module => module-evaluation}/eval.nix (100%) create mode 100644 lessons/module-evaluation/lesson.md rename lessons/{001-a-module => module-evaluation}/run (100%) diff --git a/README.md b/README.md index 8109a9f..e7ee3a4 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,15 @@ Please refer to the [CONTRIBUTING](CONTRIBUTING.md) file for guidelines on how t ## License This repository is licensed under the [MIT License](LICENSE.md), fostering open collaboration and knowledge sharing. + +## Attribution + +Much of content in these lessons is original but could only be attempted after consuming and thanks to the already existing resources out there. +The lessons here were inspired by or directly lifted content from the following resources: + +- The nixpkgs source code: + - [modules](https://github.com/NixOS/nixpkgs/blob/master/lib/modules.nix) + - [types](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) +- [The original module article on nix.dev](https://nix.dev/tutorials/module-system/module-system.html) +- [The NixOS Manual](https://nixos.org/manual/nixos/stable/#sec-writing-modules) +- [The NixOS Wiki](https://nixos.wiki/wiki/NixOS_modules) diff --git a/lessons/001-a-module/lesson.md b/lessons/001-a-module/lesson.md index 02f32a3..6a7af0d 100644 --- a/lessons/001-a-module/lesson.md +++ b/lessons/001-a-module/lesson.md @@ -1,39 +1,157 @@ -# A Module +# What is a module -In this lesson, we will cover what a module is composed of. +In this lesson, you will learn what a module is and how to define one. -In the [eval][eval] file, we have declared an attrset called `mymodule` which is composed of 3 -fields: `imports`, `options` and `config`. This is a module with default values for those fields. -You can omit any of those fields and they will use those same default values. +## Definition -[//]: # (./eval.nix) +A *module* is a function that returns an attrset. +It declares options with types. +It defines option values. +When evaluated, it produces a configuration based on the declarations and definitions. -If you execute the run file (`./run`), you will see printed the empty JSON object `{}`. +If that does not make a lot of sense, do not worry. +Keep reading. +The rest of this lesson will provide clarity. -[eval]: ./eval.nix +## Structure -## Module Fields +### An empty module -The `options` field lets you define variables that can be used in the `config` section. +Modules have the following basic structure. -The `config` field assigns values to options defined in other modules. We call this using an option. +```nix title="useless.nix" +{...}: { +} +``` -The `imports` field lets you import other modules from a module. +This, as the filename suggests, is completely useless. +It takes no arguments and returns an empty attrset. +However, it does evaluate. +So when we look at the following examples, keep in mind that everything we add is optional. -We will see what goes in those fields in later lessons. +### A basic module -## Evaluating Modules +Let us look at a more practical setup. -In the [eval][eval] file, we use a function called `evalModules`. This function takes an attrset as -argument which can contain the field named `modules`. In there, we can put as many module as we -want. +```nix title="default.nix" +{lib, ...}: { + imports = [ + ./relative/path/to/another/module.nix + /absolute/path/to/another/module.nix + ]; + options = { + # declarations of options + }; + }; + config = { + # configuration of options + }; +} +``` -`evalModules` will then merge all `imports`, `options` and `config` fields of all given modules -following some rules we will see in the next lessons. It then produces an attrset whose only -interesting field - the result of merging all modules - is found in the `config` field. This is the -one we print when executing the `./run` file. +!!! 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. -## Nixpkgs +#### function argument -The vast majority of nixpkgs is made out of modules, all merged together in the top-level -`evalModules`. +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. + +!!! note + The ellipsis `...` is necessary because arbitrary arguments can be passed to modules. + Every module you create that is a function, should have this. + + 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. + +#### imports + +This imports list enumerates the paths to other NixOS modules. +This is a useful mechanism for breaking up modules into small components and importing what you need. + +#### options + +To set 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`. + +``` nix title="options.nix" +{lib, ...}: { + options = { + name = lib.mkOption { + type = lib.types.str; + }; + }; +} +``` + +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. + +Here we have declared an option `name` with the `str` type. +This specifies that the only valid value is a string and can only be a single definition. + +#### config + +Option definitions are generally straight-forward bindings of values to option names. + +``` nix title="config.nix" +{ + config.name = "Boaty McBoatface"; +} +``` + +!!! note + Modules do not have to be functions. + They can be attrsets as well. + The above `config.nix` file is a valid module. + Generally, you can write modules that are only option definitions as attrsets. + +Note that 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. + +## Function Arguments + +When you define a module as a function like we did previously, there are certain arguments that are automatically provided. +You ***do not*** have to explicitely put them in the function signature as we have the ellipsis `...` to manage any arguments we do not use. + +All modules are passed the following arguments: + +- `lib`: The nixpkgs library. +- `config`: The results of all options after merging the values from all modules together. +- `options`: The options declared in all modules. +- `specialArgs`: An attribute set of extra arguments to be passed to the module functions. +- All attributes of `specialArgs`. + +!!! note + The fact that all the attributes of `specialArgs` are automatically provided means you don't need to add `specialArgs` to the module function signature if we want access to `specialArgs.thing`. + We can just add `thing` to the function signature and use it directly. + +When designing a module for NixOS, there are some additional arguments that are automatically provided: + +- `pkgs`: The nixpkgs package set according to the `nixpkgs.pkgs` option. +- `modulesPath`: The path to the NixOS modules directory in the nixpkgs repository. + +`modulesPath` is very handy as it allows you to import extra modules from the nixpkgs package tree without having to somehow make the module aware of the location of the `nixpkgs` or NixOS directories. +It allows you to do things like this: + +``` nix +{ modulesPath, ... }: { + imports = [ + (modulesPath + "/profiles/minimal.nix") + ]; +} +``` + +[option-types-basic]: https://nixos.org/manual/nixos/stable/#sec-option-types-basic diff --git a/lessons/001-a-module/eval.nix b/lessons/module-evaluation/eval.nix similarity index 100% rename from lessons/001-a-module/eval.nix rename to lessons/module-evaluation/eval.nix diff --git a/lessons/module-evaluation/lesson.md b/lessons/module-evaluation/lesson.md new file mode 100644 index 0000000..07438bf --- /dev/null +++ b/lessons/module-evaluation/lesson.md @@ -0,0 +1,38 @@ +# Module Evaluation + +In the [eval][eval] file, we have declared an attrset called `mymodule` which is composed of 3 +fields: `imports`, `options` and `config`. This is a module with default values for those fields. +You can omit any of those fields and they will use those same default values. + +[//]: # (./eval.nix) + +If you execute the run file (`./run`), you will see printed the empty JSON object `{}`. + +[eval]: ./eval.nix + +## Module Fields + +The `options` field lets you define variables that can be used in the `config` section. + +The `config` field assigns values to options defined in other modules. We call this using an option. + +The `imports` field lets you import other modules from a module. + +We will see what goes in those fields in later lessons. + +## Evaluating Modules + +In the [eval][eval] file, we use a function called `evalModules`. This function takes an attrset as +argument which can contain the field named `modules`. In there, we can put as many module as we +want. + +`evalModules` will then merge all `imports`, `options` and `config` fields of all given modules +following some rules we will see in the next lessons. It then produces an attrset whose only +interesting field - the result of merging all modules - is found in the `config` field. This is the +one we print when executing the `./run` file. + +## Nixpkgs + +The vast majority of nixpkgs is made out of modules, all merged together in the top-level +`evalModules`. + diff --git a/lessons/001-a-module/run b/lessons/module-evaluation/run similarity index 100% rename from lessons/001-a-module/run rename to lessons/module-evaluation/run diff --git a/site/mkdocs.yml b/site/mkdocs.yml index c19531a..c142178 100644 --- a/site/mkdocs.yml +++ b/site/mkdocs.yml @@ -9,6 +9,7 @@ nav: - Lessons: - Getting Started: - lessons/001-a-module/lesson.md + - lessons/module-evaluation/lesson.md - Types: - lessons/010-basic-types/lesson.md - lessons/composed-types/lesson.md