@@ -1,860 +0,0 @@
1−<div id="вступление" class="slide section level1">
4−<meta charset="utf8" />
5−<title>Заметки выступающего</title>
7−<p>Основано на <a href="https://nixos.org/nixos/nix-pills" class="uri">https://nixos.org/nixos/nix-pills</a> . Огромная благодарность <span class="citation">@Lethalman</span> и <span class="citation">@grahamc</span> за написание и поддержку этого материала!</p>
8−<p>Я хотел бы, чтобы к концу презентации всем стало понятно</p>
10−<li>Что такое Nix</li>
11−<li>Как собрать пакеты из <code>contractor</code> с помощью nix</li>
12−<li>Что находится в <code>contractor/nix/pkgs</code></li>
13−<li>Что находится в <code>contractor/nix/overlay.nix</code></li>
14−<li>Как писать несложные выражения на nix, не совершая неочевидных ошибок</li>
16−<p>Вы можете прекратить слушать после любой секции, и я надеюсь, что вы унесете какую-то полезную для себя информацию.</p>
17−<p>Исходники, собранный pdf и заметки докладчика для этой презентации находятся на <a href="https://github.com/typeable/nix-pills-ru" class="uri">https://github.com/typeable/nix-pills-ru</a></p>
19−Как вы все скорее всего знаете, мы используем Nix для сборки и тестирования нашего кода. Я, Александр Бантьев (@balsoft) отвечаю за инфраструктуру сборки пакетов (Hydra, NixOps), а также за поддержание инструкций сборки в актуальном состоянии. Я бы хотел рассказать вам про nix с помощью вольного пересказа отличной серии статей ["Nix pills"](https://nixos.org/nixos/nix-pills)
21−<h2 id="что-такое-nix">Что такое nix?</h2>
23−Говоря "Nix", мы подразумеваем одну из двух его ипостасей -- Nix как язык или Nix как пакетный менеджер. Такая путаница возникла из-за того, что как DSL Nix имеет смысл только в связке со своим пакетным менеджером, а как пакетный менеджер он без своего языка слишком сложен в использовании, и Eelco Dolstra (создатель Nix) решил не разделять их.
25−<p><a href="https://nixos.org/nix">Nix</a> – это</p>
26−<ol style="list-style-type: decimal">
29−<li>Декларативный</li>
30−<li>Функциональный</li>
33−<li>Динамически типизированный</li>
39−<li>Source/Binary</li>
42−<h2 id="экосистема">Экосистема</h2>
44−Nix довольно быстро стал широко известен в узких кругах, и вокруг него возникла большая экосистема декларативных, атомарных инструментов, в том числе:
46−* nixpkgs, репозиторий описаний пакетов на языке nix;
47−* NixOS, дистрибутив GNU/Linux с интересным подходом к конфигурации системы;
48−* NixOps, инструментарий для разворачивания виртуальных машин и приложений на них на базе NixOS;
49−* Hydra, CI -- сервер;
50−* Disnix, система для разворачивания распределенных систем.
52−<p><img src="images/graphviz-8192fc5615858231309e589f5892c04c8314b9078de8fb84b070abd59b0b1934.png" alt="digraph {
53− graph [ dpi = 200; font = Hack; ];
54− subgraph cluster_nix {
55− "nix (пакетный менеджер)" -> "nix (язык)";
56− "nix (язык)" -> "nix (пакетный менеджер)";
59− cluster_nix -> Disnix;
60− cluster_nix -> Hydra;
61− cluster_nix -> nixpkgs -> NixOS -> NixOps;
64−<div id="nix-как-пакетный-менеджер" class="slide section level1">
65−<h1>Nix как пакетный менеджер</h1>
67−<p>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.</p>
69−<p><em>— nixos.org/nix</em></p>
71−На https://nixos.org/nix заявлено, что nix -- это все-таки пакетный менеджер. С этого компонента мы и начнем.
73−<h2 id="хранилище-nix-store">Хранилище (nix store)</h2>
75−Центральным местом файловой системы для Nix является Хранилище (nix store). Именно здесь хранятся все файлы, с которыми оперирует наш пакетный менеджер: инструкции сборки и её результаты -- исполняемые файлы, документация, заголовочные файлы, etc.
77−Все элементы директории `/nix/store` имеют одинаковый формат имени -- `<hash>-<name>[-version][-output]`. Хэш вычисляется при добавлении в хранилище или при сборке на основе содержимого файла либо содержимого деривации (инструкции сборки), из которого этот путь был собран.
80−<p>In the beginning was the Store, and the Store was with Nix, and the Store was Nix.</p>
82−<p><em>— Nix manual, 1:1</em></p>
83−<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><a class="sourceLine" id="cb1-1" title="1">$ <span class="fu">ls</span> /nix/store <span class="kw">|</span> <span class="fu">wc</span> -l</a>
84−<a class="sourceLine" id="cb1-2" title="2"><span class="ex">46251</span></a>
85−<a class="sourceLine" id="cb1-3" title="3">$ <span class="ex">nix</span> add-to-store nix.conf</a>
86−<a class="sourceLine" id="cb1-4" title="4"><span class="ex">/nix/store/za34q8y0jcx3qsrnbrd005mh8zcnlrlr-nix.conf</span></a></code></pre></div>
87−<h3 id="формат-имени">Формат имени</h3>
88−<pre><code><hash>-<name>[-version][-output]</code></pre>
90−<pre><code>/nix/store/04mm93j20jlnwvhh21db674b3vy6fs54-pandoc-2.7.1-data
91− | hash | name | ver | out |</code></pre>
92−<h2 id="принцип-работы">Принцип работы</h2>
94−В общих чертах, получение результата любого выражения (в терминологии ПМ -- пакета) на nix состоит из двух этапов -- вычисления (за это отвечает nix как язык), и сборки -- именно эта часть ложится на плечи nix как пакетного менеджера.
96−<p><img src="images/graphviz-a226e3212ab86c6caf182261f96850451237da3d2f6e3a4ed9b2eb38cb343d17.png" alt="digraph {
97− graph [ dpi = 200; font = Hack; ];
98− "myname.nix" -> "/nix/store/...-myname.drv" [ label = " Вычисление (nix как язык)" ];
99− "/nix/store/...-myname.drv" -> "/nix/store/...-myname" [ label = " Сборка (nix как пакетный менеджер)" ];
101−<h2 id="деривации-derivations-.drv-files">Деривации (derivations, <code>.drv</code> files)</h2>
103−Для сборки пакетов любому пакетному менеджеру нужны инструкции. Например, ABS (Arch build system) использует PKGBUILD, portage (Gentoo) использует EBUILD, brew использует recepies. Nix не является исключением, и для сборки он использует Деривации (.drv). Пример деривации (с добавленными мною переносами строки и пробелами для улучшения читаемости) -- на слайде.
105−<h3 id="nixstorez3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv"><code>/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv</code></h3>
108− ,"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname","","")]
111−,"mysystem"
112−,"mybuilder"
114−,[("builder","mybuilder")
115− ,("name","myname")
116− ,("out","/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname")
117− ,("system","mysystem") ] )</code></pre>
118−<h2 id="краткая-справка-по-инструментарию">Краткая справка по инструментарию</h2>
119−<h3 id="собрать-buildrealize-деривацию">Собрать (build/realize) деривацию</h3>
120−<pre><code>$ nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv</code></pre>
121−<h3 id="вычислить-evaluate-и-затем-собрать-содержимое-файла-default.nix">Вычислить (evaluate) и затем собрать содержимое файла <code>default.nix</code></h3>
122−<pre><code>$ nix build -f default.nix # или nix-build default.nix</code></pre>
123−<h3 id="вычислить-собрать-и-запустить-интерактивное-окружение">Вычислить, собрать и запустить интерактивное окружение</h3>
124−<pre><code>$ nix run -f default.nix # или nix-shell default.nix</code></pre>
126−<div id="nix-как-язык" class="slide section level1">
127−<h1>Nix как язык</h1>
129−В общих чертах разобравшись с пакетным менеджером, перейдем к языку. Большая часть синтаксиса, конструкций и идей будет знакома вам по другим языкам, поэтому мы быстро пробежимся по примерам, останавливаясь лишь на уникальных чертах и граблях, на которые легко наступить.
131−<p>Рассмотрим основы синтаксиса Nix.</p>
132−<p>Краткая инструкция для желающих повторить:</p>
133−<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><a class="sourceLine" id="cb8-1" title="1">$ <span class="ex">curl</span> https://nixos.org/nix/install <span class="kw">|</span> <span class="fu">sh</span></a>
134−<a class="sourceLine" id="cb8-2" title="2"><span class="op"><</span><span class="ex">...</span><span class="op">></span></a>
135−<a class="sourceLine" id="cb8-3" title="3">$ <span class="ex">nix</span> repl</a></code></pre></div>
136−<h2 id="числа-арифметика-и-комментарии">Числа, арифметика и комментарии</h2>
137−<pre><code>nix-repl> :t 4 # Показать тип выражения
139−nix-repl> 1 + 2 - 3 * 2
143−nix-repl> builtins.add 1 2
145−nix-repl> # Однострочный комментарий
146−nix-repl> /* Многострочный комментарий */</code></pre>
147−<h3 id="замечание">Замечание</h3>
148−<p>Арифметические операторы лучше обносить пробелами! У символов <code>/</code> и <code>-</code> есть особые значения в Nix</p>
149−<pre><code>nix-repl> 6/2
150−/home/balsoft/6/2</code></pre>
151−<h2 id="булевы-булева-арифметика-и-if">Булевы, булева арифметика и if</h2>
152−<pre><code>nix-repl> :t true
154−nix-repl> true && false
156−nix-repl> true || false
158−nix-repl> if true || false then 3 else 4
160−nix-repl> if false then 5 else 6
162−<h2 id="строки-strings">Строки (strings)</h2>
163−<pre><code>nix-repl> "foo"
165−nix-repl> :t "foo"
167−nix-repl> "Привет"
169−nix-repl> ''foo''
171−nix-repl> ''
175−"foo\nbar\n"</code></pre>
176−<h2 id="переменные-и-стороковая-интерполяция">Переменные и стороковая интерполяция</h2>
177−<pre><code>nix-repl> foo = "hello"
178−nix-repl> "${foo}, world!"
179−"hello, world!"</code></pre>
180−<h3 id="замечание-1">Замечание</h3>
181−<p>Названия переменных могут содержать знак <code>-</code></p>
182−<pre><code>nix-repl> foo-bar = 10
185−<p>Но не могут содержать не-ASCII!</p>
186−<pre><code>nix-repl> привет
187−error: syntax error, unexpected $undefined, at (string):1:1</code></pre>
188−<h2 id="как-избежать-интерполяции">Как избежать интерполяции?</h2>
189−<p>Довольно часто встречаются ситуации, когда мы хотим вставить в строку последовательность <code>${...}</code> (например, в bash-код).</p>
190−<pre><code>nix-repl> "\${foo}"
193−nix-repl> ''test ''${foo} test''
194−"test ${foo} test"</code></pre>
195−<h2 id="пути-paths-и-url">Пути (paths) и URL</h2>
196−<pre><code>nix-repl> /bin/sh
198−nix-repl> :t /bin/sh
200−nix-repl> ./hello # Относительно текущего .nix файла или PWD для repl
202−nix-repl> https://typeable.io # Синтаксический сахар для строк
203−"https://typeable.io"
204−nix-repl> :t https://typeable.io
205−a string</code></pre>
206−<h3 id="замечание-2">Замечание</h3>
207−<p>Пути, как и названия переменных, не могут содержать не-ASCII. Я бы рассматривал это, как баг.</p>
208−<pre><code>nix-repl> /home/balsoft/Документы
209−error: path '/home/balsoft/' has a trailing slash</code></pre>
210−<h2 id="nix_path-и-nixpkgs"><code>NIX_PATH</code> и <code><nixpkgs></code></h2>
212−`NIX_PATH` -- переменная, очень похожая на `PATH` по синтаксису значений (значения разделены двоеточием), в которой nix ищет пути запрошенные через синтаксис `<path>`.
214−<pre><code>$ NIX_PATH=home=$HOME nix repl
215−nix-repl> <home>
218−$ NIX_PATH=/home nix repl
219−nix-repl> <balsoft>
222−nix-repl> <nixpkgs>
223−/nix/store/zzjhgd7p1y879z0y9pz70vjd45yfi9iz-nixpkgs</code></pre>
224−<h2 id="списки-lists">Списки (lists)</h2>
225−<p>Списки в Nix иммутабельны и могут содержать значения любых типов вперемешку.</p>
226−<pre><code>nix-repl> [ 2 "foo" true (2+3) ]
227−[ 2 "foo" true 5 ]
229−<h3 id="замечание-3">Замечание</h3>
230−<p>Обратите внимание: элементы списка не разделены запятыми!</p>
231−<pre><code>nix-repl> [ 1 + 2 ]
232−error: syntax error, unexpected '+', at (string):1:5
233−nix-repl> [ (1 + 2) ]
235−nix-repl> [ import ./hello.nix {} ]
236−[ «primop-app» /home/balsoft/hello.nix { ... } ]</code></pre>
237−<h2 id="множества-аттрибутов-attribute-sets-attrsets">Множества аттрибутов (attribute sets, attrsets)</h2>
238−<pre><code>nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; }
242−{ "123" = "num"; a-b = "baz"; foo = "bar"; }
245−nix-repl> s."123"
247−nix-repl> s.${toString (122 + 1)}
248−"num"</code></pre>
249−<h2 id="множества-аттрибутов">Множества аттрибутов</h2>
250−<pre><code>nix-repl> s ? "123"
256−nix-repl> s.abc or "not found" # Note that `or` is a keyword
257−"not found"
258−nix-repl> s // { a = 10; }
259−{ "123" = "num"; a = 10; a-b = "baz"; foo = "bar"; }
260−nix-repl> rec { a = 3; b = a + 4; } # Recursive attrset
261−{ a = 3; b = 7; }</code></pre>
262−<h2 id="let-with-и-inherit">Let, with и inherit</h2>
263−<pre><code>nix-repl> let a = 3; b = 4; in a + b
265−nix-repl> let a = 4; b = a + 5; in b # Let definitions are recursive
267−nix-repl> let a = 3; a = 8; in a
268−error: attribute `a' at (string):1:12 already defined at (string):1:5
269−nix-repl> longName = { a = 3; b = 4; }
270−nix-repl> with longName; a + b
272−nix-repl> let a = 10; b = 20; in { inherit a; b = b + 1; }
274−nix-repl> { inherit (s) "123" foo; }
275−{ "123" = "num"; foo = "bar"; }</code></pre>
276−<h2 id="функции-functions">Функции (functions)</h2>
277−<pre><code>nix-repl> x: x*2
279−nix-repl> double = x: x * 2
280−nix-repl> :t double
282−nix-repl> double 3
284−nix-repl> greet = end: name: "Hello, ${name} ${end}"
285−nix-repl> greet "dredozubov" "!"
286−"Hello, dredozubov !"
287−nix-repl> greetExclamaition = greet "!"
288−nix-repl> greetExclamaition "s9gf4ult"
289−"Hello, s9gf4ult !"</code></pre>
290−<h2 id="паттерн-матчинг">Паттерн-матчинг</h2>
291−<pre><code>nix-repl> greet = { name, end }: "Hello, ${name} ${end}"
292−nix-repl> greet { name = "ak3n"; exc = "!"; }
293−"Hello, ak3n !"
294−nix-repl> greet = { name, end ? "!" }: "Hello, ${name} ${end}"
295−nix-repl> greet { name = "ak3n"; }
296−"Hello, ak3n !"
297−nix-repl> greet { name = "ak3n"; end = "."; }
298−"Hello, ak3n ."
299−nix-repl> greet = { name, end ? "!", ... }: "Hello, ${name} ${end}"
300−nix-repl> greet { name = "ak3n"; end = "."; extra = "foo"; }
301−"Hello, ak3n ."
302−nix-repl> foo = {a, ...}@args: args // { a = a + 3; }
303−nix-repl> foo { a = 10; b = 11; }
304−{ a = 13; b = 11; }</code></pre>
305−<h2 id="подключения-файлов-import">Подключения файлов (import)</h2>
306−<h3 id="number.nix"><code>number.nix</code></h3>
307−<pre><code>3</code></pre>
308−<h3 id="function.nix"><code>function.nix</code></h3>
309−<pre><code>a: a + 3</code></pre>
310−<h3 id="обратно-в-наш-уютный-repl">Обратно в наш уютный repl</h3>
311−<pre><code>nix-repl> a = import ./number.nix
312−nix-repl> f = import ./function.nix
315−<h2 id="деривации-derivation">Деривации (derivation)</h2>
317−Вот мы и подошли к моменту, когда мы можем сделать свою собственную деривацию! Исследуем встроенную функцию `derivation`. Она возвращает attrset, но nix показывает его как `derivation`. Странные дела!
319−<pre><code>nix-repl> d = derivation {
320− name = "myname";
321− builder = "mybuilder";
322− system = "mysystem"; }
326−«derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv»</code></pre>
327−<h2 id="деривации-сборка">Деривации: сборка</h2>
329−Как вы помните, деривации -- это инструкции сборки для nix. Попробуем собрать нашу фейковую деривацию. nix подсказывает, что у нас система не `mysystem`, а `x86\_64-
330−linux`. Исправим это и попробуем ещё раз. Теперь проблема в том, что файла `mybuilder`, который должен собрать наш пакет, не существует. Fail. Остановимся и исследуем `derivation` поподробнее.
332−<pre><code>nix-repl> :b d
334−error: a `mysystem' is required to build ..., but I am a `x86_64-linux'
335−nix-repl> d = derivation {
336− name = "myname";
337− builder = "mybuilder";
338− system = builtins.currentSystem; }
341−[...]: executing 'mybuilder': No such file or directory</code></pre>
342−<h2 id="деривации-что-внутри">Деривации: что внутри?</h2>
344−Так как derivation -- это обычный attrset, мы можем исследовать его аттрибуты с помощью встроенного инструментария. Заметим, что `drvAttrs` содержит аргументы, переданные в функцию `derivation`, `out` совпадает с самой деривацией (т.к. на данный момент у нашей деривации только один результат), ну и восхитимся магией типизации на аттрибутах (так вот почему nix так странно себя ведет, показывая derivation вместо сета!). Также заметим, что `toString` превращает в строки только те сеты, которые содержат аттрибут `outPath`.
346−<pre><code>nix-repl> builtins.attrNames d
347−[ "all" "builder" "drvAttrs" "drvPath" "name"
348− "out" "outPath" "outputName" "system" "type" ]
349−nix-repl> d.drvAttrs
350−{ builder = "mybuilder"; name = "myname"; system = "x86_64-linux"; }
351−nix-repl> (d == d.out)
353−nix-repl> { type = "derivation"; } # Типизация истинных джентельменов
355−nix-repl> toString d
356−"/nix/store/qaq9ir9w2a34dzkjmk3igfbibi5q59sd-myname"
357−nix-repl> toString { outPath = "blah"; }
359−nix-repl> toString { a = 10; }
360−error: cannot coerce a set to a string, at (string):1:1</code></pre>
361−<h2 id="деривации-добавляем-зависимости">Деривации: добавляем зависимости</h2>
362−<pre><code>nix-repl> :l <nixpkgs> # nixpkgs -- это огромная куча дериваций!
363−Added 10082 variables.
364−nix-repl> coreutils
365−«derivation /nix/store/...-coreutils-8.30.drv»
366−nix-repl> toString coreutils
367−"/nix/store/...-coreutils-8.30"
368−nix-repl> d = derivation {
369− name = "myname";
370− builder = "${coreutils}/bin/true";
371− system = builtins.currentSystem; }
373−builder for '...-myname.drv' failed to produce output path '...-myname'</code></pre>
374−<h2 id="деривации-смотрим-на-зависимости">Деривации: смотрим на зависимости</h2>
376−`nix show-derivation` показывает содержимое деривации в виде pretty-printed JSON. Как мы видим, у нас в зависимостях появилась ссылка на coreutils.drv, и builder стал указывать на true.
378−<pre><code>$ nix show-derivation /nix/store/...-myname.drv
380− "/nix/store/apvmj79y84wivsnjc2vkv2q79a37nf7l-myname.drv": {
382− "inputDrvs": {
383− "...-coreutils-8.30.drv": [
387− "builder": "...-coreutils-8.30/bin/true",
391−<h2 id="деривации-рабочая-деривация">Деривации: рабочая деривация!</h2>
393−Исправим ошибку, которая не дает нам получить нашу первую собирающуюся деривацию! Создадим скрипт `builder.sh`, который создаст outPath. Обратите внимание: мы не будем указывать hashbang для нашего скрипта, потому что даже сам исполняемый файл bash лежит в `/nix/store`, и путь к нему мы можем определить только из nix. Из-за невозможности добавить hashbang, будем запускать наш сборщик как `bash builder.sh`:
395−<h3 id="builder.sh"><code>builder.sh</code></h3>
396−<div class="sourceCode" id="cb35"><pre class="sourceCode bash"><code class="sourceCode bash"><a class="sourceLine" id="cb35-1" title="1"><span class="bu">declare</span> -xp</a>
397−<a class="sourceLine" id="cb35-2" title="2"><span class="bu">echo</span> foo <span class="op">></span> <span class="va">$out</span></a></code></pre></div>
398−<h3 id="уютный-nix-repl">Уютный <code>nix repl</code></h3>
399−<pre><code>nix-repl> :l <nixpkgs>
400−Added 10082 variables.
401−nix-repl> d = derivation {
402− name = "foo";
403− builder = "${bash}/bin/bash";
404− args = [ ./builder.sh ];
405− system = builtins.currentSystem; }
407−this derivation produced the following outputs:
408− out -> /nix/store/82jrv90z3aj1lmzr437jpp7m9krnrrzf-foo</code></pre>
409−<h2 id="деривации-посмотрим-на-env">Деривации: посмотрим на env</h2>
411−Посмотрим на то, что показал `declare -xp` (т.е. на переменные окружения, которые доступны внутри сборщика). `nix log` показывает вывод сборщика при сборке определенного пути.
413−* `$HOME` указывает в пустой `/homeless-shelter`
415−* `$PWD` указывает во временную директорию `/build` (на самом деле она находистся в `/tmp`)
417−Замечу, что все аргументы derivation окажутся в env сборщика.
419−<pre><code>$ nix log /nix/store/...-foo
421−declare -x HOME="/homeless-shelter"
422−declare -x NIX_BUILD_CORES="0"
423−declare -x NIX_BUILD_TOP="/build"
424−declare -x NIX_LOG_FD="2"
425−declare -x NIX_STORE="/nix/store"
426−declare -x PATH="/path-not-set"
427−declare -x PWD="/build"
428−declare -x TEMP="/build"
429−declare -x builder="/nix/store/...-bash-4.4-p23/bin/bash"
430−declare -x name="foo"
431−declare -x out="/nix/store/82jrv90z3aj1lmzr437jpp7m9krnrrzf-foo"
432−declare -x system="x86_64-linux"</code></pre>
433−<h2 id="деривации-опакетим-простую-программу-на-c">Деривации: опакетим простую программу на C</h2>
435−Теперь, когда мы умеем создавать рабочие деривации, добавлять зависимости и передавать произвольные переменные в env их сборщика, попробуем опакетить простую программу на C.
436−На этот раз вылезем из привычного `nix repl` и напишем файл!
438−Для сборки программы нам нужны coreutils (для создания выходной директории) и gcc (для сборки). К счастью, эти (и десятки тысяч других) приложения уже опакечены в nixpkgs. Передадим их деривации как аргументы нашему `derivation`, и они окажутся в env сборщика.
440−<h3 id="simple.c"><code>simple.c</code></h3>
441−<div class="sourceCode" id="cb38"><pre class="sourceCode c"><code class="sourceCode c"><a class="sourceLine" id="cb38-1" title="1"><span class="dt">void</span> main() {</a>
442−<a class="sourceLine" id="cb38-2" title="2"> puts(<span class="st">"Simple!"</span>);</a>
443−<a class="sourceLine" id="cb38-3" title="3">}</a></code></pre></div>
444−<h3 id="simple_builder.sh"><code>simple_builder.sh</code></h3>
445−<div class="sourceCode" id="cb39"><pre class="sourceCode bash"><code class="sourceCode bash"><a class="sourceLine" id="cb39-1" title="1"><span class="bu">export</span> <span class="va">PATH=</span><span class="st">"</span><span class="va">$coreutils</span><span class="st">/bin:</span><span class="va">$gcc</span><span class="st">/bin"</span> <span class="co"># Переменные из derivation</span></a>
446−<a class="sourceLine" id="cb39-2" title="2"><span class="fu">mkdir</span> -p <span class="va">$out</span>/bin <span class="co"># Создадим выходную директорию</span></a>
447−<a class="sourceLine" id="cb39-3" title="3"><span class="fu">gcc</span> -o <span class="va">$out</span>/bin/simple <span class="va">$src</span> <span class="co"># Соберем нашу программу</span></a></code></pre></div>
449−<div class="slide section level1">
451−<h3 id="simple.nix"><code>simple.nix</code></h3>
452−<pre><code>with (import <nixpkgs> {});
454− name = "simple";
455− builder = "${bash}/bin/bash";
456− args = [ ./simple_builder.sh ]; # Сборщик
457− inherit gcc coreutils; # Зависимости
459− system = builtins.currentSystem;
461−<h3 id="поехали">Поехали!</h3>
462−<pre><code>$ nix build -f simple.nix
463−[1 built, 0.0 MiB DL]
464−$ ./result/bin/simple
470−<div class="slide section level1">
472−<h3 id="посмотрим-граф-зависимостей-nix-store--q-result---graph">Посмотрим граф зависимостей (<code>nix-store -q result --graph</code>)</h3>
473−<p><img src="images/graphviz-51a061c1cc85aa203111e4121c911fb0fa438cb89e96a16302b17a4be9c75885.png" alt="digraph G {
475−"/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [label = "simple", shape = box, style = filled];
476−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "black"];
477−"/nix/store/9sll05wmn9b05f0pm1pwfmmq92fg0syh-simple.c" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "red"];
478−"/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "green"];
479−"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" -> "/nix/store/p6a11dai9b7xw3xgf44pgyr5kdlnr9rf-simple" [color = "blue"];
480−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" [label = "glibc-2.27", shape = box, style = filled];
481−"/nix/store/9sll05wmn9b05f0pm1pwfmmq92fg0syh-simple.c" [label = "simple.c", shape = box, style = filled];
482−"/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [label = "gcc-7.4.0", shape = box, style = filled];
483−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "magenta"];
484−"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "burlywood"];
485−"/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "black"];
486−"/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" -> "/nix/store/d4n93jn9fdq8fkmkm1q8f32lfagvibjk-gcc-7.4.0" [color = "red"];
487−"/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" [label = "gcc-7.4.0-lib", shape = box, style = filled];
488−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib" [color = "green"];
489−"/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" [label = "zlib-1.2.11", shape = box, style = filled];
490−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/iiymx8j7nlar3gc23lfkcscvr61fng8s-zlib-1.2.11" [color = "blue"];
491−"/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [label = "glibc-2.27-dev", shape = box, style = filled];
492−"/nix/store/5lyvydxv0w4f2s1ba84pjlbpvqkgn1ni-linux-headers-4.19.16" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "magenta"];
493−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "burlywood"];
494−"/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" -> "/nix/store/sr4253np2gz2bpha4gn8gqlmiw604155-glibc-2.27-dev" [color = "black"];
495−"/nix/store/5lyvydxv0w4f2s1ba84pjlbpvqkgn1ni-linux-headers-4.19.16" [label = "linux-headers-4.19.16", shape = box, style = filled];
496−"/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" [label = "glibc-2.27-bin", shape = box, style = filled];
497−"/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27" -> "/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin" [color = "red"];
500−<div id="стандартные-паттерны-в-nix.-nixpkgs." class="slide section level1">
501−<h1>Стандартные паттерны в Nix. nixpkgs.</h1>
502−<h2 id="mkderivation"><code>mkDerivation</code></h2>
504−Итак, у нас есть мощный и специализированный язык, а также пакетный менеджер, который предоставляет удобную, изолированную среду для сборки пакетов. Для того, чтобы опакечивать софт было проще, создадим функцию, которая будет обобщать `derivation` и уменьшать бойлерплейт. Назовем эту функцию...
506−<p>Неудобства <code>derivation</code></p>
508−<li>Очень большая часть написанного кода будет повторяться из пакета в пакет
510−<li>Распаковка (чаще всего достаточно <code>tar -xf</code>)</li>
511−<li>Добавление зависимостей (<code>gcc</code>, <code>coreutils</code>, <code>bash</code>, <code>awk</code>, <code>make</code>, …)</li>
512−<li>Сборка (очень часто можно обойтись <code>./configure && make</code>)</li>
513−<li>Установка (<code>make install</code>)</li>
515−<li>Требует как минимум два файла – builder и expression</li>
516−<li>После вычисления уже нельзя поменять отдельные аргументы, а это может быть удобно</li>
518−<p>Создадим свой <code>derivation</code> <del>c блекджеком и шлюхами</del> без этих недостатков!</p>
519−<h2 id="mkderivation-зависимости-и-стандартный-сборщик"><code>mkDerivation</code>: зависимости и стандартный сборщик</h2>
521−Для начала, напишем сборщик в файле `generic-builder.sh`. Сначала поставим свой `$PATH`, собрав его из содержимого `buildInputs`. Затем распакуем архив, переданный нам в `$src` и перейдем в свежесозданную директорию. Там сконфигурируем, соберем и установим наш autotools-проект.
523−<h3 id="generic-builder.sh"><code>generic-builder.sh</code></h3>
526−for p in $buildInputs $baseInputs; do
527− export PATH=$p/bin${PATH:+:}$PATH
531− if [ -d "$d" ]; then
536−./configure --prefix=$out
538−make install</code></pre>
540−<div class="slide section level1">
543−В nix-части напишем функцию, которая создает деривацию с нашим сборщиком и некоторыми пакетами по-умолчанию, которые будут полезны при создании любого пакета.
545−<h3 id="generic-builder.nix"><code>generic-builder.nix</code></h3>
546−<pre><code>pkgs: attrs:
548− let defaultAttrs = {
549− builder = "${bash}/bin/bash";
550− args = [ ./builder.sh ];
551− baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped
552− coreutils gawk gnused gnugrep findutils ];
554− system = builtins.currentSystem;
557−derivation (defaultAttrs // attrs)</code></pre>
558−<h2 id="mkderivation-добавляем-фазы"><code>mkDerivation</code>: добавляем фазы</h2>
560−Создадим файл `setup.sh`, в котором определим стандартные "фазы" сборки, которые затем будем использовать в `builder.sh`.
562−<pre><code>unset PATH
563−for p in $baseInputs $buildInputs; do
564− export PATH=$p/bin${PATH:+:}$PATH
567−function unpackPhase() {
571− if [ -d "$d" ]; then
578−<div class="slide section level1">
580−<pre><code>function configurePhase() {
581− ./configure --prefix=$out
583−function buildPhase() {
586−function installPhase() {
589−function genericBuild() {
597−<div class="slide section level1">
599−<h3 id="builder.sh-1"><code>builder.sh</code></h3>
602−genericBuild</code></pre>
604−<div class="slide section level1">
606−<h3 id="generic-builder.nix-1"><code>generic-builder.nix</code></h3>
607−<pre><code>pkgs: attrs:
609− let defaultAttrs = {
610− builder = "${bash}/bin/bash";
611− args = [ ./builder.sh ];
613− baseInputs = [ gnutar gzip gnumake gcc binutils-unwrapped
614− coreutils gawk gnused gnugrep findutils ];
616− system = builtins.currentSystem;
619−derivation (defaultAttrs // attrs)</code></pre>
620−<h2 id="mkderivation-воспользуемся-нашей-функцией"><code>mkDerivation</code>: воспользуемся нашей функцией!</h2>
622−Отлично! Наша функция умеет собирать autotools-проекты, при этом `builder.sh` получился очень простым благодаря `setup.nix`. Теперь если мы хотим собрать проект не на основе autotools, мы можем это легко сделать, пропустив некоторые фазы в builder.
624−<h3 id="default.nix"><code>default.nix</code></h3>
626− pkgs = import <nixpkgs> {};
627− mkDerivation = import ./generic-builder.nix pkgs;
630− name = "hello";
631− src = ./hello-2.10.tar.gz;
633−<h2 id="пакеты-как-функции-от-зависимостей-паттерн-inputs">Пакеты как функции от зависимостей: паттерн inputs</h2>
635−Сделаем наш репозиторий ещё удобнее, вынеся определения пакетов в отдельные файлы. В каждом таком файле будет находиться функция, принимающая в качестве аргументов зависимости пакета и возвращающая деривацию (который мы будем создавать с помощью нашего `mkDerivation`)
637−<h3 id="hello.nix"><code>hello.nix</code></h3>
638−<pre><code>{ mkDerivation }:
640− name = "hello";
641− src = ./hello-2.10.tar.gz;
643−<h3 id="default.nix-1"><code>default.nix</code></h3>
645− pkgs = import <nixpkgs> {};
646− mkDerivation = import ./generic-builder.nix pkgs;
649− hello = import ./hello.nix { inherit mkDerivation };
651−<h2 id="сборка-с-библиотеками-nix_cflags_compile-nix_ldflags">Сборка с библиотеками: <code>NIX_CFLAGS_COMPILE</code>, <code>NIX_LDFLAGS</code></h2>
653−Если мы хотим собирать пакеты, которые зависят от сторонних библиотек, нам понадобится ещё немного магии: нужно указать GCC и ld, где искать заголовочные и объектные файлы библиотек. Для этого существуют переменные `NIX CFLAGS COMPILE` и `NIX LDFLAGS`
655−<h3 id="generic-builder.sh-1"><code>generic-builder.sh</code></h3>
657−for p in $baseInputs $buildInputs; do
659− if [ -d $p/include ]; then
660− export NIX_CFLAGS_COMPILE="-I $p/include${NIX_CFLAGS_COMPILE:+ }\
661−$NIX_CFLAGS_COMPILE"
663− if [ -d $p/lib ]; then
664− export NIX_LDFLAGS="-rpath $p/lib -L \
665−$p/lib${NIX_LDFLAGS:+ }$NIX_LDFLAGS"
670−<div class="slide section level1">
672−<h3 id="graphviz.nix"><code>graphviz.nix</code></h3>
673−<pre><code>{ mkDerivation, gdSupport ? true, gd, fontconfig, libjpeg, bzip2 }:
676− name = "graphviz";
677− src = ./graphviz-2.38.0.tar.gz;
678− buildInputs = if gdSupport
679− then [ gd fontconfig libjpeg bzip2 ]
683−<div class="slide section level1">
685−<h3 id="default.nix-2"><code>default.nix</code></h3>
687− pkgs = import <nixpkgs> {};
688− mkDerivation = import ./generic-builder.nix pkgs;
691− hello = import ./hello.nix { inherit mkDerivation };
692− graphviz = import ./graphviz.nix { inherit mkDerivation
693− gd fontconfig libjpeg bzip2; };
694− graphvizCore = import ./graphviz.nix {
695− inherit mkDerivation gd fontconfig libjpeg bzip2;
699−<h2 id="паттерн-callpackage">Паттерн <code>callPackage</code></h2>
701−Здорово! Мы сделали свой небольшой репозиторий с красивой файловой структурой, научились собирать пакеты с помощью autotools с возможностью легко перейти на другие системы сборки и даже собирать пакеты с библиотеками. Теперь пора уменьшить бойлерплейт. Можно заметить, что в корневом файле репозитория мы повторяем названия аргументов функции при её вызове. Это будет происходить постоянно при структуре репозитория, похожего на наш. Для того, чтобы исправить эту проблему, мы создадим функцию `callPackage`, которая будет вызывать функцию из указанного файла, автоматически передавая ей аргументы из указанного attrset. Например:
703−<h3 id="default.nix-3"><code>default.nix</code></h3>
705− pkgs = import <nixpkgs> {};
706− mkDerivation = import ./generic-builder.nix pkgs;
709− hello = callPackage ./hello.nix { };
710− graphviz = callPackage ./graphviz.nix { };
711− graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
714−<div class="slide section level1">
717−Для начала разберемся с тем, как же нам найти названия аргументов функции. В nix для этого существует встроенная функция `builins.functionArgs`. Она возвращает attrset, в котором названию аргументов соответствует наличие у них значения по-умолчанию.
719−<pre><code>nix-repl> add = { a ? 3, b }: a+b
720−nix-repl> builtins.functionArgs add
721−{ a = true; b = false; }</code></pre>
723−Также в nix существует удобный builtin для "пересечения" двух attrset-ов. Он возвращает attrset, в котором имена аттрибутов -- пересечение имен аттрибутов его аргументов, а их значения берутся из второго аргумента. Например:
725−<pre><code>nix-repl> values = { a = 3; b = 5; c = 10; }
726−nix-repl> builtins.intersectAttrs values (builtins.functionArgs add)
727−{ a = true; b = false; }
728−nix-repl> builtins.intersectAttrs (builtins.functionArgs add) values
729−{ a = 3; b = 5; }</code></pre>
730−<h2 id="простой-вариант-callpackage">Простой вариант <code>callPackage</code></h2>
732−Обладая этими знаниями, напишем простую реализацию нашего callPackage.
734−<pre><code>nix-repl> callPackage = set: f:
735− f (builtins.intersectAttrs
736− (builtins.functionArgs f)
738−nix-repl> callPackage values add
740−nix-repl> with values; add { inherit a b; }
742−<h2 id="callpackage-добавляем-оверрайды"><code>callPackage</code>: добавляем оверрайды</h2>
744−Добавим возможность перегружать значения с помощью последнего аргумента...
746−<pre><code>nix-repl> callPackage = set: f: overrides:
747− f ((builtins.intersectAttrs
748− (builtins.functionArgs f)
751−nix-repl> callPackage values add { }
753−nix-repl> callPackage values add { b = 12; }
755−<h2 id="callpackage-используем"><code>callPackage</code>: используем!</h2>
757− nixpkgs = import <nixpkgs> {};
758− allPkgs = nixpkgs // pkgs;
759− callPackage = path: overrides:
760− let f = import path;
761− in f ((builtins.intersectAttrs
762− (builtins.functionArgs f)
765− pkgs = with nixpkgs; {
766− mkDerivation = import ./generic-builder.nix nixpkgs;
767− hello = callPackage ./hello.nix { };
768− graphviz = callPackage ./graphviz.nix { };
769− graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
772−<h2 id="паттерн-override">Паттерн <code>override</code></h2>
774−Nix -- это функциональный язык. Одна из идей функциональной парадигмы состоит в композиции функций. Это может быть очень удобно при работе с деривациями. Например, если мы хотим получить `graphviz`, собранный с нашей собственной версией библиотеки gd, на данный момент нам придется заново делать `callPackage ./graphviz.nix { gd = customgd; }` Это неудобно и добавляет бойлерплейт.
776−<pre><code>mygraphviz = callPackage ./graphviz.nix { gd = customgd; }
780−mygraphviz = pkgs.graphviz.override { gd = customgd }</code></pre>
781−<h2 id="паттерн-override-makeoverridable">Паттерн <code>override</code>: <code>makeOverridable</code></h2>
783−Для реализации нашей затеи напишем функцию `mkOverridable`, которая будет принимать функцию, возвращающую attrset, и attrset (который будет являться "дефолтными" аргументами для данной функции). Вернет `makeOverridable` тот же attrset, что вернула бы данная функция, с добавлением аттрибута `override`, который является функцией, позволяющей нам "перегружать" стандартные аргументы.
785−<h3 id="lib.nix"><code>lib.nix</code></h3>
787− makeOverridable = f: origArgs:
789− origRes = f origArgs;
793− makeOverridable f (origArgs // newArgs);
796−<h2 id="паттерн-override-потестируем">Паттерн <code>override</code>: потестируем!</h2>
798−Работает! Теперь мы можем легко "перегружать" аргументы пакетов (которые, напомню, просто функции) в нашем репозитории. Нужно только не забыть вызывать все эти пакеты с makeOverridable.
800−<pre><code>nix-repl> :l lib.nix
802−nix-repl> f = { a, b }: { result = a+b; }
803−nix-repl> res = makeOverridable f { a = 3; b = 5; }
804−nix-repl> res2 = res.override { a = 10; }
806−{ override = «lambda»; result = 15; }
807−nix-repl> res2.override { b = 20; }
808−{ override = «lambda»; result = 30; }</code></pre>
809−<h2 id="паттерн-override-объединяем-с-callpackage">Паттерн <code>override</code>: объединяем с <code>callPackage</code></h2>
810−<h3 id="lib.nix-1"><code>lib.nix</code></h3>
813− callPackage = pkgs: path: overrides:
814− let f = import path;
816− f ((builtins.intersectAttrs
817− (builtins.functionArgs f)
821−<h2 id="паттерн-override-репозиторий">Паттерн <code>override</code>: репозиторий</h2>
822−<h3 id="default.nix-4"><code>default.nix</code></h3>
824− lib = import ./lib.nix;
825− nixpkgs = import <nixpkgs> {};
826− callPackage = lib.callPackage allPkgs;
827− allPkgs = nixpkgs // pkgs;
828− pkgs = with nixpkgs; rec {
829− mkDerivation = import ./generic-builder.nix nixpkgs;
830− hello = callPackage ./hello.nix { };
831− graphviz = callPackage ./graphviz.nix { };
832− graphvizCore = graphviz.override { gdSupport = false };
835−<h2 id="nixpkgs-краткая-справка">nixpkgs: краткая справка</h2>
836−<h3 id="общая-конфигурация-для-всех-пакетов">Общая конфигурация для всех пакетов</h3>
838−<li><code>import <nixpkgs> { config = {...}; }</code></li>
839−<li><code>$NIXPKGS_CONFIG</code></li>
840−<li><code>~/.config/nixpkgs/config.nix</code></li>
842−<h3 id="перегрузка-пакетов">Перегрузка пакетов</h3>
843−<pre><code>config.packageOverrides = oldPkgs: {
844− graphviz = oldPkgs.graphviz.override { gd = customgd; };
846−<h3 id="оверлеи">Оверлеи</h3>
847−<pre><code>nixpkgs.overlays = [ (self: super: {
848− graphviz = super.graphviz.override { gd = customgd; };
850−<h2 id="что-осталось-за-кадром-в-этом-разделе">Что осталось за кадром в этом разделе</h2>
852−Нельзя объять необъятное -- а nixpkgs действительно необъятен, более 20МБ кода на данный момент -- и поэтому я оставлю изучение частностей на совести слушателя.
855−<li><code>stdenv</code></li>
856−<li><code>lib</code></li>
857−<li><code>fix</code></li>
859−<p><a href="https://nixos.org/nixos/nix-pills" class="uri">https://nixos.org/nixos/nix-pills</a> и <a href="https://nixos.org/nixpkgs/manual" class="uri">https://nixos.org/nixpkgs/manual</a> – для тех, кому надо или интересно!</p>