Skip to content

Commit

Permalink
lesson/improve getting started section (#14)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
djacu authored Feb 12, 2024
1 parent 012a14d commit 5c013bb
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 24 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
166 changes: 142 additions & 24 deletions lessons/001-a-module/lesson.md
Original file line number Diff line number Diff line change
@@ -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
File renamed without changes.
38 changes: 38 additions & 0 deletions lessons/module-evaluation/lesson.md
Original file line number Diff line number Diff line change
@@ -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`.

File renamed without changes.
1 change: 1 addition & 0 deletions site/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

1 comment on commit 5c013bb

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.