1
0
Fork 0
mirror of https://github.com/NixOS/nix.dev.git synced 2024-10-18 14:32:43 -04:00

Update sharing deps tutorial

This commit is contained in:
Zach Mitchell 2023-08-28 11:17:07 -06:00
parent bc863f7f85
commit b038f6e117

View file

@ -6,7 +6,7 @@
### What will you learn?
In this tutorial you'll learn how not to repeat yourself by sharing dependencies between 'default.nix`, which is responsible for building the project, and `shell.nix`, which is responsible for providing you with an environment to work in.
In this tutorial you'll learn how not to repeat yourself by sharing dependencies between `default.nix`, which is responsible for building the project, and `shell.nix`, which is responsible for providing you with an environment to work in.
### How long will it take?
@ -14,25 +14,26 @@ This tutorial will take approximately 1 hour.
### What will you need?
This tutorial assumes you've seen a derivation (`mkDerivation`, `buildPythonApplication`, etc) before, and that you've seen `nix-shell` used to create shell environments.
This tutorial assumes you're familiar with Nixpkgs build helpers (`mkDerivation`, `buildPythonApplication`, etc) and know how to create environments for `nix-shell`.
While this tutorial uses Python as the language for the example project, no actual Python knowledge is requried.
## Setting the stage
Suppose you have a working build for your project in a `default.nix` file so that when you run `nix-build` it builds your project.
It includes all of the dependencies needed to build it, but nothing more.
Now suppose you wanted to bring in some tools during development, such as a linter, a code formatter, [git commit hooks][git_hooks], etc.
Now suppose you wanted to bring in some tools during development, such as a linter, a code formatter, [git commit hooks], etc.
[git_hooks]: https://github.com/cachix/pre-commit-hooks.nix
[git commit hooks]: https://github.com/cachix/pre-commit-hooks.nix
One solution could be to add those packages to your build.
This would certainly work in a pinch, but now your build depends on packages that aren't necessary for it to actually build.
This would certainly work in a pinch, but now your build depends on packages that aren't actually required.
A better solution is to add those development packages to a shell environment so that the build dependencies stay as lean as possible.
However, now you need to define a `shell.nix` that not only provides your development packages, but can also build your project.
In other words, you need a `shell.nix` that brings in all of the packages that your build depends on.
You could certainly copy the build dependencies from `default.nix` and copy them into `shell.nix`, but this is less than ideal:
your build dependencies are defined in multiple places, and aside from repeating yourself there's now the possiblity that the dependencies in `default.nix` and `shell.nix` may fall out of sync.
your build dependencies would be defined in two places.
Maintaining duplicate declarations in `default.nix` and `shell.nix` opens the possibility for them to diverge, producing surprising results.
There is a better way!
@ -86,10 +87,10 @@ app = "app:main"
This file tells Python how to build the project and what will execute when you run the executable called `app`.
For the Nix part of the project you'll create two files: `build.nix` and `default.nix`.
The actual build recipe will be in `build.nix` and `default.nix` will import this file to perform the build.
For the Nix part of the project you'll create two files: `package.nix` and `default.nix`.
The actual build recipe will be in `package.nix` and `default.nix` will import this file to perform the build.
First create a `build.nix` file like this:
First create a `package.nix` file like this:
```nix
{
@ -112,10 +113,12 @@ buildPythonApplication {
The Nix expression in this file is a _function_ that produces a derivation.
This method of defining builds is a common design pattern in the Nix community, and is the format used throughout the `nixpkgs` repository.
This particular derivation builds your Python application and ensures that `flask`, the library used to create the web application, is available at runtime for the application.
This particular derivation builds your Python application and ensures that `flask`, the library used to create the web application, is available at runtime.
Note that on line 11 of the `build.nix` file the `src` attribute is set using `builtins.path`.
This is a good habit to form because it will give your build a fixed name rather than simply inheriting the name of the parent directory.
Note that on line 11 of the `package.nix` file the `src` attribute is set using `builtins.path`.
This creates a [reproducible source path], and is a good habit to form.
[reproducible source path]: https://nix.dev/recipes/best-practices#reproducible-source-paths
Finally, create a `default.nix` that looks like this:
@ -124,14 +127,15 @@ let
pkgs = import <nixpkgs> {};
in
{
build = pkgs.python3Packages.callPackage ./build.nix {};
build = pkgs.python3Packages.callPackage ./package.nix {};
}
```
The `callPackage` function reads the expression in `build.nix` to determine which inputs it needs (in this case, `buildPythonApplication`, `setuptools-scm`, and `flask`), then calls the expression with the inputs that were requested.
You can read more about `callPackage` in the [Nix Pills][nix_pills_callpackage].
The `python3Packages.callPackage` function determines which arguments the function in `package.nix` takes (in this case, `buildPythonApplication`, `setuptools-scm`, and `flask`) then calls the function in `package.nix` with the corresponding attributes from `python3Packages`.
You can read more about the `callPackage` pattern in the [Nix Pills][nix_pills_callpackage].
Also note that this `default.nix` returns an attribute set with a single attribute called `build`.
This allows adding more attributes later without breaking existing consumers.
Try to build this project by running `nix-build -A build`
[nix_pills_callpackage]: https://nixos.org/guides/nix-pills/callpackage-design-pattern.html
@ -144,7 +148,7 @@ Edit `default.nix` to look like this:
```nix
let
pkgs = import <nixpkgs> {};
build = pkgs.python3Packages.callPackage ./build.nix {};
build = pkgs.python3Packages.callPackage ./package.nix {};
in
{
inherit build;
@ -163,7 +167,7 @@ Let's break this all down.
The `pkgs.mkShell` function produces a shell environment, and it's common to put the expression that calls this function in a `shell.nix` file by itself.
However, doing so means that you to declare `pkgs = ...` a second time (first in `default.nix`, then again in `shell.nix`) and if you're pinning `nixpkgs` to a particular revision you may forget to update one of the declarations.
By putting the `build` declaration on line 3 you're able to use it throughout the attribute set that spans lines 5-14.
By putting the `build` declaration in the `let` binding on line 3 you're able to use it throughout the attribute set that spans lines 5-14.
Line 6 includes the `build` attribute in the attribute set.
Lines 7-13 produce the shell environment for working on the project.
@ -198,7 +202,7 @@ $ which black
These are the Nix store paths on the author's machine at the time of writing.
You will likely see different store paths and versions depending on when you execute these commands and the architecture of the machine that the commands are executed on.
## Where to next?
## Next steps
- [Nixpkgs Manual - `mkShell`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell)
- [Nix Pills - callPackage Design Pattern][nix_pills_callpackage]
- [Creating shell environments](https://nix.dev/tutorials/learning-journey/shell-dot-nix.html)