Nix basic concepts and patterns
Here are some common patterns and concepts you'll often encounter in Nix:
-
Attribute Sets: These are similar to dictionaries or maps in other languages. They are a key-value store, where the keys are strings and the values can be of any type.
{ packageName = "example"; version = "1.2.3"; dependencies = [ "dep1" "dep2" ]; } -
Functions: Functions are first-class citizens in Nix. They are often used to parameterize package descriptions.
{ stdenv, fetchurl }: stdenv.mkDerivation { name = "example-1.2.3"; src = fetchurl { url = "http://example.com/example-1.2.3.tar.gz"; sha256 = "0l6m5..."; }; } -
Derivations: The most important concept in Nix is the derivation. A derivation describes everything needed to build a package (source, dependencies, build script, etc.).
derivation { name = "example-1.2.3"; builder = "${bash}/bin/bash"; args = [ ./builder.sh ]; env = { source = fetchurl { url = "http://example.com/example-1.2.3.tar.gz"; sha256 = "0l6m5..."; }; }; } -
String Interpolation: Nix supports string interpolation, making it easy to compose strings.
let name = "example"; version = "1.2.3"; in "${name}-${version}" -
Let-In Blocks: Used for defining local variables. The
letblock allows you to define a set of local variables that can be used in the expression following thein.let x = 10; y = 20; in x + y # 30 -
Importing Other Nix Files: Nix allows modularization by letting you import other Nix files.
let myPackage = import ./my-package.nix; in myPackage -
Conditional Expressions: Nix supports
if...then...elseexpressions for conditional logic.if lib.versionAtLeast lib.version "2.0" then { enableFeatureX = true; } else { enableFeatureX = false; } -
Lists: Just like in other languages, Nix supports lists. They are often used for dependencies.
[ "pkg1" "pkg2" "pkg3" ] -
Overrides and Customization: One powerful feature of Nix is the ability to override or customize packages. This is often done using the
overrideAttrsfunction.pkgs.hello.overrideAttrs (oldAttrs: { buildInputs = oldAttrs.buildInputs ++ [ pkgs.gnutls ]; })This example takes the existing
hellopackage and addsgnutlsto itsbuildInputs. -
Builtins and Language Constructs: Nix has several built-in functions and language constructs that are frequently used.
builtins.fetchGit: Fetches sources from a Git repository.builtins.readFile: Reads the contents of a file into a string.
let version = builtins.readFile ./version.txt; in "Package version: ${version}" -
Lazy Evaluation: Nix employs lazy evaluation, meaning expressions are only evaluated when they are needed. Keep that in mind, since your code may not execute when you expect it to. (Particularly when debugging)
-
Path Expressions: Nix has a unique way of handling file paths, using the
/operator to construct paths in a manner that is aware of the Nix store.let src = ././source; in "${src}/subdir/file" -
Platform Specifics: Nix allows you to specify platform-specific dependencies or configurations using conditional expressions.
{ stdenv }: stdenv.mkDerivation { name = "example"; buildInputs = stdenv.lib.optionals stdenv.isLinux [ pkgs.glibc ] ++ stdenv.lib.optionals stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.CoreFoundation ]; } -
Using
withStatement: Thewithstatement allows for importing all attributes from a given set into the local scope, reducing the need for prefixing them.
with pkgs;
let
myPackage = stdenv.mkDerivation {
name = "my-package";
buildInputs = [ git cmake gcc ];
};
in
myPackage
- Conditional Dependencies with
lib.optionals: You can conditionally include dependencies based on certain conditions usinglib.optionals.
{ lib, stdenv, fetchurl, openssl, libevent }:
stdenv.mkDerivation {
name = "example";
src = fetchurl {
url = "http://example.com/example.tar.gz";
sha256 = "0l6m5...";
};
buildInputs = [ openssl libevent ]
++ lib.optionals stdenv.isLinux [ pkgs.glibc ];
}
- Dynamic Attribute Set with
mapAttrs: ThemapAttrsfunction is used to transform each attribute in a set, which is useful for dynamically creating attribute sets.
let
inputSet = { a = 1; b = 2; c = 3; };
outputSet = lib.mapAttrs (name: value: value * 2) inputSet;
in
outputSet # { a = 2; b = 4; c = 6; }
- Environment Variables in Derivations: You can set environment variables in package derivations, which are used during the build process.
stdenv.mkDerivation {
name = "example";
buildInputs = [ openssl ];
preBuild = ''
export OPENSSL_DIR=${openssl.dev}
'';
}