Nix - Die Sprache

Das Editieren von shell.nix oder flake.nix Dateien kann furchteinflössend sein, wenn man mit der Syntax von “Nix” nicht vertraut ist. Deshalb nun ein Versuch, die Sprache etwas näher zu Beleuchten.

Publiziert von Patrik Stutz am 06.03.2023

Nix - Die Sprache - ist eigentlich ziemlich simpel. Man könnte schon fast sagen, es handelt sich dabei um JSON mit Funktionen.

Hier ein Beispiel der Basis-Datentypen und Konstrukte:

rec {  # "rec" erlaubt uns hier, dass wir innerhalb des objects auf properties verweisen können
    aBoolean = true;
    aNumber = 1;
    aString = "1";
    aMultilineString = '' Der whitespace wird bei allen Zeilen getrimmt
        a
        b
        c
    '';
    anArray = [
        aBoolean
        aNumber
        aString
    ];
    anObject = {
        a = 1;
        b = 2;
        c = anArray;
    };
    aConditional = if aBoolean then anObject else anArray; 
    letIn = let
        foo = 1;
        bar = 2;
    in foo+bar;
}

/*

nix-repl> (import ./datatypes.nix).anObject       
{ a = 1; b = 2; c = [ ... ]; }

nix-repl> (import ./datatypes.nix).anArray 
[ true 1 "1" ]

nix-repl> (import ./datatypes.nix).aMultilineString
"a\nb\nc\n"

nix-repl> (import ./datatypes.nix).aConditional    
{ a = 1; b = 2; c = [ ... ]; }

nix-repl> (import ./datatypes.nix).aConditional    
{ a = 1; b = 2; c = [ ... ]; }

nix-repl> (import ./datatypes.nix).letIn
3

*/

Und so sehen Funktionen aus:

rec {
		# Funktionen haben immer nur ein Argument
    sayHello = name: "hello "+name;

		# Braucht man mehrere, fasst man diese zu einem Objekt zusammen
    plus = {a,b}: a+b;

		# Oder gibt eine neue Funktion zurück, der man ein weiteres Argument übergeben kann (currying)
    plusCurried = a: b: a+b;

		# Es gibt auch eine handvoll eingabaute funktionen im "builtins" objekt
    longerThan4Characters = input: (builtins.stringLength input) > 4; # https://nixos.org/manual/nix/stable/language/builtins.html#builtins-stringLength
}

/*

nix-repl> (import ./functions.nix).sayHello "patrik" 
"hello patrik"

nix-repl> (import ./functions.nix).plus {a = 1; b = 3;}
4

nix-repl> (import ./functions.nix).plusCurried 1 3
4

nix-repl> (import ./functions.nix).longerThan4Characters "hello world"
true

*/

2-Phasen

Nix-Builds durchlaufen immer 2 Phasen:

  1. Nix-Dateien und die Expressions darin evaluieren zu sogenannten “Derivations”, dies sind statische Daten-Konstrukte, die alle Informationen beinhalten, um ein “Paket” zu builden.
  2. Das Paket mit diesen Informationen in einer Sandbox ohne Internet- oder Dateisystem-Zugriff builden. In diesem Schritt wird die Nix-Sprache nicht mehr verwendet. Man könnte diese “Derivation” konstrukte theoretisch also auch mit einer anderen Sprache generieren und dann builden lassen.

Ein Beispiel:

let
    nixpkgs = import <nixpkgs> {};
in nixpkgs.stdenv.mkDerivation {
    name = "say-hello";
    src = ./say-hello;
    nativeBuildInputs = [
        nixpkgs.nodePackages.typescript
    ];
    buildInputs = [
        nixpkgs.nodejs-18_x
    ];
    buildPhase = ''
        tsc --out say-hello.js $src/index.ts
    '';
    installPhase = ''
        mkdir -p $out/bin
        cp say-hello.js $out/bin/say-hello2
    '';
}

evaluiert zu

Derive(
	[
		("out","/nix/store/0l5g8w1wmk7j3q6z7ldkfvcp2xfbpr2w-say-hello","","")
	],
	[
		("/nix/store/182qlb4yhgwcalpfixj54p9avgdyyk48-typescript-4.9.4.drv",["out"]),
		("/nix/store/66c26vpjxagdr5dirdcg27k0jgakq51k-stdenv-linux.drv",["out"]),
		("/nix/store/vqs99ic7qjkjplb7r4qvja3l276avqzq-nodejs-18.13.0.drv",["out"]),
		("/nix/store/wx04vp65xza0916zv3w1xprjc92pa7f8-bash-5.2-p15.drv",["out"])
	],
	[
		"/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh",
		"/nix/store/707bbyri4cypp9lrdc7j4kwflncd9xgy-say-hello"
	],
	"x86_64-linux",
	"/nix/store/qqa28hmysc23yy081d178jfd9a1yk8aw-bash-5.2-p15/bin/bash",
	[
		"-e",
		"/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh"
	],
	[
		("__structuredAttrs",""),
		("buildInputs","/nix/store/xl6461kig4j53p1cm0b3j9nm4l373g6a-nodejs-18.13.0"),
		("buildPhase","tsc --out say-hello.js $src/index.ts\n"),
		("builder","/nix/store/qqa28hmysc23yy081d178jfd9a1yk8aw-bash-5.2-p15/bin/bash"),
		("cmakeFlags",""),
		("configureFlags",""),
		("depsBuildBuild",""),
		("depsBuildBuildPropagated",""),
		("depsBuildTarget",""),
		("depsBuildTargetPropagated",""),
		("depsHostHost",""),
		("depsHostHostPropagated",""),
		("depsTargetTarget",""),
		("depsTargetTargetPropagated",""),
		("doCheck",""),
		("doInstallCheck",""),
		("installPhase","mkdir -p $out/bin\ncp say-hello.js $out/bin/say-hello2\n"),
		("mesonFlags",""),
		("name","say-hello"),
		("nativeBuildInputs","/nix/store/v678yfnj4c0bw8rcy01sf7rgi9j5n29d-typescript-4.9.4"),
		("out","/nix/store/0l5g8w1wmk7j3q6z7ldkfvcp2xfbpr2w-say-hello"),
		("outputs","out"),
		("patches",""),
		("propagatedBuildInputs",""),
		("propagatedNativeBuildInputs",""),
		("src","/nix/store/707bbyri4cypp9lrdc7j4kwflncd9xgy-say-hello"),
		("stdenv","/nix/store/3yfs41f4b60jya2gk6xikx4s97zsxjr0-stdenv-linux"),
		("strictDeps",""),
		("system","x86_64-linux")
	]
)