@@ -3,8 +3,8 @@ title: Nix
33subtitle: Язык, пакетный менеджер и экосистема
44author: Александр Бантьев @balsoft, typeable.io
55description: |
6− Вольный пересказ nix-pills на русском языке.
7− https://nixos.org/nixos/nix-pills, автор Luca Bruno (aka Lethalman)
6+ Вольный пересказ nix-pills на русском языке.
7+ https://nixos.org/nixos/nix-pills, автор Luca Bruno (aka Lethalman)
88theme: Boadilla
99colortheme: dolphin
1010mainfont: Roboto
@@ -13,21 +13,20 @@ aspectratio: 169
1313titlegraphic: images/nix.png
1414toc: true
1515header-includes:
16−- \usepackage[utf8]{inputenc}
17−- \usepackage[russian]{babel}
18−- \uselanguage{Russian}
19−- \languagepath{Russian}
20−- \usepackage{tikz}
16+ - \usepackage[utf8]{inputenc}
17+ - \usepackage[russian]{babel}
18+ - \uselanguage{Russian}
19+ - \languagepath{Russian}
20+ - \usepackage{tikz}
2121---
2222
23+# Вступление
24+
2325<!--
2426<meta charset="utf8" />
2527<title>Заметки выступающего</title>
26−# Заметки выступающего
2728-->
2829
29−# Вступление
30−
3130Основано на <https://nixos.org/nixos/nix-pills> . Огромная благодарность @Lethalman и @grahamc за написание и поддержку этого материала!
3231
3332Я хотел бы, чтобы к концу презентации всем стало понятно
@@ -91,7 +90,7 @@ digraph {
9190}
9291```
9392
94−
93+
9594# Nix как пакетный менеджер
9695
9796> Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. It provides atomic upgrades and rollbacks, side-by-side installation of multiple versions of a package, multi-user package management and easy setup of build environments.
@@ -302,6 +301,22 @@ nix-repl> /home/balsoft/Документы
302301error: path '/home/balsoft/' has a trailing slash
303302```
304303
304+## `NIX_PATH` и `<nixpkgs>`
305+<!--
306+`NIX_PATH` -- переменная, очень похожая на `PATH` по синтаксису значений (значения разделены двоеточием), в которой nix ищет пути запрошенные через синтаксис `<path>`.
307+-->
308+```
309+$ NIX_PATH=home=$HOME nix repl
310+nix-repl> <home>
311+/home/balsoft
312+^D
313+$ NIX_PATH=/home nix repl
314+nix-repl> <balsoft>
315+/home/balsoft
316+$ nix repl
317+nix-repl> <nixpkgs>
318+/nix/store/zzjhgd7p1y879z0y9pz70vjd45yfi9iz-nixpkgs
319+```
305320## Списки (lists)
306321
307322Списки в Nix иммутабельны и могут содержать значения любых типов вперемешку.
@@ -326,6 +341,8 @@ nix-repl> [ import ./hello.nix {} ]
326341## Множества аттрибутов (attribute sets, attrsets)
327342```
328343nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; }
344+nix-repl> :t s
345+a set
329346nix-repl> s
330347{ "123" = "num"; a-b = "baz"; foo = "bar"; }
331348nix-repl> s.a-b
@@ -334,6 +351,15 @@ nix-repl> s."123"
334351"num"
335352nix-repl> s.${toString (122 + 1)}
336353"num"
354+```
355+***
356+```
357+nix-repl> s ? "123"
358+true
359+nix-repl> s ? a-b
360+true
361+nix-repl> s ? abc
362+false
337363nix-repl> s.abc or "not found" # Note that `or` is a keyword
338364"not found"
339365nix-repl> s // { a = 10; }
@@ -754,6 +780,9 @@ pkgs: attrs:
754780derivation (defaultAttrs // attrs)
755781```
756782## `mkDerivation`: воспользуемся нашей функцией!
783+<!--
784+Отлично! Наша функция умеет собирать autotools-проекты, при этом `builder.sh` получился очень простым благодаря `setup.nix`. Теперь если мы хотим собрать проект не на основе autotools, мы можем это легко сделать, пропустив некоторые фазы в builder.
785+-->
757786### `default.nix`
758787```
759788let
@@ -767,6 +796,9 @@ mkDerivation {
767796```
768797
769798## Пакеты как функции от зависимостей: паттерн inputs
799+<!--
800+Сделаем наш репозиторий ещё удобнее, вынеся определения пакетов в отдельные файлы. В каждом таком файле будет находиться функция, принимающая в качестве аргументов зависимости пакета и возвращающая деривацию (который мы будем создавать с помощью нашего `mkDerivation`)
801+-->
770802### `hello.nix`
771803```
772804{ mkDerivation }:
@@ -775,6 +807,7 @@ mkDerivation {
775807 src = ./hello-2.10.tar.gz;
776808}
777809```
810+
778811### `default.nix`
779812```
780813let
@@ -786,3 +819,204 @@ in
786819}
787820```
788821
822+## Сборка с библиотеками: `NIX_CFLAGS_COMPILE`, `NIX_LDFLAGS`
823+<!--
824+Если мы хотим собирать пакеты, которые зависят от сторонних библиотек, нам понадобится ещё немного магии: нужно указать GCC и ld, где искать заголовочные и объектные файлы библиотек. Для этого существуют переменные `NIX_CFLAGS_COMPILE` и `NIX_LDFLAGS_`
825+-->
826+### `generic-builder.sh`
827+```
828+[...]
829+for p in $baseInputs $buildInputs; do
830+ if [ -d $p/bin ]; then
831+ export PATH="$p/bin${PATH:+:}$PATH"
832+ fi
833+ if [ -d $p/include ]; then
834+ export NIX_CFLAGS_COMPILE="-I $p/include${NIX_CFLAGS_COMPILE:+ }$NIX_CFLAGS_COMPILE"
835+ fi
836+ if [ -d $p/lib ]; then
837+ export NIX_LDFLAGS="-rpath $p/lib -L $p/lib${NIX_LDFLAGS:+ }$NIX_LDFLAGS"
838+ fi
839+done
840+[...]
841+```
842+***
843+### `graphviz.nix`
844+```
845+{ mkDerivation, gdSupport ? true, gd, fontconfig, libjpeg, bzip2 }:
846+
847+mkDerivation {
848+ name = "graphviz";
849+ src = ./graphviz-2.38.0.tar.gz;
850+ buildInputs = if gdSupport then [ gd fontconfig libjpeg bzip2 ] else [];
851+}
852+```
853+***
854+### `default.nix`
855+```
856+let
857+ pkgs = import <nixpkgs> {};
858+ mkDerivation = import ./generic-builder.nix pkgs;
859+in
860+{
861+ hello = import ./hello.nix { inherit mkDerivation };
862+ graphviz = import ./graphviz.nix { inherit mkDerivation
863+ gd fontconfig libjpeg bzip2; };
864+ graphvizCore = import ./graphviz.nix {
865+ inherit mkDerivation gd fontconfig libjpeg bzip2;
866+ gdSupport = false;
867+ };
868+}
869+```
870+
871+## Паттерн `callPackage`
872+<!--
873+Здорово! Мы сделали свой небольшой репозиторий с красивой файловой структурой, научились собирать пакеты с помощью autotools с возможностью легко перейти на другие системы сборки и даже собирать пакеты с библиотеками. Теперь пора уменьшить бойлерплейт. Можно заметить, что в корневом файле репозитория мы повторяем названия аргументов функции при её вызове. Это будет происходить постоянно при структуре репозитория, похожего на наш. Для того, чтобы исправить эту проблему, мы создадим функцию `callPackage`, которая будет вызывать функцию из указанного файла, автоматически передавая ей аргументы из указанного attrset. Например:
874+-->
875+### `default.nix`
876+```
877+{
878+ hello = callPackage ./hello.nix { };
879+ graphviz = callPackage ./graphviz.nix { };
880+ graphvizCore = callPackage ./graphviz.nix
881+ { gdSupport = false; };
882+}
883+```
884+***
885+<!--
886+Для начала разберемся с тем, как же нам найти названия аргументов функции. В nix для этого существует встроенная функция `builins.functionArgs`. Она возвращает attrset, в котором названию аргументов соответствует наличие у них значения по-умолчанию.
887+-->
888+```
889+nix-repl> add = { a ? 3, b }: a+b
890+nix-repl> builtins.functionArgs add
891+{ a = true; b = false; }
892+```
893+<!--
894+Также в nix существует удобный builtin для "пересечения" двух attrset-ов. Он возвращает attrset, в котором имена аттрибутов -- пересечение имен аттрибутов его аргументов, а их значения берутся из второго аргумента. Например:
895+-->
896+```
897+nix-repl> values = { a = 3; b = 5; c = 10; }
898+nix-repl> builtins.intersectAttrs values (builtins.functionArgs add)
899+{ a = true; b = false; }
900+nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values
901+{ a = 3; b = 5; }
902+```
903+## Простой вариант `callPackage`
904+<!--
905+Обладая этими знаниями, напишем простую реализацию нашего callPackage.
906+-->
907+```
908+nix-repl> callPackage = set: f:
909+ f (builtins.intersectAttrs
910+ (builtins.functionArgs f) set)
911+nix-repl> callPackage values add
912+8
913+nix-repl> with values; add { inherit a b; }
914+8
915+```
916+## `callPackage`: добавляем оверрайды
917+<!--
918+Добавим возможность перегружать значения с помощью последнего аргумента...
919+-->
920+```
921+nix-repl> callPackage = set: f: overrides:
922+ f ((builtins.intersectAttrs
923+ (builtins.functionArgs f) set) // overrides)
924+nix-repl> callPackage values add { }
925+8
926+nix-repl> callPackage values add { b = 12; }
927+15
928+```
929+## `callPackage`: используем!
930+```
931+let
932+ nixpkgs = import <nixpkgs> {};
933+ allPkgs = nixpkgs // pkgs;
934+ callPackage = path: overrides:
935+ let f = import path;
936+ in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
937+ pkgs = with nixpkgs; {
938+ mkDerivation = import ./generic-builder.nix nixpkgs;
939+ hello = callPackage ./hello.nix { };
940+ graphviz = callPackage ./graphviz.nix { };
941+ graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
942+ };
943+in pkgs
944+```
945+## Паттерн `override`
946+<!--
947+Nix -- это функциональный язык. Одна из идей функциональной парадигмы состоит в композиции функций. Это может быть очень удобно при работе с деривациями. Например, если мы хотим получить `graphviz`, собранный с нашей собственной версией библиотеки gd, на данный момент нам придется заново делать `callPackage ./graphviz.nix { gd = customgd; }` Это неудобно и добавляет бойлерплейт.
948+-->
949+```
950+mygraphviz = callPackage ./graphviz.nix { gd = customgd; }
951+
952+VS.
953+
954+mygraphviz = pkgs.graphviz.override { gd = customgd }
955+```
956+***
957+<!--
958+Для реализации нашей затеи напишем функцию `mkOverridable`, которая будет принимать функцию, возвращающую attrset, и attrset (который будет являться "дефолтными" аргументами для данной функции). Вернет `makeOverridable` тот же attrset, что вернула бы данная функция, с добавлением аттрибута `override`, который является функцией, позволяющей нам "перегружать" стандартные аргументы.
959+-->
960+### `lib.nix`
961+```
962+rec {
963+ makeOverridable = f: origArgs:
964+ let
965+ origRes = f origArgs;
966+ in
967+ origRes // {
968+ override = newArgs:
969+ makeOverridable f (origArgs // newArgs);
970+ };
971+}
972+```
973+***
974+### Протестируем в `nix repl`
975+<!--
976+Работает! Теперь мы можем легко "перегружать" аргументы пакетов (которые, напомню, просто функции) в нашем репозитории. Нужно только не забыть вызывать все эти пакеты с makeOverridable.
977+-->
978+```
979+nix-repl> :l lib.nix
980+Added 1 variables.
981+nix-repl> f = { a, b }: { result = a+b; }
982+nix-repl> res = makeOverridable f { a = 3; b = 5; }
983+nix-repl> res2 = res.override { a = 10; }
984+nix-repl> res2
985+{ override = «lambda»; result = 15; }
986+nix-repl> res2.override { b = 20; }
987+{ override = «lambda»; result = 30; }
988+```
989+***
990+### `lib.nix`
991+```
992+rec {
993+ [...]
994+ callPackage = pkgs: path: overrides:
995+ let f = import path;
996+ in makeOverridable
997+ f ((builtins.intersectAttrs
998+ (builtins.functionArgs f) pkgs) // overrides);
999+}
1000+```
1001+***
1002+### `default.nix`
1003+```
1004+let
1005+ lib = import ./lib.nix;
1006+ nixpkgs = import <nixpkgs> {};
1007+ callPackage = lib.callPackage allPkgs;
1008+ allPkgs = nixpkgs // pkgs;
1009+ pkgs = with nixpkgs; rec {
1010+ mkDerivation = import ./generic-builder.nix nixpkgs;
1011+ hello = callPackage ./hello.nix { };
1012+ graphviz = callPackage ./graphviz.nix { };
1013+ graphvizCore = graphviz.override { gdSupport = false };
1014+ };
1015+in pkgs
1016+```
1017+## nixpkgs
1018+<!--
1019+Изначально я планировал вкратце рассказать про остальные паттерны nixpkgs, но в итоге получился просто объяснение, что же происходит в каждом файле, которое к тому же растянуло бы презентацию ещё на несколько десятков слайдов. На самом деле, на данный момент ваших знаний nix вполне достаточно для чтения исходников nixpkgs.
1020+-->
1021+> Изучение nixpkgs остается в качестве упражнения для слушателя.
1022+