This section lists the functions built into the Nix language evaluator. All built-in functions are available through the global builtins
constant.
derivation attrs
derivation is described in its own section .
abort s
Abort Nix expression evaluation and print the error message s .
add e1 e2
Return the sum of the numbers e1 and e2 .
addDrvOutputDependencies s
Create a copy of the given string where a single consant string context element is turned into a “derivation deep” string context element.
The store path that is the constant string context element should point to a valid derivation, and end in .drv
.
The original string context element must not be empty or have multiple elements, and it must not have any other type of element other than a constant or derivation deep element. The latter is supported so this function is idempotent.
This is the opposite of builtins.unsafeDiscardOutputDependency
.
all pred list
Return true
if the function pred returns true
for all elements of list , and false
otherwise.
any pred list
Return true
if the function pred returns true
for at least one element of list , and false
otherwise.
attrNames set
Return the names of the attributes in the set set in an alphabetically sorted list. For instance, builtins.attrNames { y = 1; x = "foo"; }
evaluates to [ "x" "y" ]
.
attrValues set
Return the values of the attributes in the set set in the order corresponding to the sorted attribute names.
baseNameOf s
Return the base name of the string s , that is, everything following the final slash in the string. This is similar to the GNU basename
command.
bitAnd e1 e2
Return the bitwise AND of the integers e1 and e2 .
bitOr e1 e2
Return the bitwise OR of the integers e1 and e2 .
bitXor e1 e2
Return the bitwise XOR of the integers e1 and e2 .
break v
In debug mode (enabled using --debugger
), pause Nix expression evaluation and enter the REPL. Otherwise, return the argument v
.
catAttrs attr list
Collect each attribute named attr from a list of attribute sets. Attrsets that don’t contain the named attribute are ignored. For example,
builtins .catAttrs "a" [{a = 1 ;} {b = 0 ;} {a = 2 ;}]
evaluates to [1 2]
.
ceil double
Converts an IEEE-754 double-precision floating-point number (double ) to the next higher integer.
If the datatype is neither an integer nor a “float”, an evaluation error will be thrown.
compareVersions s1 s2
Compare two strings representing versions and return -1
if version s1 is older than version s2 , 0
if they are the same, and 1
if s1 is newer than s2 . The version comparison algorithm is the same as the one used by nix-env -u
.
concatLists lists
Concatenate a list of lists into a single list.
concatMap f list
This function is equivalent to builtins.concatLists (map f list)
but is more efficient.
concatStringsSep separator list
Concatenate a list of strings with a separator between each element, e.g. concatStringsSep "/" ["usr" "local" "bin"] == "usr/local/bin"
.
convertHash args
Return the specified representation of a hash string, based on the attributes presented in args :
hash
The hash to be converted. The hash format is detected automatically.
hashAlgo
The algorithm used to create the hash. Must be one of
"md5"
"sha1"
"sha256"
"sha512"
The attribute may be omitted when hash
is an SRI hash or when the hash is prefixed with the hash algorithm name followed by a colon. That <hashAlgo>:<hashBody>
syntax is supported for backwards compatibility with existing tooling.
toHashFormat
The format of the resulting hash. Must be one of
"base16"
"nix32"
"base32"
(deprecated alias for "nix32"
)"base64"
"sri"
The result hash is the toHashFormat representation of the hash hash .
Example
Convert a SHA256 hash in Base16 to SRI:
builtins .convertHash {
hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ;
toHashFormat = "sri" ;
hashAlgo = "sha256" ;
}
"sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
Example
Convert a SHA256 hash in SRI to Base16:
builtins .convertHash {
hash = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" ;
toHashFormat = "base16" ;
}
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
Example
Convert a hash in the form <hashAlgo>:<hashBody>
in Base16 to SRI:
builtins .convertHash {
hash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ;
toHashFormat = "sri" ;
}
"sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
deepSeq e1 e2
This is like seq e1 e2
, except that e1 is evaluated deeply : if it’s a list or set, its elements or attributes are also evaluated recursively.
dirOf s
Return the directory part of the string s , that is, everything before the final slash in the string. This is similar to the GNU dirname
command.
div e1 e2
Return the quotient of the numbers e1 and e2 .
elem x xs
Return true
if a value equal to x occurs in the list xs , and false
otherwise.
elemAt xs n
Return element n from the list xs . Elements are counted starting from 0. A fatal error occurs if the index is out of bounds.
fetchClosure args
Note
This function is only available if the fetch-closure
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = fetch-closure
Fetch a store path closure from a binary cache, and return the store path as a string with context.
This function can be invoked in three ways, that we will discuss in order of preference.
Fetch a content-addressed store path
Example:
builtins .fetchClosure {
fromStore = "https://cache.nixos.org" ;
fromPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33 .1 ;
}
This is the simplest invocation, and it does not require the user of the expression to configure trusted-public-keys
to ensure their authenticity.
If your store path is input addressed instead of content addressed, consider the other two invocations.
Fetch any store path and rewrite it to a fully content-addressed store path
Example:
builtins .fetchClosure {
fromStore = "https://cache.nixos.org" ;
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33 .1 ;
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33 .1 ;
}
This example fetches /nix/store/r2jd...
from the specified binary cache, and rewrites it into the content-addressed store path /nix/store/ldbh...
.
Like the previous example, no extra configuration or privileges are required.
To find out the correct value for toPath
given a fromPath
, use nix store make-content-addressed
:
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
Alternatively, set toPath = ""
and find the correct toPath
in the error message.
Fetch an input-addressed store path as is
Example:
builtins .fetchClosure {
fromStore = "https://cache.nixos.org" ;
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33 .1 ;
inputAddressed = true ;
}
It is possible to fetch an input-addressed store path and return it as is. However, this is the least preferred way of invoking fetchClosure
, because it requires that the input-addressed paths are trusted by the Nix configuration.
builtins.storePath
fetchClosure
is similar to builtins.storePath
in that it allows you to use a previously built store path in a Nix expression. However, fetchClosure
is more reproducible because it specifies a binary cache from which the path can be fetched. Also, using content-addressed store paths does not require users to configure trusted-public-keys
to ensure their authenticity.
fetchGit args
Fetch a path from git. args can be a URL, in which case the HEAD of the repo at that URL is fetched. Otherwise, it can be an attribute with the following attributes (all except url
optional):
url
The URL of the repo.
name
(default: source
)
The name of the directory the repo should be exported to in the store.
rev
(default: the tip of ref
)
The Git revision to fetch. This is typically a commit hash.
ref
(default: HEAD
)
The Git reference under which to look for the requested revision. This is often a branch or tag name.
By default, the ref
value is prefixed with refs/heads/
. As of 2.3.0, Nix will not prefix refs/heads/
if ref
starts with refs/
.
submodules
(default: false
)
A Boolean parameter that specifies whether submodules should be checked out.
exportIgnore
(default: true
)
A Boolean parameter that specifies whether export-ignore
from .gitattributes
should be applied. This approximates part of the git archive
behavior.
Enabling this option is not recommended because it is unknown whether the Git developers commit to the reproducibility of export-ignore
in newer Git versions.
shallow
(default: false
)
Make a shallow clone when fetching the Git tree.
allRefs
Whether to fetch all references of the repository. With this argument being true, it’s possible to load a rev
from any ref
(by default only rev
s from the specified ref
are supported).
verifyCommit
(default: true
if publicKey
or publicKeys
are provided, otherwise false
)
Whether to check rev
for a signature matching publicKey
or publicKeys
. If verifyCommit
is enabled, then fetchGit
cannot use a local repository with uncommitted changes. Requires the verified-fetches
experimental feature .
publicKey
The public key against which rev
is verified if verifyCommit
is enabled. Requires the verified-fetches
experimental feature .
keytype
(default: "ssh-ed25519"
)
The key type of publicKey
. Possible values:
publicKeys
The public keys against which rev
is verified if verifyCommit
is enabled. Must be given as a list of attribute sets with the following form:
{
key = "<public key>" ;
type = "<key type>" ;
}
Requires the verified-fetches
experimental feature .
Here are some examples of how to use fetchGit
.
To fetch a private repository over SSH:
builtins .fetchGit {
url = "[email protected] :my-secret/repository.git" ;
ref = "master" ;
rev = "adab8b916a45068c044658c4158d81878f9ed1c3" ;
}
To fetch an arbitrary reference:
builtins .fetchGit {
url = "https://github.com/NixOS/nix.git" ;
ref = "refs/heads/0.5-release" ;
}
If the revision you’re looking for is in the default branch of the git repository you don’t strictly need to specify the branch name in the ref
attribute.
However, if the revision you’re looking for is in a future branch for the non-default branch you will need to specify the the ref
attribute as well.
builtins .fetchGit {
url = "https://github.com/nixos/nix.git" ;
rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452" ;
ref = "1.11-maintenance" ;
}
Note
It is nice to always specify the branch which a revision belongs to. Without the branch being specified, the fetcher might fail if the default branch changes. Additionally, it can be confusing to try a commit from a non-default branch and see the fetch fail. If the branch is specified the fault is much more obvious.
If the revision you’re looking for is in the default branch of the git repository you may omit the ref
attribute.
builtins .fetchGit {
url = "https://github.com/nixos/nix.git" ;
rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452" ;
}
To fetch a specific tag:
builtins .fetchGit {
url = "https://github.com/nixos/nix.git" ;
ref = "refs/tags/1.9" ;
}
To fetch the latest version of a remote branch:
builtins .fetchGit {
url = "ssh://[email protected] /nixos/nix.git" ;
ref = "master" ;
}
To verify the commit signature:
builtins .fetchGit {
url = "ssh://[email protected] /nixos/nix.git" ;
verifyCommit = true ;
publicKeys = [
{
type = "ssh-ed25519" ;
key = "AAAAC3NzaC1lZDI1NTE5AAAAIArPKULJOid8eS6XETwUjO48/HKBWl7FTCK0Z//fplDi" ;
}
];
}
Nix will refetch the branch according to the tarball-ttl
setting.
This behavior is disabled in pure evaluation mode .
To fetch the content of a checked-out work directory:
builtins .fetchGit ./work-dir
If the URL points to a local directory, and no ref
or rev
is given, fetchGit
will use the current content of the checked-out files, even if they are not committed or added to Git’s index. It will only consider files added to the Git repository, as listed by git ls-files
.
fetchTarball args
Download the specified URL, unpack it and return the path of the unpacked tree. The file must be a tape archive (.tar
) compressed with gzip
, bzip2
or xz
. The top-level path component of the files in the tarball is removed, so it is best if the tarball contains a single directory at top level. The typical use of the function is to obtain external Nix expression dependencies, such as a particular version of Nixpkgs, e.g.
with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-14.12 .tar.gz) {};
stdenv.mkDerivation { … }
The fetched tarball is cached for a certain amount of time (1 hour by default) in ~/.cache/nix/tarballs/
. You can change the cache timeout either on the command line with --tarball-ttl
number-of-seconds or in the Nix configuration file by adding the line tarball-ttl =
number-of-seconds .
Note that when obtaining the hash with nix-prefetch-url
the option --unpack
is required.
This function can also verify the contents against a hash. In that case, the function takes a set instead of a URL. The set requires the attribute url
and the attribute sha256
, e.g.
with import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz" ;
sha256 = "1jppksrfvbk5ypiqdz4cddxdl8z6zyzdb2srq8fcffr327ld5jj2" ;
}) {};
stdenv.mkDerivation { … }
Not available in restricted evaluation mode .
fetchTree input
Note
This function is only available if the fetch-tree
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = fetch-tree
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
the resulting fixed-output store path the corresponding NAR hash backend-specific metadata (currently not documented). input must be an attribute set with the following attributes:
type
(String, required)
One of the supported source types . This determines other required and allowed input attributes.
narHash
(String, optional)
The narHash
parameter can be used to substitute the source of the tree. It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism. If narHash
is set, the source is first looked up is the Nix store and substituters , and only fetched if not available.
A subset of the output attributes of fetchTree
can be re-used for subsequent calls to fetchTree
to produce the same result again. That is, fetchTree
is idempotent.
Downloads are cached in $XDG_CACHE_HOME/nix
. The remote source will be fetched from the network if both are true:
A NAR hash is supplied and the corresponding store path is not valid , that is, not available in the store
Note
Substituters are not used in fetching.
There is no cache entry or the cache entry is older than tarball-ttl
The following source types and associated input attributes are supported.
"file"
Place a plain file into the Nix store. This is similar to builtins.fetchurl
url
(String, required)
Supported protocols:
"tarball"
Download a tar archive and extract it into the Nix store. This has the same underyling implementation as builtins.fetchTarball
url
(String, required)
Example
fetchTree {
type = "tarball" ;
url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11" ;
}
"git"
Fetch a Git tree and copy it to the Nix store. This is similar to builtins.fetchGit
.
url
(String, required)
The URL formats supported are the same as for Git itself.
Example
fetchTree {
type = "git" ;
url = "[email protected] :NixOS/nixpkgs.git" ;
}
Note
If the URL points to a local directory, and no ref
or rev
is given, Nix will only consider files added to the Git index, as listed by git ls-files
but use the current file contents of the Git working directory.
ref
(String, optional)
A Git reference , such as a branch or tag name.
Default: "HEAD"
rev
(String, optional)
A Git revision; a commit hash.
Default: the tip of ref
shallow
(Bool, optional)
Make a shallow clone when fetching the Git tree.
Default: false
submodules
(Bool, optional)
Also fetch submodules if available.
Default: false
allRefs
(Bool, optional)
If set to true
, always fetch the entire repository, even if the latest commit is still in the cache. Otherwise, only the latest commit is fetched if it is not already cached.
Default: false
lastModified
(Integer, optional)
Unix timestamp of the fetched commit.
If set, pass through the value to the output attribute set. Otherwise, generated from the fetched Git tree.
revCount
(Integer, optional)
Number of revisions in the history of the Git repository before the fetched commit.
If set, pass through the value to the output attribute set. Otherwise, generated from the fetched Git tree.
The following input types are still subject to change:
"path"
"github"
"gitlab"
"sourcehut"
"mercurial"
input can also be a URL-like reference . The additional input types and the URL-like syntax requires the flakes
experimental feature to be enabled.
Example
Fetch a GitHub repository using the attribute set representation:
builtins .fetchTree {
type = "github" ;
owner = "NixOS" ;
repo = "nixpkgs" ;
rev = "ae2e6b3958682513d28f7d633734571fb18285dd" ;
}
This evaluates to the following attribute set:
{
lastModified = 1686503798 ;
lastModifiedDate = "20230611171638" ;
narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=" ;
outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source" ;
rev = "ae2e6b3958682513d28f7d633734571fb18285dd" ;
shortRev = "ae2e6b3" ;
}
Example
Fetch the same GitHub repository using the URL-like syntax:
builtins .fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
fetchurl url
Download the specified URL and return the path of the downloaded file.
Not available in restricted evaluation mode .
filter f list
Return a list consisting of the elements of list for which the function f returns true
.
filterSource e1 e2
Warning
filterSource
should not be used to filter store paths. Since filterSource
uses the name of the input directory while naming the output directory, doing so will produce a directory name in the form of <hash2>-<hash>-<name>
, where <hash>-<name>
is the name of the input directory. Since <hash>
depends on the unfiltered directory, the name of the output directory will indirectly depend on files that are filtered out by the function. This will trigger a rebuild even when a filtered out file is changed. Use builtins.path
instead, which allows specifying the name of the output directory.
This function allows you to copy sources into the Nix store while filtering certain files. For instance, suppose that you want to use the directory source-dir
as an input to a Nix expression, e.g.
stdenv.mkDerivation {
...
src = ./source-dir;
}
However, if source-dir
is a Subversion working copy, then all those annoying .svn
subdirectories will also be copied to the store. Worse, the contents of those directories may change a lot, causing lots of spurious rebuilds. With filterSource
you can filter out the .svn
directories:
src = builtins .filterSource
(path: type: type != "directory" || baseNameOf path != ".svn" )
./source-dir;
Thus, the first argument e1 must be a predicate function that is called for each regular file, directory or symlink in the source tree e2 . If the function returns true
, the file is copied to the Nix store, otherwise it is omitted. The function is called with two arguments. The first is the full path of the file. The second is a string that identifies the type of the file, which is either "regular"
, "directory"
, "symlink"
or "unknown"
(for other kinds of files such as device nodes or fifos — but note that those cannot be copied to the Nix store, so if the predicate returns true
for them, the copy will fail). If you exclude a directory, the entire corresponding subtree of e2 will be excluded.
findFile search-path lookup-path
Find lookup-path in search-path .
A search path is represented list of attribute sets with two attributes:
prefix
is a relative path.path
denotes a file system location The exact syntax depends on the command line interface.Examples of search path attribute sets:
The lookup algorithm checks each entry until a match is found, returning a path value of the match:
If lookup-path matches prefix
, then the remainder of lookup-path (the “suffix”) is searched for within the directory denoted by path
. Note that the path
may need to be downloaded at this point to look inside. If the suffix is found inside that directory, then the entry is a match. The combined absolute path of the directory (now downloaded if need be) and the suffix is returned. Lookup path expressions are desugared using this and builtins.nixPath
:
<nixpkgs>
is equivalent to:
builtins .findFile builtins .nixPath "nixpkgs"
flakeRefToString attrs
Note
This function is only available if the flakes
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = flakes
Convert a flake reference from attribute set format to URL format.
For example:
builtins .flakeRefToString {
dir = "lib" ; owner = "NixOS" ; ref = "23.05" ; repo = "nixpkgs" ; type = "github" ;
}
evaluates to
"github:NixOS/nixpkgs/23.05?dir=lib"
floor double
Converts an IEEE-754 double-precision floating-point number (double ) to the next lower integer.
If the datatype is neither an integer nor a “float”, an evaluation error will be thrown.
foldl' op nul list
Reduce a list by applying a binary operator, from left to right, e.g. foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2) ...
.
For example, foldl' (acc: elem: acc + elem) 0 [1 2 3]
evaluates to 6
and foldl' (acc: elem: { "${elem}" = elem; } // acc) {} ["a" "b"]
evaluates to { a = "a"; b = "b"; }
.
The first argument of op
is the accumulator whereas the second argument is the current element being processed. The return value of each application of op
is evaluated immediately, even for intermediate values.
fromJSON e
Convert a JSON string to a Nix value. For example,
builtins .fromJSON ''{"x": [1, 2, 3], "y": null}''
returns the value { x = [ 1 2 3 ]; y = null; }
.
fromTOML e
Convert a TOML string to a Nix value. For example,
builtins .fromTOML ''
x=1
s="a"
[table]
y=2
''
returns the value { s = "a"; table = { y = 2; }; x = 1; }
.
functionArgs f
Return a set containing the names of the formal arguments expected by the function f . The value of each attribute is a Boolean denoting whether the corresponding argument has a default value. For instance, functionArgs ({ x, y ? 123}: ...) = { x = false; y = true; }
.
“Formal argument” here refers to the attributes pattern-matched by the function. Plain lambdas are not included, e.g. functionArgs (x: ...) = { }
.
genList generator length
Generate list of size length , with each element i equal to the value returned by generator i
. For example,
builtins .genList (x: x * x) 5
returns the list [ 0 1 4 9 16 ]
.
genericClosure attrset
builtins.genericClosure
iteratively computes the transitive closure over an arbitrary relation defined by a function.
It takes attrset with two attributes named startSet
and operator
, and returns a list of attrbute sets:
startSet
: The initial list of attribute sets.
operator
: A function that takes an attribute set and returns a list of attribute sets. It defines how each item in the current set is processed and expanded into more items.
Each attribute set in the list startSet
and the list returned by operator
must have an attribute key
, which must support equality comparison. The value of key
can be one of the following types:
The result is produced by calling the operator
on each item
that has not been called yet, including newly added items, until no new items are added. Items are compared by their key
attribute.
Common usages are:
Generating unique collections of items, such as dependency graphs. Traversing through structures that may contain cycles or loops. Processing data structures with complex internal relationships. Example
builtins .genericClosure {
startSet = [ {key = 5 ;} ];
operator = item: [{
key = if (item.key / 2 ) * 2 == item.key
then item.key / 2
else 3 * item.key + 1 ;
}];
}
evaluates to
[ { key = 5 ; } { key = 16 ; } { key = 8 ; } { key = 4 ; } { key = 2 ; } { key = 1 ; } ]
getAttr s set
getAttr
returns the attribute named s from set . Evaluation aborts if the attribute doesn’t exist. This is a dynamic version of the .
operator, since s is an expression rather than an identifier.
getContext s
Return the string context of s .
The string context tracks references to derivations within a string. It is represented as an attribute set of store derivation paths mapping to output names.
Using string interpolation on a derivation will add that derivation to the string context. For example,
builtins .getContext "${derivation { name = "a"; builder = "b"; system = "c"; } }"
evaluates to
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
getEnv s
getEnv
returns the value of the environment variable s , or an empty string if the variable doesn’t exist. This function should be used with care, as it can introduce all sorts of nasty environment dependencies in your Nix expression.
getEnv
is used in Nix Packages to locate the file ~/.nixpkgs/config.nix
, which contains user-local settings for Nix Packages. (That is, it does a getEnv "HOME"
to locate the user’s home directory.)
getFlake args
Note
This function is only available if the flakes
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = flakes
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
(builtins .getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a" ).packages.x86_64-linux.nix
Unless impure evaluation is allowed (--impure
), the flake reference must be “locked”, e.g. contain a Git revision or content hash. An example of an unlocked usage is:
(builtins .getFlake "github:edolstra/dwarffs" ).rev
groupBy f list
Groups elements of list together by the string returned from the function f called on each element. It returns an attribute set where each attribute value contains the elements of list that are mapped to the same corresponding attribute name returned by f .
For example,
builtins .groupBy (builtins .substring 0 1 ) ["foo" "bar" "baz" ]
evaluates to
{ b = [ "bar" "baz" ]; f = [ "foo" ]; }
hasAttr s set
hasAttr
returns true
if set has an attribute named s , and false
otherwise. This is a dynamic version of the ?
operator, since s is an expression rather than an identifier.
hasContext s
Return true
if string s has a non-empty context. The context can be obtained with getContext
.
Example
Many operations require a string context to be empty because they are intended only to work with “regular” strings, and also to help users avoid unintentionally loosing track of string context elements. builtins.hasContext
can help create better domain-specific errors in those case.
name: meta:
if builtins .hasContext name
then throw "package name cannot contain string context"
else { ${name} = meta; }
hashFile type p
Return a base-16 representation of the cryptographic hash of the file at path p . The hash algorithm specified by type must be one of "md5"
, "sha1"
, "sha256"
or "sha512"
.
hashString type s
Return a base-16 representation of the cryptographic hash of string s . The hash algorithm specified by type must be one of "md5"
, "sha1"
, "sha256"
or "sha512"
.
head list
Return the first element of a list; abort evaluation if the argument isn’t a list or is an empty list. You can test whether a list is empty by comparing it with []
.
import path
Load, parse, and return the Nix expression in the file path .
Note
Unlike some languages, import
is a regular function in Nix.
The path argument must meet the same criteria as an interpolated expression .
If path is a directory, the file default.nix
in that directory is used if it exists.
Example
$ echo 123 > default.nix
Import default.nix
from the current directory.
import ./.
123
Evaluation aborts if the file doesn’t exist or contains an invalid Nix expression.
A Nix expression loaded by import
must not contain any free variables , that is, identifiers that are not defined in the Nix expression itself and are not built-in. Therefore, it cannot refer to variables that are in scope at the call site.
Example
If you have a calling expression
rec {
x = 123 ;
y = import ./foo.nix;
}
then the following foo.nix
will give an error:
x + 456
since x
is not in scope in foo.nix
. If you want x
to be available in foo.nix
, pass it as a function argument:
rec {
x = 123 ;
y = import ./foo.nix x;
}
and
x: x + 456
The function argument doesn’t have to be called x
in foo.nix
; any name would work.
intersectAttrs e1 e2
Return a set consisting of the attributes in the set e2 which have the same name as some attribute in e1 .
Performs in O(n log m ) where n is the size of the smaller set and m the larger set’s size.
isAttrs e
Return true
if e evaluates to a set, and false
otherwise.
isBool e
Return true
if e evaluates to a bool, and false
otherwise.
isFloat e
Return true
if e evaluates to a float, and false
otherwise.
isFunction e
Return true
if e evaluates to a function, and false
otherwise.
isInt e
Return true
if e evaluates to an integer, and false
otherwise.
isList e
Return true
if e evaluates to a list, and false
otherwise.
isNull e
Return true
if e evaluates to null
, and false
otherwise.
This is equivalent to e == null
.
isPath e
Return true
if e evaluates to a path, and false
otherwise.
isString e
Return true
if e evaluates to a string, and false
otherwise.
length e
Return the length of the list e .
lessThan e1 e2
Return true
if the number e1 is less than the number e2 , and false
otherwise. Evaluation aborts if either e1 or e2 does not evaluate to a number.
listToAttrs e
Construct a set from a list specifying the names and values of each attribute. Each element of the list should be a set consisting of a string-valued attribute name
specifying the name of the attribute, and an attribute value
specifying its value.
In case of duplicate occurrences of the same name, the first takes precedence.
Example:
builtins .listToAttrs
[ { name = "foo" ; value = 123 ; }
{ name = "bar" ; value = 456 ; }
{ name = "bar" ; value = 420 ; }
]
evaluates to
{ foo = 123 ; bar = 456 ; }
map f list
Apply the function f to each element in the list list . For example,
map (x: "foo" + x) [ "bar" "bla" "abc" ]
evaluates to [ "foobar" "foobla" "fooabc" ]
.
mapAttrs f attrset
Apply function f to every element of attrset . For example,
builtins .mapAttrs (name: value: value * 10 ) { a = 1 ; b = 2 ; }
evaluates to { a = 10; b = 20; }
.
match regex str
Returns a list if the extended POSIX regular expression regex matches str precisely, otherwise returns null
. Each item in the list is a regex group.
builtins .match "ab" "abc"
Evaluates to null
.
builtins .match "abc" "abc"
Evaluates to [ ]
.
builtins .match "a(b)(c)" "abc"
Evaluates to [ "b" "c" ]
.
builtins .match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " FOO "
Evaluates to [ "FOO" ]
.
mul e1 e2
Return the product of the numbers e1 and e2 .
outputOf derivation-reference output-name
Note
This function is only available if the dynamic-derivations
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = dynamic-derivations
Return the output path of a derivation, literally or using a placeholder if needed.
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addresed), the output path will just be returned. But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
derivation reference
must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select drv.outPath
. This primop can be chained arbitrarily deeply. For instance,
builtins .outputOf
(builtins .outputOf myDrv "out" )
"out"
will return a placeholder for the output of the output of myDrv
.
This primop corresponds to the ^
sigil for derivable paths, e.g. as part of installable syntax on the command line.
parseDrvName s
Split the string s into a package name and version. The package name is everything up to but not including the first dash not followed by a letter, and the version is everything following that dash. The result is returned in a set { name, version }
. Thus, builtins.parseDrvName "nix-0.12pre12876"
returns { name = "nix"; version = "0.12pre12876"; }
.
parseFlakeRef flake-ref
Note
This function is only available if the flakes
experimental feature is enabled.
For example, include the following in nix.conf
:
extra-experimental-features = flakes
Parse a flake reference, and return its exploded form.
For example:
builtins .parseFlakeRef "github:NixOS/nixpkgs/23.05?dir=lib"
evaluates to:
{ dir = "lib" ; owner = "NixOS" ; ref = "23.05" ; repo = "nixpkgs" ; type = "github" ; }
partition pred list
Given a predicate function pred , this function returns an attrset containing a list named right
, containing the elements in list for which pred returned true
, and a list named wrong
, containing the elements for which it returned false
. For example,
builtins .partition (x: x > 10 ) [1 23 9 3 42 ]
evaluates to
{ right = [ 23 42 ]; wrong = [ 1 9 3 ]; }
path args
An enrichment of the built-in path type, based on the attributes present in args . All are optional except path
:
path The underlying path.
name The name of the path when added to the store. This can used to reference paths that have nix-illegal characters in their names, like @
.
filter A function of the type expected by builtins.filterSource
, with the same semantics.
recursive When false
, when path
is added to the store it is with a flat hash, rather than a hash of the NAR serialization of the file. Thus, path
must refer to a regular file, not a directory. This allows similar behavior to fetchurl
. Defaults to true
.
sha256 When provided, this is the expected hash of the file at the path. Evaluation will fail if the hash is incorrect, and providing a hash allows builtins.path
to be used even when the pure-eval
nix config option is on.
pathExists path
Return true
if the path path exists at evaluation time, and false
otherwise.
placeholder output
Return a placeholder string for the specified output that will be substituted by the corresponding output path at build time. Typical outputs would be "out"
, "bin"
or "dev"
.
readDir path
Return the contents of the directory path as a set mapping directory entries to the corresponding file type. For instance, if directory A
contains a regular file B
and another directory C
, then builtins.readDir ./A
will return the set
{ B = "regular" ; C = "directory" ; }
The possible values for the file type are "regular"
, "directory"
, "symlink"
and "unknown"
.
readFile path
Return the contents of the file path as a string.
readFileType p
Determine the directory entry type of a filesystem node, being one of “directory”, “regular”, “symlink”, or “unknown”.
removeAttrs set list
Remove the attributes listed in list from set . The attributes don’t have to exist in set . For instance,
removeAttrs { x = 1 ; y = 2 ; z = 3 ; } [ "a" "x" "z" ]
evaluates to { y = 2; }
.
replaceStrings from to s
Given string s , replace every occurrence of the strings in from with the corresponding string in to .
The argument to is lazy, that is, it is only evaluated when its corresponding pattern in from is matched in the string s
Example:
builtins .replaceStrings ["oo" "a" ] ["a" "i" ] "foobar"
evaluates to "fabir"
.
seq e1 e2
Evaluate e1 , then evaluate and return e2 . This ensures that a computation is strict in the value of e1 .
sort comparator list
Return list in sorted order. It repeatedly calls the function comparator with two elements. The comparator should return true
if the first element is less than the second, and false
otherwise. For example,
builtins .sort builtins .lessThan [ 483 249 526 147 42 77 ]
produces the list [ 42 77 147 249 483 526 ]
.
This is a stable sort: it preserves the relative order of elements deemed equal by the comparator.
split regex str
Returns a list composed of non matched strings interleaved with the lists of the extended POSIX regular expression regex matches of str . Each item in the lists of matched sequences is a regex group.
builtins .split "(a)b" "abc"
Evaluates to [ "" [ "a" ] "c" ]
.
builtins .split "([ac])" "abc"
Evaluates to [ "" [ "a" ] "b" [ "c" ] "" ]
.
builtins .split "(a)|(c)" "abc"
Evaluates to [ "" [ "a" null ] "b" [ null "c" ] "" ]
.
builtins .split "([[:upper:]]+)" " FOO "
Evaluates to [ " " [ "FOO" ] " " ]
.
splitVersion s
Split a string representing a version into its components, by the same version splitting logic underlying the version comparison in nix-env -u
.
storePath path
This function allows you to define a dependency on an already existing store path. For example, the derivation attribute src = builtins.storePath /nix/store/f1d18v1y…-source
causes the derivation to depend on the specified path, which must exist or be substitutable. Note that this differs from a plain path (e.g. src = /nix/store/f1d18v1y…-source
) in that the latter causes the path to be copied again to the Nix store, resulting in a new path (e.g. /nix/store/ld01dnzc…-source-source
).
Not available in pure evaluation mode .
See also builtins.fetchClosure
.
stringLength e
Return the length of the string e . If e is not a string, evaluation is aborted.
sub e1 e2
Return the difference between the numbers e1 and e2 .
substring start len s
Return the substring of s from character position start (zero-based) up to but not including start + len . If start is greater than the length of the string, an empty string is returned. If start + len lies beyond the end of the string or len is -1
, only the substring up to the end of the string is returned. start must be non-negative. For example,
builtins .substring 0 3 "nixos"
evaluates to "nix"
.
tail list
Return the list without its first item; abort evaluation if the argument isn’t a list or is an empty list.
Warning
This function should generally be avoided since it’s inefficient: unlike Haskell’s tail
, it takes O(n) time, so recursing over a list by repeatedly calling tail
takes O(n^2) time.
throw s
Throw an error message s . This usually aborts Nix expression evaluation, but in nix-env -qa
and other commands that try to evaluate a set of derivations to get information about those derivations, a derivation that throws an error is silently skipped (which is not the case for abort
).
toFile name s
Store the string s in a file in the Nix store and return its path. The file has suffix name . This file can be used as an input to derivations. One application is to write builders “inline”. For instance, the following Nix expression combines the Nix expression for GNU Hello and its build script into one file:
{ stdenv, fetchurl, perl }:
stdenv.mkDerivation {
name = "hello-2.1.1" ;
builder = builtins .toFile "builder.sh" "
source $stdenv/setup
PATH=$perl/bin:$PATH
tar xvfz $src
cd hello-*
./configure --prefix=$out
make
make install
" ;
src = fetchurl {
url = "http://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz" ;
sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465" ;
};
inherit perl;
}
It is even possible for one file to refer to another, e.g.,
builder = let
configFile = builtins .toFile "foo.conf" "
# This is some dummy configuration file.
...
" ;
in builtins .toFile "builder.sh" "
source $stdenv/setup
...
cp ${configFile} $out/etc/foo.conf
" ;
Note that ${configFile}
is a string interpolation , so the result of the expression configFile
(i.e., a path like /nix/store/m7p7jfny445k...-foo.conf
) will be spliced into the resulting string.
It is however not allowed to have files mutually referring to each other, like so:
let
foo = builtins .toFile "foo" "...${bar} ..." ;
bar = builtins .toFile "bar" "...${foo} ..." ;
in foo
This is not allowed because it would cause a cyclic dependency in the computation of the cryptographic hashes for foo
and bar
.
It is also not possible to reference the result of a derivation. If you are using Nixpkgs, the writeTextFile
function is able to do that.
toJSON e
Return a string containing a JSON representation of e . Strings, integers, floats, booleans, nulls and lists are mapped to their JSON equivalents. Sets (except derivations) are represented as objects. Derivations are translated to a JSON string containing the derivation’s output path. Paths are copied to the store and represented as a JSON string of the resulting store path.
toPath s
DEPRECATED. Use /. + "/path"
to convert a string into an absolute path. For relative paths, use ./. + "/path"
.
toString e
Convert the expression e to a string. e can be:
A string (in which case the string is returned unmodified).
A path (e.g., toString /foo/bar
yields "/foo/bar"
.
A set containing { __toString = self: ...; }
or { outPath = ...; }
.
An integer.
A list, in which case the string representations of its elements are joined with spaces.
A Boolean (false
yields ""
, true
yields "1"
).
null
, which yields the empty string.
toXML e
Return a string containing an XML representation of e . The main application for toXML
is to communicate information with the builder in a more structured format than plain environment variables.
Here is an example where this is the case:
{ stdenv, fetchurl, libxslt, jira, uberwiki }:
stdenv.mkDerivation (rec {
name = "web-server" ;
buildInputs = [ libxslt ];
builder = builtins .toFile "builder.sh" "
source $stdenv/setup
mkdir $out
echo " $servlets" | xsltproc ${stylesheet} - > $out/server-conf.xml ①
" ;
stylesheet = builtins .toFile "stylesheet.xsl" ②
"<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:template match='/'>
<Configure>
<xsl:for-each select='/expr/list/attrs'>
<Call name='addWebApplication'>
<Arg><xsl:value-of select=\" attr[@name = 'path']/string/@value\" /></Arg>
<Arg><xsl:value-of select=\" attr[@name = 'war']/path/@value\" /></Arg>
</Call>
</xsl:for-each>
</Configure>
</xsl:template>
</xsl:stylesheet>
" ;
servlets = builtins .toXML [ ③
{ path = "/bugtracker" ; war = jira + "/lib/atlassian-jira.war" ; }
{ path = "/wiki" ; war = uberwiki + "/uberwiki.war" ; }
];
})
The builder is supposed to generate the configuration file for a Jetty servlet container . A servlet container contains a number of servlets (*.war
files) each exported under a specific URI prefix. So the servlet configuration is a list of sets containing the path
and war
of the servlet (①). This kind of information is difficult to communicate with the normal method of passing information through an environment variable, which just concatenates everything together into a string (which might just work in this case, but wouldn’t work if fields are optional or contain lists themselves). Instead the Nix expression is converted to an XML representation with toXML
, which is unambiguous and can easily be processed with the appropriate tools. For instance, in the example an XSLT stylesheet (at point ②) is applied to it (at point ①) to generate the XML configuration file for the Jetty server. The XML representation produced at point ③ by toXML
is as follows:
<?xml version='1.0' encoding='utf-8'?>
<expr >
<list >
<attrs >
<attr name ="path" >
<string value ="/bugtracker" />
</attr >
<attr name ="war" >
<path value ="/nix/store/d1jh9pasa7k2...-jira/lib/atlassian-jira.war" />
</attr >
</attrs >
<attrs >
<attr name ="path" >
<string value ="/wiki" />
</attr >
<attr name ="war" >
<path value ="/nix/store/y6423b1yi4sx...-uberwiki/uberwiki.war" />
</attr >
</attrs >
</list >
</expr >
Note that we used the toFile
built-in to write the builder and the stylesheet “inline” in the Nix expression. The path of the stylesheet is spliced into the builder using the syntax xsltproc ${stylesheet}
.
trace e1 e2
Evaluate e1 and print its abstract syntax representation on standard error. Then return e2 . This function is useful for debugging.
If the debugger-on-trace
option is set to true
and the --debugger
flag is given, the interactive debugger will be started when trace
is called (like break
).
traceVerbose e1 e2
Evaluate e1 and print its abstract syntax representation on standard error if --trace-verbose
is enabled. Then return e2 . This function is useful for debugging.
tryEval e
Try to shallowly evaluate e . Return a set containing the attributes success
(true
if e evaluated successfully, false
if an error was thrown) and value
, equalling e if successful and false
otherwise. tryEval
will only prevent errors created by throw
or assert
from being thrown. Errors tryEval
will not catch are for example those created by abort
and type errors generated by builtins. Also note that this doesn’t evaluate e deeply, so let e = { x = throw ""; }; in (builtins.tryEval e).success
will be true
. Using builtins.deepSeq
one can get the expected result: let e = { x = throw ""; }; in (builtins.tryEval (builtins.deepSeq e e)).success
will be false
.
typeOf e
Return a string representing the type of the value e , namely "int"
, "bool"
, "string"
, "path"
, "null"
, "set"
, "list"
, "lambda"
or "float"
.
unsafeDiscardOutputDependency s
Create a copy of the given string where every “derivation deep” string context element is turned into a constant string context element.
This is the opposite of builtins.addDrvOutputDependencies
.
This is unsafe because it allows us to “forget” store objects we would have otherwise refered to with the string context, whereas Nix normally tracks all dependencies consistently. Safe operations “grow” but never “shrink” string contexts. builtins.addDrvOutputDependencies
in contrast is safe because “derivation deep” string context element always refers to the underlying derivation (among many more things). Replacing a constant string context element with a “derivation deep” element is a safe operation that just enlargens the string context without forgetting anything.
zipAttrsWith f list
Transpose a list of attribute sets into an attribute set of lists, then apply mapAttrs
.
f
receives two arguments: the attribute name and a non-empty list of all values encountered for that attribute name.
The result is an attribute set where the attribute names are the union of the attribute names in each element of list
. The attribute values are the return values of f
.
builtins .zipAttrsWith
(name: values: { inherit name values; })
[ { a = "x" ; } { a = "y" ; b = "z" ; } ]
evaluates to
{
a = { name = "a"; values = [ "x" "y" ]; };
b = { name = "b"; values = [ "z" ]; };
}
Advantages & Disadvantages of NixOS Advantages of NixOSDeclarative Configuration, OS as Code NixOS uses declarative configuration to manage the entire system environment. These configurations can be managed directly with Git, allowing the system to be restored to any historical state as long as the configuration files are preserved (provided the desired states are declared in the Nix configuration). Nix Flakes further enhance reproducibility by utilizing a flake.lock
version lock file, which records the data source addresses, hash values, and other relevant information for all dependencies. This design greatly improves Nix’s reproducibility and ensures consistent build results. It draws inspiration from package management designs in programming languages like Cargo and npm. Highly Convenient System Customization Capability With just a few configuration changes, various components of the system can be easily replaced. Nix encapsulates all the underlying complex operations within Nix packages, providing users with a concise set of declarative parameters. Modifications are safe and switching between different desktop environments (such as GNOME, KDE, i3, and sway) is straightforward, with minimal pitfalls. Rollback Capability It is possible to roll back to any previous system state, and NixOS even includes all old versions in the boot options by default, ensuring the ability to easily revert changes. Consequently, Nix is regarded as one of the most stable package management approaches. No Dependency Conflict Issues Each software package in Nix has a unique hash, which is incorporated into its installation path, allowing multiple versions to coexist. The community is active, with a diverse range of third-party projects The official package repository, nixpkgs, has numerous contributors, and many people share their Nix configurations. Exploring the NixOS ecosystem is an exciting experience, akin to discovering a new continent. All historical versions are listed in the boot options of NixOS. Image from NixOS Discourse – 10074 Disadvantages of NixOSHigh Learning Curve :Achieving complete reproducibility and avoiding pitfalls associated with improper usage requires learning about Nix’s entire design and managing the system declaratively, rather than blindly using commands like nix-env -i
(similar to apt-get install
). Disorganized Documentation :Currently, Nix Flakes remains an experimental feature, and there is limited documentation specifically focused on it. Most Nix community documentation primarily covers the classic /etc/nixos/configuration.nix
. If you want to start learning directly from Nix Flakes(flake.nix
), you need to refer to a significant amount of outdated documentation and extract the relevant information. Additionally, some core features of Nix, such as imports
and the Nixpkgs Module System, lack detailed official documentation, requiring resorting to source code analysis. Increased Disk Space Usage :To ensure the ability to roll back the system at any time, Nix retains all historical environments by default, resulting in increased disk space usage. While this additional space usage may not be a concern on desktop computers, it can become problematic on resource-constrained cloud servers. Obscure Error Messages :Due to the complex merging algorithm of the Nixpkgs module system , NixOS error messages are quite poor. In many cases, regardless of whether you add --show-trace
, it will only tell you that there is an error in the code (the most common and confusing error message is Infinite recursion encountered ), but where exactly is the error? The type system says it doesn’t know, so you have to find it yourself. In my experience, the simplest and most effective way to deal with these meaningless error messages is to use a “binary search” to gradually restore the code . This problem is probably the biggest pain point of NixOS at the moment. More Complex Underlying Implementation :Nix’s declarative abstraction introduces additional complexity in the underlying code compared to similar code in traditional imperative tools. This complexity increases implementation difficulty and makes it more challenging to make custom modifications at the lower level. However, this burden primarily falls on Nix package maintainers, as regular users have limited exposure to the underlying complexities, reducing their burden. SummaryOverall, I believe that NixOS is suitable for developers with a certain level of Linux usage experience and programming knowledge who desire greater control over their systems.
I do not recommend newcomers without any Linux usage experience to dive directly into NixOS, as it may lead to a frustrating journey.
NixOS… Yet another distro that uses a different package manager? 🤯
Well, NixOS is one of the advanced Linux distros .
So if I’m writing this, I must have a rock-solid reason, right? Well, there are plenty!
I’ve been using it for 3 months, and it is so good that I’m considering switching from my all-time favorite Pop!_OS to NixOS .
And in this guide, I will share the key features of NixOS, making it stand out from the stack of Linux distros.
Fret not; before we move on, let me tell you what NixOS is:
It is a Linux distribution that uses the Nix package manager at its core to save you the trouble from setting it up on a different Linux distribution and letting you make the most out of Nix. Built by the same team that developed Nix. 6 Reasons to Use NixOSNixOS is an interesting independent Linux distro built from scratch.
Everyone can learn a lot by using NixOS, but in my opinion , if you are a developer or a computer science student, NixOS should fit perfectly.
Let me tell you why.
1. Does Not Break Easily / Easy to RecoverBy its core, NixOS is built to last long.
This does not mean that it ships with years-old packages like Debian does for stability but it follows a different approach.
To understand how NixOS is so stable, let’s talk about how users generally break their system, i.e, “dependency issues or package conflicts “
You will generally face a significant system crash when updating your system or installing a new package. Mainly because your package manager could not satisfy the dependency or the installed package conflicts with the existing system.
And NixOS has a very smart way of handling this issue.
See, whenever you upgrade your system or install a package, the system state is rebuilt , termed as a “new generation ” over the current.
So if you face any trouble using the new package or update, you can always roll back to the old generation, where you will find the previous state of the system.
Even if the system is inaccessible, you will find the previous generations available at boot time.
2. ReproducibilityWith one config file, you can create a replica of your current environment for other physical systems.
To benefit from this feature, you can use the Nix config file for installation and configuration purposes.
Once you have the config file that fits your purpose, send that file to the fresh install and replace the default config file with yours. It is that easy!
Rebuild the config, upgrade the system, and make a switch by the given command:
sudo nixos-rebuild switch --upgrade
And you will have the exact development environment that you had on your main machine replicated in a few minutes.
3. Easy RollbacksWhile you already may have got the idea from NixOS’s feature of “Generations “.
There’s more to it. 🕵️
NixOS heavily relies on symlinks (for good). If it is a new concept to you, refer to the guide below:
How to Create Symbolic Links in Linux [Complete Guide]
This detailed tutorial tells you what are symbolic links, how to create a symbolic links and other important things associated with symlinks.
Typically, with other Linux distros, when you upgrade a package, the new package replaces the old one.
But that’s not the case with NixOS.
In NixOS, packages are isolated and stored inside a unique directory, and that is where the use of symbolic links comes in.
Whenever you upgrade a package, the NixOS will tune the symbolic link to locate the new package but won’t remove the old one .
So if you face conflicts with the new package, just switch to the old generations, and symbolic links help locate the old version of a package. 😌
4. Nix package managerThe Nix package manager allows you to access more than 80,000 packages! Not just limited to Linux; it also works on macOS, WSL2, Docker, and more platforms.
And the availability of packages is similar to or even better than AUR as you should find almost everything (I mean it) on the Nix package manager.
For instance, I wanted to install the Librewolf browser, which is not available in the default repository of most Linux distros.
But Nix had it! This means you can rely on the Nix package manager for almost every package.
It is also relatively easy to understand if you have prior Linux experience. In a nutshell, the Nix package manager is impressive!
5. Use multiple versions of the same packageThis can be crucial for developers, where some applications demand the old version of a specific dependency, whereas some require the latest one.
And as I mentioned earlier, nix installs packages to a specific sub-directory, and every package is isolated so one won’t interfere with another!
Before using Nix, I used VMs and containers to meet different dependencies for the same package, especially with PHP, but NixOS did wonders for my workflow.
Suggested Read 📖
Downgrading a Package via apt-get in Ubuntu and Debian
Yes! That’s totally possible. You can downgrade a recently updated package using the apt command in Ubuntu and Debian based distros. Here’s how to do that.
6. Ability to test packages without installationYou can use the nix-shell , which will temporarily modify the $PATH environment variable and be used to test a package temporarily.
And there is no limitation. You are allowed to test every package that is available for installation!
How often do you find yourself in a situation where something builds and works on your machine, but doesn’t build on CI or fails catastrophically in production?
In many cases, those problems are a symptom of something most developers face: lack of reproducibility. Your code depends on an environment variable at compile time, or significantly changes behavior with different versions of a dependency.
Such problems are usually hard to diagnose and even harder to fix. However, there is a tool that can help you solve those issues – Nix.
Nix consists of a package manager and a language to describe packages for the said manager. It features reproducible builds, cross-distro and -platform compatibility, binary caching, as well as a large collection of packages maintained by thousands of contributors.
Nix: how it works
Nix consists of two parts: a package manager and a language. Nix programming language is a rather simple lazy (almost) pure functional with dynamic typing that specializes in building packages. The package manager, on the other hand, is interesting and pretty unique. It all starts with one idea.
FHS is not suitable for reproducible builds
Nix stems from the idea that FHS is fundamentally incompatible with reproducibility . Let me explain.
Every time you see a path like /bin/python
or /lib/libudev.so
, there are a lot of things that you don’t know about the file that’s located there.
What’s the version of the package it came from? What are the libraries it uses? What configure flags were enabled during the build? Answers to these questions can (and most likely will) change the behaviour of an application that uses those files. There are ways to get around this in FHS – for example, link directly to /lib/libudev.so.1.6.3
or use /bin/python3.7
in your shebang. However, there are still a lot of unknowns.
This means that if we want to get any reproducibility and consistency, FHS does not work since there is no way to infer a lot of properties of a given file .
One solution is tools like Docker, Snap, and Flatpak that create isolated FHS environments containing fixed versions of all the dependencies of a given application, and distribute those environments. However, this solution has a host of problems.
What if we want to apply different configure flags to our application or change one of the dependencies? There is no guarantee that you would be able to get the build artifact from build instructions, since putting all the build artifacts in an isolated container guarantees consistency, not reproducibility, because during build-time, tools from host’s FHS are often used, and besides the dependencies that come from other isolated environments might change.
For example, two people using the same docker image will always get the same results, but two people building the same Dockerfile can (and often do) end up with two different images.
This makes one wonder: why not isolate the build itself , similarly to build artifacts?
What does Nix actually do (as a package manager)?
For every package that Nix builds, it first computes its derivation (this is usually done by evaluating expressions written in Nix language), a file that contains:
mentions of all the files and other packages that will be required during the build build instructions for actually building the package, some metainformation about the package, most crucially, a store path (prefix) under which the package will be installed, which is of the form /nix/store/<hash>-<name>-<version>
(hence the name store path), where hash
is a hash of all the other data in the derivation. For example, here’s a really simple derivation for GNU hello
in JSON format with some (empty) fields omitted for brevity:
{
"/nix/store/sg3sw1zdddfkl3hk639asml56xsxw8pf-hello-2.10.drv" : {
"outputs" : {
"out" : {
"path" : "/nix/store/dvv4irwgdm8lpbhdkqghvmjmjknrikh4-hello-2.10"
}
} ,
"inputSrcs" : [
"/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"
] ,
"inputDrvs" : {
"/nix/store/8pq31sp946581sbh2m18pb8iwp0bwxj6-stdenv-linux.drv" : [
"out"
] ,
"/nix/store/cni8m2cjshnc8fbanwrxagan6f8lxjf6-hello-2.10.tar.gz.drv" : [
"out"
] ,
"/nix/store/md39vwk6mmi64f6z6z9cnnjksvv6xkf3-bash-4.4-p23.drv" : [
"out"
]
} ,
"platform" : "x86_64-linux" ,
"builder" : "/nix/store/kgp3vq8l9yb8mzghbw83kyr3f26yqvsz-bash-4.4-p23/bin/bash" ,
"args" : [
"-e" ,
"/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"
] ,
"env" : {
"buildInputs" : "" ,
"builder" : "/nix/store/kgp3vq8l9yb8mzghbw83kyr3f26yqvsz-bash-4.4-p23/bin/bash" ,
"doCheck" : "1" ,
"name" : "hello-2.10" ,
"nativeBuildInputs" : "" ,
"out" : "/nix/store/dvv4irwgdm8lpbhdkqghvmjmjknrikh4-hello-2.10" ,
"outputs" : "out" ,
"pname" : "hello" ,
"src" : "/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz" ,
"stdenv" : "/nix/store/hn7xq448b49d40zq0xs6lq538qvldls1-stdenv-linux" ,
"system" : "x86_64-linux" ,
"version" : "2.10"
}
}
}
Then, it realises that derivation by running build instructions specified in it inside an isolated environment containing the dependencies and only the dependencies of the package.
This way, Nix can guarantee some really important properties:
Realising the same derivation will always get almost the same output since the build commands only have access to the explicitly specified dependencies. (The output can differ if the build instruction uses some hardware-dependent information, such as current time, /dev/urandom
, or CPU performance) The same store path (/nix/store/<hash>-<name>-<version>
) will always contain the result of exactly the same commands, since <hash>
depends on all the variables that are at play while building said package. But what do these guarantees give us?
Benefits of Nix
Reproducibility
This is the most obvious benefit, as it is the whole motivation behind Nix. Two people building the same package will always get the same output (see the note above for situations where the output can differ slightly ) if you’re careful enough with pinning the versions of inputs in place. And even if some input is different, it will be very clear since the store path will change.
Binary caching
Another benefit we get from Nix is binary caching. Since the store path is known before building it and we are sure that the same store path will contain the same output, we can substitute (in other words, fetch) that store path from some other location as long as that location has that path and we trust the person building it (the outputs can be signed after building so that one can store them in an untrusted place).
Multiple versions of any package can be installed simultaneously
Every package is installed under its own prefix, so there are no collisions (unless a given package depends on multiple versions of another package, which is a rare occasion and is usually easy enough to handle). This can be really handy during development – think Python’s virtualenvs but for any language!
Distributed building
Since we know exactly what’s needed to realise a given derivation, it can be easily done remotely as long as the remote server has all the dependencies already (or can build them faster than the local machine).
Non-privileged builds
The build is isolated and it can’t alter anything on the host system other than its output. This means that it’s safe to allow non-privileged users to realise derivations.
Less state = smaller backups
Since the application and its dependencies can now be easily rebuilt from just the derivations, we can safely omit them from backups.
Ecosystem
These benefits are cool, however, the concepts of Nix can be extended further. So far, we only described how to perform reproducible builds and have said nothing about another property that is a very important prerequisite for the healthy sleep of all the DevOps team – runtime consistency. How can we be sure that the application will have exactly the same configuration files and Linux kernel version every time we deploy it on the server?
NixOS
NixOS is a GNU/Linux distribution that uses Nix as both a package manager and a configuration manager. Every single configuration file is described as a derivation, and your system is a derivation that depends on all the applications and config files you’ve specified. Hence, we get all the benefits of Nix applied to our runtime environment. If two people install a NixOS system that has the same store path, they will always get exactly the same system on their computers!
Additionally, because nothing is ever installed the way it usually is on other distros, all the updates and rollbacks are atomic. You have specified an incorrect configuration file for your init system and your PC doesn’t boot anymore? Worry not, every single derivation that was installed on your PC (and wasn’t garbage collected ) is listed in the boot loader’s listing, and you can boot directly into it.
Another benefit of NixOS is that it’s really easy to spin up a new server since the only thing you need for that is the original Nix expressions you’ve used to build your old server.
Nixpkgs
nixpkgs
a giant collection of package descriptions in the Nix language that can be easily altered and combined together. It contains a lot of language-specific package sets, including a complete and up-to-date mirror of Hackage. It also features powerful cross-compilation tools. For example, cross-compiling bash to Windows is as simple as nix build nixpkgs.pkgsCross.mingwW64.bash
.
nixpkgs
makes Nix a very powerful tool to build your application, especially if you need to target multiple platforms and use many languages.
Deep integration with many existing tools and services
nixpkgs
and NixOS also contain a lot of integrations with existing tools.
Need to build a Docker image with your application? Easy:
dockerTools.buildImage {
name = "helloworld" ;
contents = [ hello ];
config.Entrypoint = "hello" ;
}
Need to quickly spin up nginx
without writing a ton of configs? Literally two lines:
services.nginx.enable = true ;
services.nginx.virtualHosts."example.com" .webRoot = "/var/lib/example.com" ;
Want to declaratively configure vim
? That’s the spirit:
vim_configurable.customize {
name = "vim-with-plugins" ;
vimrcConfig.customRC = ''
set hidden
set colorcolumn=80
'' ;
vimrcConfig.vam.pluginDictionaries = [
{ name = "youcompleteme" ; }
{ name = "phpCompletion" ; ft_regex = "^php\$" ; }
{ name = "phpCompletion" ; filename_regex = "^.php\$" ; }
{ name = "phpCompletion" ; tag = "lazy" ; }
];
}
Path forward
If you want to try Nix out, there are multiple ways to approach it. You can download NixOS and play with it on a virtual machine; alternatively, you can download Nix and use it in the OS of your choice. To do so, visit https://nixos.org/download.html .
There are many materials for learning, including the famous Nix Pills , Nix Cookbook , and various manuals.