Nix Learning Roadmap
Nix can be used to make
reproducible development environemnts
, which means you can use any program packaged wit hnix while don’t have to install them permanently.
Learn the basics of the Nix language. Play around with the Nix REPL. Create your first flake, utils, and libs. Use nix as a package manager first. Learn how to solve your upstream Linux issues. Move slowly towards NixOS.
Nix Language Basics
General Rule
-
lazy evaluation, which means computes values when needed. (try to prepend
:p
in nix repl.) -
pure expression: declaring data and transform it with functions.
- values:
- primitives
- string
- interpolation
- file system path
- lookup path:
(try to avoid, since it's impurities) - Whenever a file system path is used in string interpolation, the contents of that file are copied to a special location in the file system, the Nix store, as a side effect.
/nix/store/<hash>-<name>
- indented string ‘’ ‘’
- string
- list
- attribute set
- function
- primitives
- assign names to values:
- attribute set
- let expression
- order of assignment does not matter.
- with
-
;
is used to delimit expressons. last expresson should omit it. -
inherit
-
string interpolation:
${...}
, only works for string values. - funciton
- only takes on argument.
- argument and function body are separated by
:
. - default arguments:
let f = {a, b ? 0}: a + b; in f {a = 1;}
- additional arguments
let f = {a, b, ...}: a + b; in f { a = 1; b = 2; c = 3;}
- @ pattern: {a, b, …}@args: a + b + args.c
builtins lib
-
primitive operations (primops)
import
is available at top level.- a classic pattern is to apply argument to the imported function:
import ./file.nix 1
- a classic pattern is to apply argument to the imported function:
- fetchers: Files to be used as build inputs do not have to come from the file system.
- builtins.fetchurl
- builtins.fetchTarball
- builtins.fetchGit
- builtins.fetchClosure
- These functions evaluate to a file system path in the Nix store.
pkgs.lib
Derivations
core of both nix and nix language.
- The Nix language is used to describe derivations.
- Nix runs derivations to produce build results.
- Build results can in turn be used as inputs for other derivations.
- compared to impore
derivation
function,stdenv.mkDerication
is more common. - The result of above functions is an attr set with certian structure and a special property: when user in string interpolation, it will evelautes to the nix store path of the build result.
??? stop at worked examples. https://nix.dev/tutorials/nix-language
- The Nix ecosystem and code style is driven by conventions.
nix pills
give introdution regarding derivations.
nix pills
-
derivatins/packages are stored in the nox store as follows: /nix/store/hash-name
-
Since Nix derivations are immutable, upgrading a library like glibc means recompiling all applications, because the glibc path to the Nix store has been hardcoded.
-
A derivation from a Nix language view point is simply a set, with some attributes.
- name
- system
- builder -> The builder will not inherit any variable from your running shell, otherwise builds would suffer from non-determinism.
-
derivation
function: It takes as input an attribute set, the attributes of which specify the inputs to the process. It outputs an attribute set, and produces a store derivation as a side effect of evaluation. -
.drv
file is the specification of how to build the derivation, without all the nix language fuzz. - when the derivation is buil, nix separate:
- instantiate: when nix expression is parsed, a drv set is returned, since nix wil lcreate .drv files, so we konow out paths beforehand.
- .drv is built, dependencies are built first.
- nix derivation show /nix/store/i76pr1cz0za3i9r6xq518bqqvd2raspw-foo.drv
- Nix automatically copies files or directories needed for the build into the store to ensure that they are not changed during the build process and that the deployment is stateless and independent of the building machine.
The trick: every attribute in the set passed to derivation will be converted to a string and passed to the builder as an environment variable. This is how the builder gains access to coreutils and gcc: when converted to strings, the derivations evaluate to their output paths, and appending /bin to these leads us to their binaries.
I’d like to stop here: https://nixos.org/guides/nix-pills/07-working-derivation
export PATH="$coreutils/bin:$gcc/bin" mkdir $out declare -xp gcc -o $out/simple $src
-
nix-build
does two jobs:- nix-instanttiate -> parse and evaluate simple.nix
-
nix-store -r -> realize
- inherit foo; -> foo = foo;
- inherit gcc coreutils; -> gcc = gcc; coreutils = coreutils;
- inherit (pkgs) gcc coreutils; => gcc = pkgs.gcc; coreutils = pkgs.coreutils;
-
This syntax only makes sense inside sets.
-
//: The // operator is an operator between two sets. The result is the union of the two sets. In case of conflicts between attribute names, the value on the right set is preferred.
- This function accepts a parameter pkgs, then returns a function which accepts a parameter attrs.
pkgs: attrs: let defaultAttrs = { builder = "${pkgs.bash}/bin/bash"; args = [./builder.sh]; baseInputs = with pkgs; [ gnutar gzip gnumake gcc coreutils gawk gnused gnugrep binutils.bintools ]; buildInputs = []; system = builtins.currentSystem; }; in derivation (defaultAttrs // attrs)
- analogy
- Analogy: in C you create objects in the heap, and then you compose them inside new objects. Pointers are used to refer to other objects.
- In Nix you create derivations stored in the nix store, and then you compose them by creating new derivations. Store paths are used to refer to other derivations.
- builder phase:
- environemnt setup
- unpack phase
- change directory phase
- configure phase -> ./configure
- build phase -> make
-
install phase -> make install
- Approaching builds in this way makes packages self-contained, ensuring (apart from data and configuration) that copying the runtime closure onto another machine is sufficient to run the program.
-
nix-shell: The take-away is that nix-shell drops us in a shell with the same (or very similar) environment used to run the builder.
- gargabe-collector:
-
GC roots
-
callPackage design pattern.
-
override design pattern.
-
nix laziness: https://nixcademy.com/posts/what-you-need-to-know-about-laziness/
-
nixpkgs has its own manual, it’s different from nix language itself.
-
Language
nix has attrset and lists
Ways to assign name to value:
- attrbute sets
let...in...
expressions- function arguments
Functions
- A function always takes exactly one argument.
- Argument and function body are separated by
:
.- On its left is the function argument.
- On its right is the function body.
What
Nix Ecosystem
- nix package manager
- non-FHS compliant
- declarative configuration (versus imperative approach)
- nix language
- like JSON with functions
- NixOS
- nixpgks
- more than 10000 packages
- builder functions
- mkShell
- utility functions
How
- To check
nix store
disk usage:du -hsc /nix/store
. - To reduce nix store disk uage:
nix-collect-garbage
. - For the design of nix, same servide will be launched for differernt project. To avoid conflicts, we can assign differernt ports for them. And kill those service on leave
nix shell
.
A samll discovery of rails. Rails will use PGPORT
env variable, if it’s defined, and use that port to connect to psql.
- Here is how to quit on exit shell: https://github.com/legendofmiracles/lila/blob/nix/shell.nix
but above only works on bash. for zsh, I have no clue.
Flake
A nix package manager feature that encforces a uniform structure for nix projects, pinning versions of dependencies in lock file.
nix flake init
: create a flake in the current directory from a template.nix flake show
: show outputs of a flake.nix flake metadata
: show flake metadata.
Structure
- description: a string
- inputs: an attrset
- outputs: a function that takes
self
(current flake) andinputs
as arguments.
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixkpgs"; /* home-manager will use nixkpgs from above, instead of its own version of nixpkgs. */
};
outputs = { self, nixpkgs } : /* self refers to current flake; nixpkgs is a flake from inputs */
let
pkgs = nixpkgs.legacyPackages.aarch64.hello;
in
{
/* For nix run */
packages.aarch64.default = nixpkgs.legacyPackages.aarch64.hello; /* hello is one of ouputs of nixkpgs flake. */
/* For nix develop */
devShells.aarch64.default = pkgs.mkShell { /* mkShell is a function from nixpkgs outputs. */
buildInputs = [pkgs.vim];
}
/* For NixOS */
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
modules = [
./configuration.nix
({ pkgs, ... } : {
programs.vim.defaultEditor = true;
})
];
};
};
}
Commands
nix run
: run a Nix application.- outputs.packages.”SYSTEM”.default
nix build
: build a package.- outpus.packages.”SYSTEM”.default
nix develop
: run a bash shell that provides the build environment of a derivation.- outpus.devShells.”SYSTEM”.default
nixos-rebuild
: build a nixos system.- outpus.nixosConfigurations.”HOSTNAME”
home-manager
: build a home configuration.- outputs.homeConfigurations.”USERNAME”
References:
Overlay
Overlays are Nix functions which accept two arguments, conventionally called final
and prev
, returning a set of packages, to customize Nixpkgs. Please check out this article for detail.
Nix Flake & Ruby on Rails
This is a sample project to set up ROR dev env by flake.
Nix REPL
https://aldoborrero.com/posts/2022/12/02/learn-how-to-use-the-nix-repl-effectively/
-
Nix Language Basics
-
nix repl
# Load nixpkgs
:l <nixpkgs>
# Load whatever flake you want
flake = builtins.getFlake "github:bobvanderlinden/nixpkgs-ruby"
flake1 = builtins.getFlake "github:numtide/flake-utils"
json = flake1.lib.eachDefaultSystem(system: { abc = system; })
builtins.toJSON json
# "{\"abc\":{\"aarch64-darwin\":\"aarch64-darwin\",\"aarch64-linux\":\"aarch64-linux\",\"x86_64-darwin\":\"x86_64-darwin\",\"x86_64-linux\":\"x86_64-linux\"}}"
# how to execute a lambda?