Добро пожаловать на восемнадцатую пилюлю 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 и других систем сборки.

Комментарии (0)