From 5b29e71e43cc0763d02fc68582639aaebed1fac2 Mon Sep 17 00:00:00 2001 From: Zach Mitchell Date: Wed, 2 Aug 2023 21:57:38 -0600 Subject: [PATCH 1/3] Add shell.nix tutorial --- .../learning-journey/shell-dot-nix.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 source/tutorials/learning-journey/shell-dot-nix.md diff --git a/source/tutorials/learning-journey/shell-dot-nix.md b/source/tutorials/learning-journey/shell-dot-nix.md new file mode 100644 index 000000000..fd3812167 --- /dev/null +++ b/source/tutorials/learning-journey/shell-dot-nix.md @@ -0,0 +1,159 @@ +# TITLE + + + +## Overview + +### What will you learn? + + +- How to create reproducible shell environments + +### How long will it take? +WIP + + + +### What will you need? + + +- A basic understanding of the Nix language + +## Entering a shell with Python installed +Suppose we wanted to enter a shell in which Python 3.10 was installed. +The simplest possible way to accomplish this is via the `nix-shell -p` command: +``` +$ nix-shell -p python310 +``` + +This command works, but there's a number of inefficiences: +- You have to type out `-p python310` every time you enter the shell. +- It doesn't scale to an arbitrary number of packages (you would have to type out each package name each time). +- It doesn't (ergonomically) allow you any further customization of your shell environment. + +A better solution is to create our shell environment from a `shell.nix` file. + +## A basic `shell.nix` file +The `nix-shell` command by default looks for a file called `shell.nix` in the current directory and tries to build a shell environment by evaluating the Nix expression in this file. +So, if you properly describe the shell environment you want in a `shell.nix` file, you can enter it with just the `nix-shell` command without any further arguments. +No more specifying packages on the command line. +Here's what a basic `shell.nix` looks like that installs Python 3.10 as before: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + ]; + } +``` +where `mkShell` is a function that when called produces a shell environment. + +If you save this into a file called `shell.nix` and call `nix-shell` in the directory containing this `shell.nix` file, you'll enter a shell with Python 3.10 installed. + +## Adding packages +Additional executable packages are added to the shell by adding them to the `packages` attribute. +For example, let's say we wanted to add `curl` to our shell environment. +The new `shell.nix` would look like this: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl # new package + ]; + } +``` + +TODO: go into `packages` vs. `buildInputs` + +## Environment variables +It's common to want to automatically export certain environment variables when you enter a shell environment. +For example, you could have a database that depends on an environment variable to set the default authentication credentials during development. + +Setting an environment variable in via `shell.nix` is trivial. +Any attribute in the `mkShell` function call that `mkShell` doesn't recognize as a reserved attribute name will be set to an environment variable in the shell environment. +The attributes that are reserved are listed in the [Nixpkgs manual][mkshell_attrs] and include `packages`, `name`, and several others. + +[mkshell_attrs]: https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell-attributes + +Let's say you wanted to set the database user (`DB_USER`) and password (`DB_PASSWORD`) via environment variables in your `shell.nix` file. +This is how that would look: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + # Database username and password + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + } +``` + +Not only can you set new environment variables, but you can overwrite _existing_ environment variables. +For instance, the shell prompt format is set by the `PS1` environment variable. +In order to set your own prompt you can simply set the `PS1` attribute in the attribute set passed to the `mkShell` command +To set the shell prompt to the format `@ [myEnv] $ ` the `shell.nix` file would look like this: + +FIXME: This doesn't actually set the prompt for some reason + +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + # Database username and password + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + + # Set the shell prompt to '@ [myEnv] $ ' + PS1 = "\u@\h [myEnv] $ "; + } +``` + + +## Startup commands +You may want to perform some initialization before entering the shell environment (for example, maybe you want to ensure that a file exists). +Commands you'd like to run before entering the shell environment can be placed in the `shellHook` attribute of the attribute set provided to the `mkShell` function. +To ensure that a file `should_exist.txt` exists, the `shell.nix` file would look like this: +```nix +let + pkgs = import {}; +in + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + # Database username and password + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + + # Ensure that 'should_exist.txt' exists + shellHook = '' + touch should_exist.txt + ''; + } +``` + + +## Where to next? + + + + +WIP From a0a4cd896857b49452cffb753d73142adef53b1f Mon Sep 17 00:00:00 2001 From: Zach Mitchell Date: Wed, 9 Aug 2023 22:06:21 -0600 Subject: [PATCH 2/3] Update with review comments --- .../learning-journey/shell-dot-nix.md | 146 +++++++++--------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/source/tutorials/learning-journey/shell-dot-nix.md b/source/tutorials/learning-journey/shell-dot-nix.md index fd3812167..32ba34c14 100644 --- a/source/tutorials/learning-journey/shell-dot-nix.md +++ b/source/tutorials/learning-journey/shell-dot-nix.md @@ -20,14 +20,14 @@ WIP - A basic understanding of the Nix language ## Entering a shell with Python installed -Suppose we wanted to enter a shell in which Python 3.10 was installed. +Suppose we wanted to enter a shell in which Python 3 was installed. The simplest possible way to accomplish this is via the `nix-shell -p` command: ``` -$ nix-shell -p python310 +$ nix-shell -p python3 ``` This command works, but there's a number of inefficiences: -- You have to type out `-p python310` every time you enter the shell. +- You have to type out `-p python3` every time you enter the shell. - It doesn't scale to an arbitrary number of packages (you would have to type out each package name each time). - It doesn't (ergonomically) allow you any further customization of your shell environment. @@ -40,13 +40,13 @@ No more specifying packages on the command line. Here's what a basic `shell.nix` looks like that installs Python 3.10 as before: ```nix let - pkgs = import {}; + pkgs = import {}; in - pkgs.mkShell { - packages = [ - pkgs.python310 - ]; - } + pkgs.mkShell { + packages = [ + pkgs.python3 + ]; + } ``` where `mkShell` is a function that when called produces a shell environment. @@ -58,17 +58,19 @@ For example, let's say we wanted to add `curl` to our shell environment. The new `shell.nix` would look like this: ```nix let - pkgs = import {}; + pkgs = import {}; in - pkgs.mkShell { - packages = [ - pkgs.python310 - pkgs.curl # new package - ]; - } + pkgs.mkShell { + packages = [ + pkgs.python3 + pkgs.curl # new package + ]; + } ``` -TODO: go into `packages` vs. `buildInputs` +:::{note} +`nix-shell` was originally conceived as a way to construct a shell environment containing the tools needed to *develop software*; only later was it widely used as a general way to construct temporary environments for other purposes. Also note that `mkShell` is a [wrapper around `mkDerivation`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell) so strictly speaking you can provide any attributes to `mkShell` that you could to `mkDerivation` such as `buildInputs`. However, the `packages` attribute provided to `mkShell` is an alias for `buildInputs`, so you shouldn't need to provide both `packages` and `buildInputs`. +::: ## Environment variables It's common to want to automatically export certain environment variables when you enter a shell environment. @@ -84,44 +86,50 @@ Let's say you wanted to set the database user (`DB_USER`) and password (`DB_PASS This is how that would look: ```nix let - pkgs = import {}; + pkgs = import {}; in - pkgs.mkShell { - packages = [ - pkgs.python310 - pkgs.curl - ]; - - # Database username and password - DB_USER = "db_user"; - DB_PASSWORD = "super secret don't look"; - } + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + env = { + # Database credentials + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + }; + } ``` -Not only can you set new environment variables, but you can overwrite _existing_ environment variables. -For instance, the shell prompt format is set by the `PS1` environment variable. -In order to set your own prompt you can simply set the `PS1` attribute in the attribute set passed to the `mkShell` command -To set the shell prompt to the format `@ [myEnv] $ ` the `shell.nix` file would look like this: -FIXME: This doesn't actually set the prompt for some reason +Some variables are protected. +For example, the shell prompt format for most shells is set by the `PS1` environment variable, but `nix-shell` already overrides this by default, and does not allow us to alter the `PS1` attribute directly. +In cases where `nix-shell` prevents you from settings these environment variables directly, we can still set a new value for that environment variable by manually exporting it in the [`shellHook` attribute](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell-attributes) passed to `mkShell`. + +To set the shell prompt to the format `@ >>> `, the `shell.nix` file would look like this: ```nix let - pkgs = import {}; + pkgs = import {}; in - pkgs.mkShell { - packages = [ - pkgs.python310 - pkgs.curl - ]; - - # Database username and password - DB_USER = "db_user"; - DB_PASSWORD = "super secret don't look"; - - # Set the shell prompt to '@ [myEnv] $ ' - PS1 = "\u@\h [myEnv] $ "; - } + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + env = { + # Database credentials + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + }; + + # Set the shell prompt format + shellHook = '' + export PS1="\u@\h >>> " + ''; + } ``` @@ -129,31 +137,27 @@ in You may want to perform some initialization before entering the shell environment (for example, maybe you want to ensure that a file exists). Commands you'd like to run before entering the shell environment can be placed in the `shellHook` attribute of the attribute set provided to the `mkShell` function. To ensure that a file `should_exist.txt` exists, the `shell.nix` file would look like this: + ```nix let - pkgs = import {}; + pkgs = import {}; in - pkgs.mkShell { - packages = [ - pkgs.python310 - pkgs.curl - ]; - - # Database username and password - DB_USER = "db_user"; - DB_PASSWORD = "super secret don't look"; - - # Ensure that 'should_exist.txt' exists - shellHook = '' - touch should_exist.txt - ''; - } + pkgs.mkShell { + packages = [ + pkgs.python310 + pkgs.curl + ]; + + env = { + # Database credentials + DB_USER = "db_user"; + DB_PASSWORD = "super secret don't look"; + }; + + # Set shell prompt format, ensure that 'should_exist.txt' exists + shellHook = '' + export PS1="\u@\h >>> " + touch should_exist.txt + ''; + } ``` - - -## Where to next? - - - - -WIP From 56429a2b666755dd100ea102d000eafe8c3f47f1 Mon Sep 17 00:00:00 2001 From: Zach Mitchell Date: Thu, 10 Aug 2023 09:20:30 -0600 Subject: [PATCH 3/3] Update with docs team review --- source/tutorials/index.md | 1 + source/tutorials/learning-journey/index.md | 10 ++++ .../learning-journey/shell-dot-nix.md | 53 ++++++++----------- 3 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 source/tutorials/learning-journey/index.md diff --git a/source/tutorials/index.md b/source/tutorials/index.md index e244be501..a5f93a1bc 100644 --- a/source/tutorials/index.md +++ b/source/tutorials/index.md @@ -9,6 +9,7 @@ These sections contains series of lessons to get started. install-nix.md first-steps/index.md +learning-journey/index.md nixos/index.md cross-compilation.md ``` diff --git a/source/tutorials/learning-journey/index.md b/source/tutorials/learning-journey/index.md new file mode 100644 index 000000000..d7778ea18 --- /dev/null +++ b/source/tutorials/learning-journey/index.md @@ -0,0 +1,10 @@ +# Learning Journey + +This collection of tutorials guides you through your first steps with Nix. +This series is a work in progress and will have some overlap with existing tutorials. +The intention is to unify these tutorials over time. + + ```{toctree} + :maxdepth: 1 + shell-dot-nix.md + ``` diff --git a/source/tutorials/learning-journey/shell-dot-nix.md b/source/tutorials/learning-journey/shell-dot-nix.md index 32ba34c14..606066978 100644 --- a/source/tutorials/learning-journey/shell-dot-nix.md +++ b/source/tutorials/learning-journey/shell-dot-nix.md @@ -1,4 +1,4 @@ -# TITLE +# Creating shell environments @@ -7,17 +7,17 @@ ### What will you learn? -- How to create reproducible shell environments +How to create and configure reproducible shell environments ### How long will it take? -WIP +30 minutes ### What will you need? -- A basic understanding of the Nix language +A basic understanding of the Nix language ## Entering a shell with Python installed Suppose we wanted to enter a shell in which Python 3 was installed. @@ -50,7 +50,7 @@ in ``` where `mkShell` is a function that when called produces a shell environment. -If you save this into a file called `shell.nix` and call `nix-shell` in the directory containing this `shell.nix` file, you'll enter a shell with Python 3.10 installed. +If you save this into a file called `shell.nix` and call `nix-shell` in the directory containing this `shell.nix` file, you'll enter a shell with Python 3 installed. ## Adding packages Additional executable packages are added to the shell by adding them to the `packages` attribute. @@ -102,35 +102,14 @@ in } ``` +:::{warning} +Some variables are protected from being overridden via the `env` attribute as described above. -Some variables are protected. -For example, the shell prompt format for most shells is set by the `PS1` environment variable, but `nix-shell` already overrides this by default, and does not allow us to alter the `PS1` attribute directly. -In cases where `nix-shell` prevents you from settings these environment variables directly, we can still set a new value for that environment variable by manually exporting it in the [`shellHook` attribute](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell-attributes) passed to `mkShell`. +For example, the shell prompt format for most shells is set by the `PS1` environment variable, but `nix-shell` already overrides this by default, and will ignore a `PS1` attribute listed in `env`. -To set the shell prompt to the format `@ >>> `, the `shell.nix` file would look like this: - -```nix -let - pkgs = import {}; -in - pkgs.mkShell { - packages = [ - pkgs.python310 - pkgs.curl - ]; - - env = { - # Database credentials - DB_USER = "db_user"; - DB_PASSWORD = "super secret don't look"; - }; - - # Set the shell prompt format - shellHook = '' - export PS1="\u@\h >>> " - ''; - } -``` +If you _really_ need to override these protected environment variables you can use the `shellHook` feature discussed in the next section and `export MYVAR="value"` in the hook script. +It's generally discouraged to set environment variables this way. +::: ## Startup commands @@ -161,3 +140,13 @@ in ''; } ``` + +Some other common use cases for `shellHook` are: +- Initializing a local data directory for a database used in a development environment +- Running commands to load secrets into environment variables +- Installing pre-commit-hooks + +## Where to next? +- [`mkShell` documentation](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell) +- Nixpkgs [shell functions and utilities](https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-functions) documentation +