git.iko.soy/typeable/nix-pills-ru
on master help sign in
typeable/nix-pills-ru/presentation.md
1078 lines 43 kB history raw
1---
2title: Nix
3subtitle: Язык, пакетный менеджер и экосистема
4author: Александр Бантьев @balsoft, typeable.io
5description: |
6 Вольный пересказ nix-pills на русском языке.
7 https://nixos.org/nixos/nix-pills, автор Luca Bruno (aka Lethalman)
8theme: Boadilla
9colortheme: dolphin
10mainfont: Roboto
11monofont: Hack
12aspectratio: 169
13titlegraphic: images/nix.png
14toc: true
15header-includes:
16 - \usepackage[utf8]{inputenc}
17 - \usepackage[russian]{babel}
18 - \uselanguage{Russian}
19 - \languagepath{Russian}
20 - \usepackage{tikz}
21---
23# Вступление
25<!--
26<meta charset="utf8" />
27<title>Заметки выступающего</title>
28-->
30Основано на <https://nixos.org/nixos/nix-pills> . Огромная благодарность @Lethalman и @grahamc за написание и поддержку этого материала!
32Я хотел бы, чтобы к концу презентации всем стало понятно
34* Что такое Nix
35* Как собрать пакеты из `contractor` с помощью nix
36* Что находится в `contractor/nix/pkgs`
37* Что находится в `contractor/nix/overlay.nix`
38* Как писать несложные выражения на nix, не совершая неочевидных ошибок
40Вы можете прекратить слушать после любой секции, и я надеюсь, что вы унесете какую-то полезную для себя информацию.
42Исходники, собранный pdf и заметки докладчика для этой презентации находятся на <https://github.com/typeable/nix-pills-ru>
45<!--
46Как вы все скорее всего знаете, мы используем Nix для сборки и тестирования нашего кода. Я, Александр Бантьев (@balsoft) отвечаю за инфраструктуру сборки пакетов (Hydra, NixOps), а также за поддержание инструкций сборки в актуальном состоянии. Я бы хотел рассказать вам про nix с помощью вольного пересказа отличной серии статей ["Nix pills"](https://nixos.org/nixos/nix-pills)
47-->
49## Что такое nix?
51<!--
52Говоря "Nix", мы подразумеваем одну из двух его ипостасей -- Nix как язык или Nix как пакетный менеджер. Такая путаница возникла из-за того, что как DSL Nix имеет смысл только в связке со своим пакетным менеджером, а как пакетный менеджер он без своего языка слишком сложен в использовании, и Eelco Dolstra (создатель Nix) решил не разделять их.
53-->
55[Nix](https://nixos.org/nix) -- это
571. Язык
58 + Декларативный
59 + Функциональный
60 + Чистый
61 + Ленивый
62 + Динамически типизированный
632. Пакетный менеджер
64 + Атомарный
65 + Повторяемый
66 + Source/Binary
69## Экосистема
71<!--
72Nix довольно быстро стал широко известен в узких кругах, и вокруг него возникла большая экосистема декларативных, атомарных инструментов, в том числе:
74* nixpkgs, репозиторий описаний пакетов на языке nix;
75* NixOS, дистрибутив GNU/Linux с интересным подходом к конфигурации системы;
76* NixOps, инструментарий для разворачивания виртуальных машин и приложений на них на базе NixOS;
77* Hydra, CI -- сервер;
78* Disnix, система для разворачивания распределенных систем.
79-->
81```{.graphviz layout=fdp}
82digraph {
83 graph [ dpi = 200; font = Hack; ];
84 subgraph cluster_nix {
85 "nix (пакетный менеджер)" -> "nix (язык)";
86 "nix (язык)" -> "nix (пакетный менеджер)";
87 color = blue;
88 }
89 cluster_nix -> Disnix;
90 cluster_nix -> Hydra;
91 cluster_nix -> nixpkgs -> NixOS -> NixOps;
93```
96# Nix как пакетный менеджер
98> 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.
100*--- nixos.org/nix*
102<!--
103На https://nixos.org/nix заявлено, что nix -- это все-таки пакетный менеджер. С этого компонента мы и начнем.
104-->
106## Хранилище (nix store)
108<!--
109Центральным местом файловой системы для Nix является Хранилище (nix store). Именно здесь хранятся все файлы, с которыми оперирует наш пакетный менеджер: инструкции сборки и её результаты -- исполняемые файлы, документация, заголовочные файлы, etc.
111Все элементы директории `/nix/store` имеют одинаковый формат имени -- `<hash>-<name>[-version][-output]`. Хэш вычисляется при добавлении в хранилище или при сборке на основе содержимого файла либо содержимого деривации (инструкции сборки), из которого этот путь был собран.
112-->
115> In the beginning was the Store, and the Store was with Nix, and the Store was Nix.
117*--- Nix manual, 1:1*
120```bash
121$ ls /nix/store | wc -l
12246251
123$ nix add-to-store nix.conf
124/nix/store/za34q8y0jcx3qsrnbrd005mh8zcnlrlr-nix.conf
125```
126### Формат имени
127```
128<hash>-<name>[-version][-output]
129```
130Например,
131```
132/nix/store/04mm93j20jlnwvhh21db674b3vy6fs54-pandoc-2.7.1-data
133 | hash | name | ver | out |
134```
136## Принцип работы
138<!--
139В общих чертах, получение результата любого выражения (в терминологии ПМ -- пакета) на nix состоит из двух этапов -- вычисления (за это отвечает nix как язык), и сборки -- именно эта часть ложится на плечи nix как пакетного менеджера.
140-->
142```graphviz
143digraph {
144 graph [ dpi = 200; font = Hack; ];
145 "myname.nix" -> "/nix/store/...-myname.drv" [ label = " Вычисление (nix как язык)" ];
146 "/nix/store/...-myname.drv" -> "/nix/store/...-myname" [ label = " Сборка (nix как пакетный менеджер)" ];
148```
150## Деривации (derivations, `.drv` files)
152<!--
153Для сборки пакетов любому пакетному менеджеру нужны инструкции. Например, ABS (Arch build system) использует PKGBUILD, portage (Gentoo) использует EBUILD, brew использует recepies. Nix не является исключением, и для сборки он использует Деривации (.drv). Пример деривации (с добавленными мною переносами строки и пробелами для улучшения читаемости) -- на слайде.
154-->
157### `/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv`
158```
159Derive
160([("out"
161 ,"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname","","")]
162,[]
163,[]
164,"mysystem"
165,"mybuilder"
166,[]
167,[("builder","mybuilder")
168 ,("name","myname")
169 ,("out","/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname")
170 ,("system","mysystem") ] )
171```
173## Краткая справка по инструментарию
174### Собрать (build/realize) деривацию
175```
176$ nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv
177```
178### Вычислить (evaluate) и затем собрать содержимое файла `default.nix`
179```
180$ nix build -f default.nix # или nix-build default.nix
181```
182### Вычислить, собрать и запустить интерактивное окружение
183```
184$ nix run -f default.nix # или nix-shell default.nix
185```
187# Nix как язык
189<!--
190В общих чертах разобравшись с пакетным менеджером, перейдем к языку. Большая часть синтаксиса, конструкций и идей будет знакома вам по другим языкам, поэтому мы быстро пробежимся по примерам, останавливаясь лишь на уникальных чертах и граблях, на которые легко наступить.
191-->
193Рассмотрим основы синтаксиса Nix.
195Краткая инструкция для желающих повторить:
196```bash
197$ curl https://nixos.org/nix/install | sh
198<...>
199$ nix repl
200```
202## Числа, арифметика и комментарии
203```
204nix-repl> :t 4 # Показать тип выражения
205an integer
206nix-repl> 1 + 2 - 3 * 2
207-3
208nix-repl> 1 / 2
210nix-repl> builtins.add 1 2
212nix-repl> # Однострочный комментарий
213nix-repl> /* Многострочный комментарий */
214```
215### Замечание
216Арифметические операторы лучше обносить пробелами! У символов `/` и `-` есть особые значения в Nix
217```
218nix-repl> 6/2
219/home/balsoft/6/2
220```
222## Булевы, булева арифметика и if
223```
224nix-repl> :t true
225a boolean
226nix-repl> true && false
227false
228nix-repl> true || false
229true
230nix-repl> if true || false then 3 else 4
232nix-repl> if false then 5 else 6
234```
236## Строки (strings)
237```
238nix-repl> "foo"
239"foo"
240nix-repl> :t "foo"
241a string
242nix-repl> "Привет"
243"Привет"
244nix-repl> ''foo''
245"foo"
246nix-repl> ''
247 foo
248 bar
249 ''
250"foo\nbar\n"
251```
254## Переменные и стороковая интерполяция
255```
256nix-repl> foo = "hello"
257nix-repl> "${foo}, world!"
258"hello, world!"
259```
261### Замечание
262Названия переменных могут содержать знак `-`
263```
264nix-repl> foo-bar = 10
265nix-repl> foo-bar
26610
267```
268Но не могут содержать не-ASCII!
269```
270nix-repl> привет
271error: syntax error, unexpected $undefined, at (string):1:1
272```
274## Как избежать интерполяции?
275Довольно часто встречаются ситуации, когда мы хотим вставить в строку последовательность `${...}` (например, в bash-код).
277```
278nix-repl> "\${foo}"
279"${foo}"
281nix-repl> ''test ''${foo} test''
282"test ${foo} test"
283```
285## Пути (paths) и URL
287```
288nix-repl> /bin/sh
289/bin/sh
290nix-repl> :t /bin/sh
291a path
292nix-repl> ./hello # Относительно текущего .nix файла или PWD для repl
293/home/balsoft/hello
294nix-repl> https://typeable.io # Синтаксический сахар для строк
295"https://typeable.io"
296nix-repl> :t https://typeable.io
297a string
298```
299### Замечание
300Пути, как и названия переменных, не могут содержать не-ASCII. Я бы рассматривал это, как баг.
301```
302nix-repl> /home/balsoft/Документы
303error: path '/home/balsoft/' has a trailing slash
304```
306## `NIX_PATH` и `<nixpkgs>`
307<!--
308`NIX_PATH` -- переменная, очень похожая на `PATH` по синтаксису значений (значения разделены двоеточием), в которой nix ищет пути запрошенные через синтаксис `<path>`.
309-->
310```
311$ NIX_PATH=home=$HOME nix repl
312nix-repl> <home>
313/home/balsoft
314^D
315$ NIX_PATH=/home nix repl
316nix-repl> <balsoft>
317/home/balsoft
318$ nix repl
319nix-repl> <nixpkgs>
320/nix/store/zzjhgd7p1y879z0y9pz70vjd45yfi9iz-nixpkgs
321```
322## Списки (lists)
324Списки в Nix иммутабельны и могут содержать значения любых типов вперемешку.
326```
327nix-repl> [ 2 "foo" true (2+3) ]
328[ 2 "foo" true 5 ]
330```
332### Замечание
333Обратите внимание: элементы списка не разделены запятыми!
334```
335nix-repl> [ 1 + 2 ]
336error: syntax error, unexpected '+', at (string):1:5
337nix-repl> [ (1 + 2) ]
338[ 3 ]
339nix-repl> [ import ./hello.nix {} ]
340[ «primop-app» /home/balsoft/hello.nix { ... } ]
341```
343## Множества аттрибутов (attribute sets, attrsets)
344```
345nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; }
346nix-repl> :t s
347a set
348nix-repl> s
349{ "123" = "num"; a-b = "baz"; foo = "bar"; }
350nix-repl> s.a-b
351"baz"
352nix-repl> s."123"
353"num"
354nix-repl> s.${toString (122 + 1)}
355"num"
356```
357## Множества аттрибутов
358```
359nix-repl> s ? "123"
360true
361nix-repl> s ? a-b
362true
363nix-repl> s ? abc
364false
365nix-repl> s.abc or "not found" # Note that `or` is a keyword
366"not found"
367nix-repl> s // { a = 10; }
368{ "123" = "num"; a = 10; a-b = "baz"; foo = "bar"; }
369nix-repl> rec { a = 3; b = a + 4; } # Recursive attrset
370{ a = 3; b = 7; }
371```
374## Let, with и inherit
375```
376nix-repl> let a = 3; b = 4; in a + b
378nix-repl> let a = 4; b = a + 5; in b # Let definitions are recursive
380nix-repl> let a = 3; a = 8; in a
381error: attribute `a' at (string):1:12 already defined at (string):1:5
382nix-repl> longName = { a = 3; b = 4; }
383nix-repl> with longName; a + b
385nix-repl> let a = 10; b = 20; in { inherit a; b = b + 1; }
386{ a = 10; b = 21; }
387nix-repl> { inherit (s) "123" foo; }
388{ "123" = "num"; foo = "bar"; }
389```
391## Функции (functions)
392```
393nix-repl> x: x*2
394«lambda»
395nix-repl> double = x: x * 2
396nix-repl> :t double
397a function
398nix-repl> double 3
400nix-repl> greet = end: name: "Hello, ${name} ${end}"
401nix-repl> greet "dredozubov" "!"
402"Hello, dredozubov !"
403nix-repl> greetExclamaition = greet "!"
404nix-repl> greetExclamaition "s9gf4ult"
405"Hello, s9gf4ult !"
406```
408## Паттерн-матчинг
409```
410nix-repl> greet = { name, end }: "Hello, ${name} ${end}"
411nix-repl> greet { name = "ak3n"; exc = "!"; }
412"Hello, ak3n !"
413nix-repl> greet = { name, end ? "!" }: "Hello, ${name} ${end}"
414nix-repl> greet { name = "ak3n"; }
415"Hello, ak3n !"
416nix-repl> greet { name = "ak3n"; end = "."; }
417"Hello, ak3n ."
418nix-repl> greet = { name, end ? "!", ... }: "Hello, ${name} ${end}"
419nix-repl> greet { name = "ak3n"; end = "."; extra = "foo"; }
420"Hello, ak3n ."
421nix-repl> foo = {a, ...}@args: args // { a = a + 3; }
422nix-repl> foo { a = 10; b = 11; }
423{ a = 13; b = 11; }
424```
426## Подключения файлов (import)
427### `number.nix`
428```
430```
431### `function.nix`
432```
433a: a + 3
434```
435### Обратно в наш уютный repl
436```
437nix-repl> a = import ./number.nix
438nix-repl> f = import ./function.nix
439nix-repl> f a
441```
443## Деривации (derivation)
444<!--
445Вот мы и подошли к моменту, когда мы можем сделать свою собственную деривацию! Исследуем встроенную функцию `derivation`. Она возвращает attrset, но nix показывает его как `derivation`. Странные дела!
446-->
447```
448nix-repl> d = derivation {
449 name = "myname";
450 builder = "mybuilder";
451 system = "mysystem"; }
452nix-repl> :t d
453a set
454nix-repl> d
455«derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv»
456```
457## Деривации: сборка
458<!--
459Как вы помните, деривации -- это инструкции сборки для nix. Попробуем собрать нашу фейковую деривацию. nix подсказывает, что у нас система не `mysystem`, а `x86\_64-
460linux`. Исправим это и попробуем ещё раз. Теперь проблема в том, что файла `mybuilder`, который должен собрать наш пакет, не существует. Fail. Остановимся и исследуем `derivation` поподробнее.
461-->
462```
463nix-repl> :b d
464[...]
465error: a `mysystem' is required to build ..., but I am a `x86_64-linux'
466nix-repl> d = derivation {
467 name = "myname";
468 builder = "mybuilder";
469 system = builtins.currentSystem; }
470nix-repl> :b d
471[...]
472[...]: executing 'mybuilder': No such file or directory
473```
474## Деривации: что внутри?
475<!--
476Так как derivation -- это обычный attrset, мы можем исследовать его аттрибуты с помощью встроенного инструментария. Заметим, что `drvAttrs` содержит аргументы, переданные в функцию `derivation`, `out` совпадает с самой деривацией (т.к. на данный момент у нашей деривации только один результат), ну и восхитимся магией типизации на аттрибутах (так вот почему nix так странно себя ведет, показывая derivation вместо сета!). Также заметим, что `toString` превращает в строки только те сеты, которые содержат аттрибут `outPath`.
477-->
478```
479nix-repl> builtins.attrNames d
480[ "all" "builder" "drvAttrs" "drvPath" "name"
481 "out" "outPath" "outputName" "system" "type" ]
482nix-repl> d.drvAttrs
483{ builder = "mybuilder"; name = "myname"; system = "x86_64-linux"; }
484nix-repl> (d == d.out)
485true
486nix-repl> { type = "derivation"; } # Типизация истинных джентельменов
487«derivation ???»
488nix-repl> toString d
489"/nix/store/qaq9ir9w2a34dzkjmk3igfbibi5q59sd-myname"
490nix-repl> toString { outPath = "blah"; }
491"blah"
492nix-repl> toString { a = 10; }
493error: cannot coerce a set to a string, at (string):1:1
494```
495## Деривации: добавляем зависимости
496```
497nix-repl> :l <nixpkgs> # nixpkgs -- это огромная куча дериваций!
498Added 10082 variables.
499nix-repl> coreutils
500«derivation /nix/store/...-coreutils-8.30.drv»
501nix-repl> toString coreutils
502"/nix/store/...-coreutils-8.30"
503nix-repl> d = derivation {
504 name = "myname";
505 builder = "${coreutils}/bin/true";
506 system = builtins.currentSystem; }
507nix-repl> :b d
508builder for '...-myname.drv' failed to produce output path '...-myname'
509```
510## Деривации: смотрим на зависимости
511<!--
512`nix show-derivation` показывает содержимое деривации в виде pretty-printed JSON. Как мы видим, у нас в зависимостях появилась ссылка на coreutils.drv, и builder стал указывать на true.
513-->
514```
515$ nix show-derivation /nix/store/...-myname.drv
517 "/nix/store/apvmj79y84wivsnjc2vkv2q79a37nf7l-myname.drv": {
518 [...]
519 "inputDrvs": {
520 "...-coreutils-8.30.drv": [
521 "out"
522 ]
523 },
524 "builder": "...-coreutils-8.30/bin/true",
525 [...]
526 }
528```
530## Деривации: рабочая деривация!
531<!--
532Исправим ошибку, которая не дает нам получить нашу первую собирающуюся деривацию! Создадим скрипт `builder.sh`, который создаст outPath. Обратите внимание: мы не будем указывать hashbang для нашего скрипта, потому что даже сам исполняемый файл bash лежит в `/nix/store`, и путь к нему мы можем определить только из nix. Из-за невозможности добавить hashbang, будем запускать наш сборщик как `bash builder.sh`:
533-->
534### `builder.sh`
535```bash
536declare -xp
537echo foo > $out
538```
540### Уютный `nix repl`
541```
542nix-repl> :l <nixpkgs>
543Added 10082 variables.
544nix-repl> d = derivation {
545 name = "foo";
546 builder = "${bash}/bin/bash";
547 args = [ ./builder.sh ];
548 system = builtins.currentSystem; }
549nix-repl> :b d
550this derivation produced the following outputs:
551 out -> /nix/store/82jrv90z3aj1lmzr437jpp7m9krnrrzf-foo
552```
553## Деривации: посмотрим на env
554<!--
555Посмотрим на то, что показал `declare -xp` (т.е. на переменные окружения, которые доступны внутри сборщика). `nix log` показывает вывод сборщика при сборке определенного пути.
557* `$HOME` указывает в пустой `/homeless-shelter`
558* `$PATH` тоже пуст
559* `$PWD` указывает во временную директорию `/build` (на самом деле она находистся в `/tmp`)
561Замечу, что все аргументы derivation окажутся в env сборщика.
562-->
563```
564$ nix log /nix/store/...-foo
565[...]
566declare -x HOME="/homeless-shelter"
567declare -x NIX_BUILD_CORES="0"
568declare -x NIX_BUILD_TOP="/build"
569declare -x NIX_LOG_FD="2"
570declare -x NIX_STORE="/nix/store"
571declare -x PATH="/path-not-set"
572declare -x PWD="/build"
573declare -x TEMP="/build"
574declare -x builder="/nix/store/...-bash-4.4-p23/bin/bash"
575declare -x name="foo"
576declare -x out="/nix/store/82jrv90z3aj1lmzr437jpp7m9krnrrzf-foo"
577declare -x system="x86_64-linux"
578```
581## Деривации: опакетим простую программу на C
582<!--
583Теперь, когда мы умеем создавать рабочие деривации, добавлять зависимости и передавать произвольные переменные в env их сборщика, попробуем опакетить простую программу на C.
584На этот раз вылезем из привычного `nix repl` и напишем файл!
586Для сборки программы нам нужны coreutils (для создания выходной директории) и gcc (для сборки). К счастью, эти (и десятки тысяч других) приложения уже опакечены в nixpkgs. Передадим их деривации как аргументы нашему `derivation`, и они окажутся в env сборщика.
587-->
588### `simple.c`
589```C
590void main() {
591 puts("Simple!");
593```
595### `simple_builder.sh`
596```bash
597export PATH="$coreutils/bin:$gcc/bin" # Переменные из derivation
598mkdir -p $out/bin # Создадим выходную директорию
599gcc -o $out/bin/simple $src # Соберем нашу программу
600```
601***
602### `simple.nix`
603```
604with (import <nixpkgs> {});
605derivation {
606 name = "simple";
607 builder = "${bash}/bin/bash";
608 args = [ ./simple_builder.sh ]; # Сборщик
609 inherit gcc coreutils; # Зависимости
610 src = ./simple.c;
611 system = builtins.currentSystem;
613```
614### Поехали!
615```
616$ nix build -f simple.nix
617[1 built, 0.0 MiB DL]
618$ ./result/bin/simple
619Simple!
620```
621<!--
622Успех!
623-->
626***
627### Посмотрим граф зависимостей (`nix-store -q result --graph`)
628```graphviz
629digraph G {
630graph [ dpi=200; ]
631"/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [label = "simple", shape = box, style = filled];
632"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "black"];
633"/nix/store/9sll05wmn9b05f0pm1pwfmmq92fg0syh-simple.c" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "red"];
634"/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "green"];
635"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "blue"];
636"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" [label = "glibc-2.27", shape = box, style = filled];
637"/nix/store/9sll05wmn9b05f0pm1pwfmmq92fg0syh-simple.c" [label = "simple.c", shape = box, style = filled];
638"/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [label = "gcc-7.4.0", shape = box, style = filled];
639"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "magenta"];
640"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "burlywood"];
641"/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "black"];
642"/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "red"];
643"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" [label = "gcc-7.4.0-lib", shape = box, style = filled];
644"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" [color = "green"];
645"/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" [label = "zlib-1.2.11", shape = box, style = filled];
646"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" [color = "blue"];
647"/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [label = "glibc-2.27-dev", shape = box, style = filled];
648"/nix/store/5lyvydxv0w4f2s1ba84pjlbpvqkgn1ni-linux-headers-4.19.16" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "magenta"];
649"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "burlywood"];
650"/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "black"];
651"/nix/store/5lyvydxv0w4f2s1ba84pjlbpvqkgn1ni-linux-headers-4.19.16" [label = "linux-headers-4.19.16", shape = box, style = filled];
652"/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" [label = "glibc-2.27-bin", shape = box, style = filled];
653"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" [color = "red"];
655```
657# Стандартные паттерны в Nix. nixpkgs.
658## `mkDerivation`
659<!--
660Итак, у нас есть мощный и специализированный язык, а также пакетный менеджер, который предоставляет удобную, изолированную среду для сборки пакетов. Для того, чтобы опакечивать софт было проще, создадим функцию, которая будет обобщать `derivation` и уменьшать бойлерплейт. Назовем эту функцию...
661-->
663Неудобства `derivation`
665- Очень большая часть написанного кода будет повторяться из пакета в пакет
666 * Распаковка (чаще всего достаточно `tar -xf`)
667 * Добавление зависимостей (`gcc`, `coreutils`, `bash`, `awk`, `make`, ...)
668 * Сборка (очень часто можно обойтись `./configure && make`)
669 * Установка (`make install`)
670- Требует как минимум два файла -- builder и expression
671- После вычисления уже нельзя поменять отдельные аргументы, а это может быть удобно
673Создадим свой `derivation` ~~c блекджеком и шлюхами~~ без этих недостатков!
675## `mkDerivation`: зависимости и стандартный сборщик
676<!--
677Для начала, напишем сборщик в файле `generic-builder.sh`. Сначала поставим свой `$PATH`, собрав его из содержимого `buildInputs`. Затем распакуем архив, переданный нам в `$src` и перейдем в свежесозданную директорию. Там сконфигурируем, соберем и установим наш autotools-проект.
678-->
679### `generic-builder.sh`
680```
681set -e
682unset PATH
683for p in $buildInputs $baseInputs; do
684 export PATH=$p/bin${PATH:+:}$PATH
685done
686tar -xf $src
687for d in *; do
688 if [ -d "$d" ]; then
689 cd "$d"
690 break
691 fi
692done
693./configure --prefix=$out
694make
695make install
696```
697***
698<!--
699В nix-части напишем функцию, которая создает деривацию с нашим сборщиком и некоторыми пакетами по-умолчанию, которые будут полезны при создании любого пакета.
700-->
701### `generic-builder.nix`
702```
703pkgs: attrs:
704 with pkgs;
705 let defaultAttrs = {
706 builder = "${bash}/bin/bash";
707 args = [ ./builder.sh ];
708 baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped
709 coreutils gawk gnused gnugrep findutils ];
710 buildInputs = [];
711 system = builtins.currentSystem;
712 };
713 in
714derivation (defaultAttrs // attrs)
715```
716## `mkDerivation`: добавляем фазы
717<!--
718Создадим файл `setup.sh`, в котором определим стандартные "фазы" сборки, которые затем будем использовать в `builder.sh`.
719-->
720```
721unset PATH
722for p in $baseInputs $buildInputs; do
723 export PATH=$p/bin${PATH:+:}$PATH
724done
726function unpackPhase() {
727 tar -xzf $src
729 for d in *; do
730 if [ -d "$d" ]; then
731 cd "$d"
732 break
733 fi
734 done
736```
737***
738```
739function configurePhase() {
740 ./configure --prefix=$out
742function buildPhase() {
743 make
745function installPhase() {
746 make install
748function genericBuild() {
749 unpackPhase
750 configurePhase
751 buildPhase
752 installPhase
753 fixupPhase
755```
757***
759### `builder.sh`
760```
761set -e
762source $setup
763genericBuild
764```
766***
768### `generic-builder.nix`
769```
770pkgs: attrs:
771 with pkgs;
772 let defaultAttrs = {
773 builder = "${bash}/bin/bash";
774 args = [ ./builder.sh ];
775 setup = ./setup.sh;
776 baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped
777 coreutils gawk gnused gnugrep findutils ];
778 buildInputs = [];
779 system = builtins.currentSystem;
780 };
781 in
782derivation (defaultAttrs // attrs)
783```
784## `mkDerivation`: воспользуемся нашей функцией!
785<!--
786Отлично! Наша функция умеет собирать autotools-проекты, при этом `builder.sh` получился очень простым благодаря `setup.nix`. Теперь если мы хотим собрать проект не на основе autotools, мы можем это легко сделать, пропустив некоторые фазы в builder.
787-->
788### `default.nix`
789```
790let
791 pkgs = import <nixpkgs> {};
792 mkDerivation = import ./generic-builder.nix pkgs;
793in
794mkDerivation {
795 name = "hello";
796 src = ./hello-2.10.tar.gz;
797};
798```
800## Пакеты как функции от зависимостей: паттерн inputs
801<!--
802Сделаем наш репозиторий ещё удобнее, вынеся определения пакетов в отдельные файлы. В каждом таком файле будет находиться функция, принимающая в качестве аргументов зависимости пакета и возвращающая деривацию (который мы будем создавать с помощью нашего `mkDerivation`)
803-->
804### `hello.nix`
805```
806{ mkDerivation }:
807mkDerivation {
808 name = "hello";
809 src = ./hello-2.10.tar.gz;
811```
813### `default.nix`
814```
815let
816 pkgs = import <nixpkgs> {};
817 mkDerivation = import ./generic-builder.nix pkgs;
818in
820 hello = import ./hello.nix { inherit mkDerivation };
822```
824## Сборка с библиотеками: `NIX_CFLAGS_COMPILE`, `NIX_LDFLAGS`
825<!--
826Если мы хотим собирать пакеты, которые зависят от сторонних библиотек, нам понадобится ещё немного магии: нужно указать GCC и ld, где искать заголовочные и объектные файлы библиотек. Для этого существуют переменные `NIX CFLAGS COMPILE` и `NIX LDFLAGS`
827-->
828### `generic-builder.sh`
829```
830[...]
831for p in $baseInputs $buildInputs; do
832 [...]
833 if [ -d $p/include ]; then
834 export NIX_CFLAGS_COMPILE="-I $p/include${NIX_CFLAGS_COMPILE:+ }\
835$NIX_CFLAGS_COMPILE"
836 fi
837 if [ -d $p/lib ]; then
838 export NIX_LDFLAGS="-rpath $p/lib -L \
839$p/lib${NIX_LDFLAGS:+ }$NIX_LDFLAGS"
840 fi
841done
842[...]
843```
844***
845### `graphviz.nix`
846```
847{ mkDerivation, gdSupport ? true, gd, fontconfig, libjpeg, bzip2 }:
849mkDerivation {
850 name = "graphviz";
851 src = ./graphviz-2.38.0.tar.gz;
852 buildInputs = if gdSupport
853 then [ gd fontconfig libjpeg bzip2 ]
854 else [];
856```
857***
858### `default.nix`
859```
860let
861 pkgs = import <nixpkgs> {};
862 mkDerivation = import ./generic-builder.nix pkgs;
863in
865 hello = import ./hello.nix { inherit mkDerivation };
866 graphviz = import ./graphviz.nix { inherit mkDerivation
867 gd fontconfig libjpeg bzip2; };
868 graphvizCore = import ./graphviz.nix {
869 inherit mkDerivation gd fontconfig libjpeg bzip2;
870 gdSupport = false;
871 };
873```
875## Паттерн `callPackage`
876<!--
877Здорово! Мы сделали свой небольшой репозиторий с красивой файловой структурой, научились собирать пакеты с помощью autotools с возможностью легко перейти на другие системы сборки и даже собирать пакеты с библиотеками. Теперь пора уменьшить бойлерплейт. Можно заметить, что в корневом файле репозитория мы повторяем названия аргументов функции при её вызове. Это будет происходить постоянно при структуре репозитория, похожего на наш. Для того, чтобы исправить эту проблему, мы создадим функцию `callPackage`, которая будет вызывать функцию из указанного файла, автоматически передавая ей аргументы из указанного attrset. Например:
878-->
879### `default.nix`
880```
881let
882 pkgs = import <nixpkgs> {};
883 mkDerivation = import ./generic-builder.nix pkgs;
884in
886 hello = callPackage ./hello.nix { };
887 graphviz = callPackage ./graphviz.nix { };
888 graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
890```
891***
892<!--
893Для начала разберемся с тем, как же нам найти названия аргументов функции. В nix для этого существует встроенная функция `builins.functionArgs`. Она возвращает attrset, в котором названию аргументов соответствует наличие у них значения по-умолчанию.
894-->
895```
896nix-repl> add = { a ? 3, b }: a+b
897nix-repl> builtins.functionArgs add
898{ a = true; b = false; }
899```
900<!--
901Также в nix существует удобный builtin для "пересечения" двух attrset-ов. Он возвращает attrset, в котором имена аттрибутов -- пересечение имен аттрибутов его аргументов, а их значения берутся из второго аргумента. Например:
902-->
903```
904nix-repl> values = { a = 3; b = 5; c = 10; }
905nix-repl> builtins.intersectAttrs values (builtins.functionArgs add)
906{ a = true; b = false; }
907nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values
908{ a = 3; b = 5; }
909```
910## Простой вариант `callPackage`
911<!--
912Обладая этими знаниями, напишем простую реализацию нашего callPackage.
913-->
914```
915nix-repl> callPackage = set: f:
916 f (builtins.intersectAttrs
917 (builtins.functionArgs f)
918 set)
919nix-repl> callPackage values add
921nix-repl> with values; add { inherit a b; }
923```
924## `callPackage`: добавляем оверрайды
925<!--
926Добавим возможность перегружать значения с помощью последнего аргумента...
927-->
928```
929nix-repl> callPackage = set: f: overrides:
930 f ((builtins.intersectAttrs
931 (builtins.functionArgs f)
932 set)
933 // overrides)
934nix-repl> callPackage values add { }
936nix-repl> callPackage values add { b = 12; }
93715
938```
939## `callPackage`: используем!
940```
941let
942 nixpkgs = import <nixpkgs> {};
943 allPkgs = nixpkgs // pkgs;
944 callPackage = path: overrides:
945 let f = import path;
946 in f ((builtins.intersectAttrs
947 (builtins.functionArgs f)
948 allPkgs)
949 // overrides);
950 pkgs = with nixpkgs; {
951 mkDerivation = import ./generic-builder.nix nixpkgs;
952 hello = callPackage ./hello.nix { };
953 graphviz = callPackage ./graphviz.nix { };
954 graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
955 };
956in pkgs
957```
958## Паттерн `override`
959<!--
960Nix -- это функциональный язык. Одна из идей функциональной парадигмы состоит в композиции функций. Это может быть очень удобно при работе с деривациями. Например, если мы хотим получить `graphviz`, собранный с нашей собственной версией библиотеки gd, на данный момент нам придется заново делать `callPackage ./graphviz.nix { gd = customgd; }` Это неудобно и добавляет бойлерплейт.
961-->
962```
963mygraphviz = callPackage ./graphviz.nix { gd = customgd; }
965VS.
967mygraphviz = pkgs.graphviz.override { gd = customgd }
968```
969## Паттерн `override`: `makeOverridable`
970<!--
971Для реализации нашей затеи напишем функцию `mkOverridable`, которая будет принимать функцию, возвращающую attrset, и attrset (который будет являться "дефолтными" аргументами для данной функции). Вернет `makeOverridable` тот же attrset, что вернула бы данная функция, с добавлением аттрибута `override`, который является функцией, позволяющей нам "перегружать" стандартные аргументы.
972-->
973### `lib.nix`
974```
975rec {
976 makeOverridable = f: origArgs:
977 let
978 origRes = f origArgs;
979 in
980 origRes // {
981 override = newArgs:
982 makeOverridable f (origArgs // newArgs);
983 };
985```
986## Паттерн `override`: потестируем!
987<!--
988Работает! Теперь мы можем легко "перегружать" аргументы пакетов (которые, напомню, просто функции) в нашем репозитории. Нужно только не забыть вызывать все эти пакеты с makeOverridable.
989-->
990```
991nix-repl> :l lib.nix
992Added 1 variables.
993nix-repl> f = { a, b }: { result = a+b; }
994nix-repl> res = makeOverridable f { a = 3; b = 5; }
995nix-repl> res2 = res.override { a = 10; }
996nix-repl> res2
997{ override = «lambda»; result = 15; }
998nix-repl> res2.override { b = 20; }
999{ override = «lambda»; result = 30; }
1000```
1001## Паттерн `override`: объединяем с `callPackage`
1002### `lib.nix`
1003```
1004rec {
1005 [...]
1006 callPackage = pkgs: path: overrides:
1007 let f = import path;
1008 in makeOverridable
1009 f ((builtins.intersectAttrs
1010 (builtins.functionArgs f)
1011 pkgs)
1012 // overrides);
1014```
1015## Паттерн `override`: репозиторий
1016### `default.nix`
1017```
1018let
1019 lib = import ./lib.nix;
1020 nixpkgs = import <nixpkgs> {};
1021 callPackage = lib.callPackage allPkgs;
1022 allPkgs = nixpkgs // pkgs;
1023 pkgs = with nixpkgs; rec {
1024 mkDerivation = import ./generic-builder.nix nixpkgs;
1025 hello = callPackage ./hello.nix { };
1026 graphviz = callPackage ./graphviz.nix { };
1027 graphvizCore = graphviz.override { gdSupport = false };
1028 };
1029in pkgs
1030```
1031## nixpkgs: краткая справка
1033### Общая конфигурация для всех пакетов
1035* `import <nixpkgs> { config = {...}; }`
1036* `$NIXPKGS_CONFIG`
1037* `~/.config/nixpkgs/config.nix`
1040### Перегрузка пакетов
1042```
1043config.packageOverrides = oldPkgs: {
1044 graphviz = oldPkgs.graphviz.override { gd = customgd; };
1046```
1048### Оверлеи
1049```
1050nixpkgs.overlays = [ (self: super: {
1051 graphviz = super.graphviz.override { gd = customgd; };
1052} ) ];
1053```
1055## `stdenv.mkDerivation`: прокачиваем наш `mkDerivation`
1056<!--
1057Наша функция `mkDerivation`, без сомнения, прекарасна, но можно лучше! `stdenv.mkDerivation` из nixpkgs предоставляет интерфейс (т.е принимаемые и возвращаемые значения) очень похожие на наш `mkDerivation`, с несколькими отличиями:
1058-->
1059`stdenv.mkDerivation` может...
1061* Изменять поведение фаз, например `{ buildPhase = "make"; }`
1062* Изменять список фаз `{ phases = [ "buildPhase" ]; }`
1063* Перегружать аргументы с помощью `overrideAttrs`
1064* Автоматически добавлять хуки на проброс зависимостей с помощью `propogatedBuildInputs`
1067## Что осталось за кадром в этом разделе
1069<!--
1070Нельзя объять необъятное -- а nixpkgs действительно необъятен, более 100МБ кода на данный момент -- и поэтому я оставлю изучение частностей на совести слушателя.
1071-->
1073* `stdenv`
1074* `lib`
1075* `fix`
1076* ...
1077<https://nixos.org/nixos/nix-pills> и <https://nixos.org/nixpkgs/manual> -- для тех, кому надо или интересно!