Добро пожаловать на восемнадцатую пилюлю Nix. В предыдущей семнадцатой пилюле мы разобрались, как «в целом» устроен репозиторий nixpkgs
. Это набор пакетов и мы можем переопределять эти пакеты так, что все остальные пакеты могут их использовать.
Прежде, чем переходить к исследованию дериваций, я бы хотел коснуться темы путей хранения и способа их вычисления. В частности, нас будут интересовать фиксированные пути хранения, зависящие от хеша целостности (такого, как sha256), который обычно есть у tar-архивов.
Способ, которым вычисляются пути хранения, немного запутанный. Так сложилось исторически. Он описан в исходном коде Nix.
Исходные пути
Давайте начнём с простого. Вы знаете, что Nix позволяет использовать относительные пути.
Так как файл или каталог находятся в хранилище Nix, то какой-нибудь ./myfile
лежит где-то в /nix/store/.......
. Мы хотим разобраться, как именно генерируется путь хранения такого файла:
$ echo mycontent > myfile
Напомню, что у простейшей деривации, которую вы можете сделать, должны быть указаны имя (name
), сборщик (buidler
) и система (system
):
$ nix repl
nix-repl> derivation { system = "x86_64-linux"; builder = ./myfile; name = "foo"; }
«derivation /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv»
Теперь загляните внутрь файла .drv
, чтобы узнать, где хранится ./myfile
:
$ nix derivation show /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv
{
"/nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv": {
"outputs": {
"out": {
"path": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo"
}
},
"inputSrcs": [
"/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile"
],
"inputDrvs": {},
"platform": "x86_64-linux",
"builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile",
"args": [],
"env": {
"builder": "/nix/store/xv2iccirbrvklck36f1g7vldn5v58vck-myfile",
"name": "foo",
"out": "/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo",
"system": "x86_64-linux"
}
}
}
Хороший вопрос: почему Nix решил выбрать имя xv2iccirbrvklck36f1g7vldn5v58vck
? Посмотрим, что там написано в исходном коде Nix.
Обратите внимание: запуск nix-store --add myfile
сохранит файл по тому же пути хранения.
Шаг 1: вычисляем хеш файла
Комментарии подсказывают нам, что сначала вычисляется хеш sha256 — таким же образом, как и при сериализации файла в архив NAR. Вручную мы можем это сделать двумя способами:
$ nix-hash --type sha256 myfile
2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3
или:
$ nix-store --dump myfile|sha256sum
2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3
В целом, Nix понимает содержимое двух видов: плоское для обычных файлов и рекурсивное для архивов NAR, где может находиться что угодно.
Шаг 2: строим строковое описание
Затем Nix использует строку специального вида, которая включает хеш, тип пути и имя файла. Сохраним её в отдельный файл:
$ echo -n "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile" > myfile.str
Шаг 3: вычисляем окончательный хеш
Наконец, комментарии подсказывают, что надо вычислить хеш sha256 от строки, полученной на предыдущем шаге, усечь результат до 160 бит и перевести их в представление Base32:
$ nix-hash --type sha256 --truncate --base32 --flat myfile.str
xv2iccirbrvklck36f1g7vldn5v58vck
Выходные пути
Обычно выходные пути генерируется для дериваций. Простой пример с файлом нужен был для того, чтобы показать, как всё работает. Что касается дериваций, то Nix знает, что выходной путь — это hs0yi5n5nw6micqhy8l1igkbhqdkzqa1
, даже если мы не запустили сборку. Причина в том, что выходной путь зависит только от входных параметров.
Он вычисляется практически также, как и пути хранения, за исключением того, что хешируется файл .drv
и тип деривации — это output:out
. В случае нескольких выходных путей, у нас может быть несколько output:<id>
.
В тот момент, когда Nix вычисляет выходной путь, в файле .div
вместо каждого выходного пути хранится пустая строка. Так что мы берём наш .drv
и заменяем выходные пути пустыми строками:
$ cp -f /nix/store/y4h73bmrc9ii5bxg6i7ck6hsf5gqv8ck-foo.drv myout.drv
$ sed -i 's,/nix/store/hs0yi5n5nw6micqhy8l1igkbhqdkzqa1-foo,,g' myout.drv
Файл myout.drv
— это состояние .drv
перед тем, как Nix вычислит выходной путь для нашей деривации:
$ sha256sum myout.drv
1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5 myout.drv
$ echo -n "output:out:sha256:1bdc41b9649a0d59f270a92d69ce6b5af0bc82b46cb9d9441ebc6620665f40b5:/nix/store:foo" > myout.str
$ nix-hash --type sha256 --truncate --base32 --flat myout.str
hs0yi5n5nw6micqhy8l1igkbhqdkzqa1
Теперь Nix записывает выходной путь в файл .drv
и на этом процесс завершается.
В случае, если в .drv
есть входные деривации, которые ссылаются на другие .drv
, вместо путей .drv
используются хеше, рассчитаные по тому же алгоритму.
Иными словами, вы получается окончательный файл .drv
, где все другие файлы .drv
заменены их хешами.
Фиксированные выходящие пути
Существует ещё один вид пути — когда нам известен хеш целостности файла. Обычное дело для tar-архивов.
У деривации может быть три специальных атрибута — outputHashMode
, outputHash
и outputHashAlgo
. Они хорошо документированы в руководстве Nix.
Скрипт сборки должен создать выходной путь и убедиться, что его хеш совпадает с хешем, указанным в атрибуте outputHash
.
Допустим, наш скрипт должен создать файл, который содержит строку mycontent
:
$ echo mycontent > myfile
$ sha256sum myfile
f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb myfile
nix-repl> derivation { name = "bar"; system = "x86_64-linux"; builder = "none"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb"; }
«derivation /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv»
Проверьте .drv
и убедитесь, что, в отличие от предыдущим примеров, в деривации действительно есть информация о фиксированном выходном пути и о хеше, вычисленном по алгоритму sha256:
$ nix derivation show /nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv
{
"/nix/store/ymsf5zcqr9wlkkqdjwhqllgwa97rff5i-bar.drv": {
"outputs": {
"out": {
"path": "/nix/store/a00d5f71k0vp5a6klkls0mvr1f7sx6ch-bar",
"hashAlgo": "sha256",
"hash": "f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb"
}
},
[...]
}
Неважно, какие входные деривации используются, окончательный выходной путь будет зависеть только от объявленного хеша.
Nix создаёт промежуточное строковое представление для содержимого с фиксированным выходным путём:
$ echo -n "fixed:out:sha256:f3f3c4763037e059b4d834eaf68595bbc02ba19f6d2a500dce06d124e2cd99bb:" > mycontent.str
$ sha256sum mycontent.str
423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639 myfile.str
Затем обрабатывает его так же, как и любой другой выходной путь деривации:
$ echo -n "output:out:sha256:423e6fdef56d53251c5939359c375bf21ea07aaa8d89ca5798fb374dbcfd7639:/nix/store:bar" > myfile.str
$ nix-hash --type sha256 --truncate --base32 --flat myfile.str
a00d5f71k0vp5a6klkls0mvr1f7sx6ch
Как видим, путь хранения зависит только от фиксированного выходного хеша.
Заключение
Существуют и другие типы путей хранения, которые обрабатываются таким же образом. Сначала Nix хеширует содержимое, затем создаёт строковое представление, затем вычисляет хеш и — получает окончательный путь хранения.
Кроме того, мы выяснили что Nix'у известен выходной путь деривации до её сборки, поскольку он зависит только от входных параметров. Наконец, мы узнали о деривациях с фиксированным выходным путём, которые nixpkgs
использует, например, для загрузки и проверки tar-архивов с исходниками.
В следующей пилюле
...мы расскажем про stdenv
. В предыдущих пилюлях мы разработали собственную удобную функцию mkDerivation
для создания дериваций.
Теперь мы узнаем, какие ещё удобные функции можно использовать для компиляции проектов autotools
и других систем сборки.