Содержание


Функциональный менеджер пакетов Nix

Часть 3. Описание сборки пакета

Comments

Серия контента:

Этот контент является частью # из серии # статей: Функциональный менеджер пакетов Nix

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Функциональный менеджер пакетов Nix

Следите за выходом новых статей этой серии.

1. Введение

Особенность Nix состоит в том, что сборка пакета (перед установкой) производится не в пользовательском окружении (с такими компонентами, как набор установленных пакетов, утилит, доступных библиотек, назначенных переменных окружения, определенная версия компилятора и т.п.), а в обособленном окружении сборки. Окружение сборки формируется (с необходимыми компонентами) на основе описания на языке Nix-выражений. Таким образом, разработчик коллекции пакетов описывает окружение сборки, удостоверяется в том, что пакет собирается в этом окружении и передает описание пользователю. Со стороны пользователя на основе этого описания формируется аналогичное окружение сборки, в котором пакет гарантированно собирается (если не возникло проблем с зависимыми компонентами или не допущена ошибка в описании).

2. Подготовка

Для удобства и предотвращения возникновения конфликтов с коллекцией Nixpkgs разместим набор собственных описаний отдельно. Расположите его там, где вам удобно, например, в домашнем каталоге: ~/pkgs.

3. Стандартное окружение сборки

Стандартное окружение сборки
Стандартное окружение сборки
Стандартное окружение сборки

Большинство операций по сборке пакетов требуют однотипного набора средств (shell, компилятор), утилит и имеют схожую последовательность операций. Все эти возможности и предоставляет стандартное окружение. Набор средств включает: GNU GCC, make, coreutils, findutils, diffutils, sed, grep, awk, tar, gzip, bzip2, bash, patch. Для специфичной сборки стандартное окружение расширяется необходимыми утилитами и конкретизируются или настраиваются определенные операции. Как решается эта задача, мы рассмотрим на примере простейшего описания пакета.

4. Простейшее описание пакета

Сначала разберемся с механизмами преобразования Nix-выражения в объект хранилища.

В каталоге ~/pkgs создаем подкаталог с названием hello. В нем создаем файл default.nix со следующим содержанием:

{ system ? builtins.currentSystem
, pkgs ? import /home/user/.nix-defexpr/channels/nixpkgs_stable { inherit system;}
, stdenv ? pkgs.stdenv
}:
stdenv.mkDerivation {
  name = "hello-0.1";
  
  builder = builtins.toFile "builder.sh" ''
    source $stdenv/setup
    echo Hello, world>$out/hello'';
}

это Nix-выражение представляет собой функцию (вспоминаем из предыдущей статьи конструкцию функции). В фигурных скобках "{...}:" определяются три ее параметра, для каждого из которых назначается значение по умолчанию (через знак вопроса):

system – определяет идентификатор платформы. Встроенная функция builtins.currentSystem возвращает идентификатор платформы, на которой производится вычисление.

pkgs – набор атрибутов, содержащий описания пакетов из Nixpkgs. Значением по умолчанию является результат импорта выражения по указанному пути. Путь указывает на каталог с коллекцией описаний Nixpkgs, его нужно подкорректировать, указав домашний каталог пользователя и версию коллекции (stable, unstable). Если вы не подписаны ни на один из каналов (как подписываться на канал, смотрите в статье "Базовое использование"), вы можете непосредственно скачать коллекцию пакетов с ресурсов, указанных в конце статьи, распаковать её в удобном для вас месте и указать путь к ней.

Импортированной функции передается параметр system, который берется из локального контекста.

stdenv – стандартное окружение сборки. Оно определено в той же коллекции описаний Nixpkgs.

В теле этой функции (то, что идет за двоеточием) производится вызов функции стандартного окружения mkDerivation. Она принимает минимальный необходимый набор параметров:

name – название сборки (пакета). Это название является частью идентификатора в хранилище Nix и используется в операциях управления пакетами для указания нужного пакета.

builder – файл скрипта для сборки пакета. С помощью встроенной функции toFile на месте создается файл с указанным содержимым.

Функция mkDerivation производит следующее:

  • создает временную директорию (по умолчанию в каталоге /tmp), устанавливает её как текущую;
  • обнуляет состояние оболочки (в том числе переменные PATH, HOME);
  • вычисляет криптографический хеш, который будет являться идентификатором этого пакета в хранилище Nix и соответственно путем (/nix/store/lg683d...-test-0.1);
  • путь назначения удаляется, если он уже был, и блокируется для предотвращения множественного выполнения сборки одного и того же пакета в одно и то же время;
  • передает управление скрипту сборки builder.sh;

Строка source $stdenv/setup инициализирует оболочку, в частности, присваивает переменной out путь назначения в хранилище, дополнительно объявляет фазы сборки и вспомогательные функции оболочки (такие, как ensureDir).

С помощью вспомогательной функции оболочки ensureDir создается директория назначения, если она не присутствует.

  • если скрипт завершился кодом 0, то считается, что сборка прошла успешно;
  • временная директория удаляется (если nix-env не была передана опция -K);
  • если сборка завершилась неудачно, то установка отменяется с выводом соответствующего сообщения;
  • каталогу пакета в хранилище со всеми его подкаталогами и файлами назначаются стандартные атрибуты: время последнего доступа – 00:00:01 1/1/1970 UTC, группа-владелец принимает значение по умолчанию, режим доступа выставляется в 0444 или 0555, для выполняемых файлов доступ на выполнение остается.

Пробуем:

$ nix-build ~/pkgs/hello/default.nix
store derivation is /nix/store/dgd4wrm649fckpylirxjnj2lsfj4k2ia-hello-0.1.drv
/nix/store/nimanjimbb4q650x9hfp4ifgjxiyws3g-hello-0.1

Утилита nix-build вычисляет переданное её Nix-выражение, производит сборку и создает в текущем каталоге символическую ссылку result, указывающую на результирующую сборку из хранилища Nix (/nix/store/niman...-hello-0.1):

 $ ls -l ./result
lrwxrwxrwx 1 user users 53 Ноя  1 13:10 ./result -> /nix/store/niman...-hello-0.1

Проверяем:

 $ cat ./result/hello 
Hello, world

Что же у нас получилось? Пока мы имеем только описание сборки пакета без исходных кодов и зависимостей, всё его содержимое – один файл hello, который формируется сборочным скриптом. Название пакета – hello-0.1. На основании описания сформирована сборка в хранилище Nix, а в текущем каталоге находится ссылка на него. Да, кстати, ссылку можно удалять, но содержимое каталога, на который она ссылается (в хранилище), изменять нельзя.

Теперь доработаем выражение так, чтобы по нему можно было установить пакет в систему, и в пользовательском окружении появилась команда (скрипт) hello, выводящая приветствие. Заодно рассмотрим еще один базовый параметр src функции mkDerivation, который принимает файл исходников; обычно это архив, но в нашем простейшем случае это будет просто скрипт hello. Изменяем параметры функции mkDerivation:

...
stdenv.mkDerivation {
  name = "hello-0.1";

  src = builtins.toFile "hello" ''
    #!/bin/sh
    echo Hello, world'';

  builder = builtins.toFile "builder.sh" ''
    source $stdenv/setup
    ensureDir $out/bin
    cp $src $out/bin/hello
    chmod a+x $out/bin/hello'';
}

В сборочном скрипте доступна переменная $src, которая содержит путь к файлу исходников, в данном случае это hello. Скрипт hello копируется в директорию назначения, и ему назначаются права на выполнение.

Вместо использования функции builtins.toFile можно создать два файла – hello и builder.sh, расположив их рядом с default.nix, а аргументам src и builder указать пути к этим файлам:

...
  src = ./hello;
  builder = ./builder.sh;
}

Запустив nix-build видим, что теперь в result появилась директория bin с исполняемым скриптом hello. Устанавливаем в систему утилитой nix-env:

 $ nix-env -f ~/pkgs/hello/default.nix -i hello

Параметр -f указывает файл, из которого брать сведения о пакетах (достаточно указать каталог, в котором находится default.nix), -i принимает название пакета для установки. Если не возникло ошибок при установке, то в пользовательском окружении стала доступна ссылка на скрипт hello:

 $ which hello
/nix/var/nix/profiles/default/bin/hello
 $ hello
Hello, world

Минимальное описание готово.

Возьмем реальный пакет с исходными текстами и зависимостями, например, xbindkeys. Описание для него будет выглядеть так (создаем директорию ~/pkgs/xbindkeys с файлом default.nix):

{ system ? builtins.currentSystem
, pkgs ? import /home/user/.nix-defexpr/channels/nixpkgs_stable { inherit system;}
, stdenv ? pkgs.stdenv
, fetchurl ? pkgs.fetchurl
, pkgconfig ? pkgs.guile
, guile ? pkgs.guile
, libX11 ? pkgs.xlibs.libX11
}:
stdenv.mkDerivation {
  name = "xbindkeys-1.8.3";
  
  src = fetchurl {
    url = http://hocwp.free.fr/xbindkeys/xbindkeys-1.8.3.tar.gz;
    sha256 = "1d2n9yb6vnvjp5gxfm0xmi5g05wydbfihqvr6pn85wqrvkzk706a";
  };

  buildInputs = [ pkgconfig guile libX11 ];
}

Для сборки и запуска xbindkeys требует pkgconfig, Guile, libX11 – описания этих пакетов представлены в коллекции Nixpkgs (stable, unstable). Набор зависимостей передается функции mkDerivation через атрибут buildInputs в виде списка. Таким образом, в окружении сборки станут доступны утилита pkgconfig и библиотеки guile, libX11. Функция fetchurl загружает файл по указанному url, проверяет его хеш с указанным sha256; если они совпадают, то возвращает путь к загруженному файлу (файл помещается в хранилище). Хеш архива определяем командой:

$ nix-prefetch-url http://hocwp.free.fr/xbindkeys/xbindkeys-1.8.3.tar.gz

Команда заодно помещает архив в хранилище, так что при установке повторная загрузка архива производиться не будет.

Пакет xbindkeys использует для сборки стандартную последовательность команд: ./configure; make; make install. mkDerivation по умолчанию использует скрипт сборки с такой же последовательностью команд, поэтому определять его заново не имеет смысла.

Установка производится командой:

 $ nix-env -f ~/pkgs/xbindkeys -i xbindkeys

5. Фазы сборки

Стандартная процедура сборки разбита на последовательность фаз. Каждая из них настраивается соответствующим набором переменных; кроме того, для каждой фазы можно установить ловушки пред- и после- выполнения, а при необходимости и целиком ее переопределить.

Ниже перечислены фазы с названиями соответствующих функций (атрибутов) и некоторых переменных-настроек (полное описание фаз смотрите в Nixpkgs Manual):

  • Распаковка архива исходных текстов (unpackPhase)

    unpackCmd – команда для распаковки специфичного архива (например, "${p7zip}/bin/7za x $curSrc";);

  • Применение набора исправлений к исходным текстам

    patches – список файлов патчей; patchFlags – аргументы команде patch;

  • Конфигурация

    preConfigure, postConfigure – выполняются до и после конфигурации соответственно;

  • Сборка (buildPhase)

    buildFlags – дополнительные параметры, передаваемые команде make; makeFlags – то же, что buildFlags, только эти параметры также передаются make на этапах установки и проверки;

  • Проверка

    doCheck – выполнять ли проверку (true/false);

  • Установка (installPhase)

    preInstall, postInstall – выполняются до и после установки соответственно;

  • Послеустановочная настройка (fixupPhase)

Существуют два способа для того, чтобы переопределить фазу, ловушку или установить значение переменной.

Первый вариант – через параметры функции mkDerivation. Например, пакет не содержит Makefile, вместо этого компиляция производится вручную:

stdenv.mkDerivation {
  name = "fnord-4.5";
  ...
  buildPhase = ''
    gcc foo.c -o foo
  '';
  installPhase = ''
    ensureDir $out/bin
    cp foo $out/bin
  '';
}

Определены две фазы: buildPhase – фаза сборки, installPhase – фаза установки.

Другой способ – использовать скрипт сборки (как это делалось в примере hello-0.1):

stdenv.mkDerivation {
  name = "libfoo-1.2.3";
  ...
  builder = ./builder.sh;
}

где builder.sh:

source $stdenv/setup
buildPhase() {
  echo "... this is my custom build phase ..."
  gcc foo.c -o foo
}
installPhase() {
  ensureDir $out/bin
  cp foo $out/bin
}
genericBuild

Переопределяются фазы buildPhase, installPhase, определенные в $stdenv/setup, и вызывается главная процедура сборки genericBuild, она также определена в $stdenv/setup.

6. Единый список пакетов

Допустим, что для сборки пакета потребовалась библиотека, описания которой нет в коллекции Nixpkgs. Конечно, мы можем её определить в самом описании пакета:

{pkgs ? ...}:
let
  libfoo = stdenv.mkDerivation {...};
in
stdenv.mkDerivation {
  ...
  buildInputs = [ libfoo ...];
}

Но вставлять её определение для каждого нашего пакета, которому она понадобится, неудобно и неэффективно. Поэтому создадим общий список описаний наших пакетов. Для примера возьмем всё тот же пакет xbindkeys. В каталоге ~/pkgs создаем файл default.nix следующего содержания:

let
 pkgs = import /home/user/.nix-defexpr/channels/nixpkgs_stable { inherit system;};
 stdenv = pkgs.stdenv;
 system = builtins.currentSystem;
 xlibs = pkgs.xlibs;
 fetchurl = pkgs.fetchurl;
in
rec {
 xbindkeys = (import ./xbindkeys) {
   inherit system stdenv fetchurl;
   inherit (pkgs) pkgconfig guile;
   inherit (xlibs) libX11;
 };
}

В разделе let объявляются локальные атрибуты. В in определяется рекурсивный набор атрибутов, содержащий описания наших пакетов. Выражение для xbindkeys импортируется из соответствующего каталога из файла default.nix, при этом импортированной функции передается набор атрибутов. Этот набор атрибутов формируется следующим образом: функция inherit берет атрибуты из локального контекста и из наборов атрибутов pkgs, xlibs. Запись "system = system;" была бы аналогична записи "inherit system;", а запись "pkgconfig=pkgs.pkgconfig;" – записи "inherit (pkgs) pkgconfig;".

Тогда описание xbindkeys примет вид:

{ system
, stdenv
, fetchurl
, pkgconfig
, guile
, libX11}:
stdenv.mkDerivation {
  name = "xbindkeys-1.8.3";
  src = fetchurl {
    url = http://hocwp.free.fr/xbindkeys/xbindkeys-1.8.3.tar.gz;
    sha256 = "1d2n9yb6vnvjp5gxfm0xmi5g05wydbfihqvr6pn85wqrvkzk706a";
  };
  buildInputs = [ pkgconfig guile libX11 ];
}

Значения по умолчанию параметров функции не используются, вместо этого значения принимаются при импорте выражения.

Установка осуществляется командой:

 $ nix-env -f ~/pkgs -i xbindkeys

Теперь в список пакетов ~/pkgs/default.nix вставляем определение общей библиотеки и передаем её параметром нужным пакетам:

...
rec {
...
  libfoo = (import ./libfoo) {...};
  bar = (import ./bar){... inherit libfoo;};
  baz = (import ./baz){... inherit libfoo;};
...
}

Команда:

 $ nix-env -f ~/pkgs -qa '*'
xbindkeys-1.8.3
libfoo-0.2
bar-0.1
baz-03

выведет список определенных нами пакетов.

7. Заключение

Освоив написание Nix-выражений, вы можете включать собственные описания пакетов в SVN-репозитарий коллекции Nixpkgs. А в следующей статье мы полностью перейдем на Nix, установив дистрибутив NixOS и немного его настроив.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=481111
ArticleTitle=Функциональный менеджер пакетов Nix: Часть 3. Описание сборки пакета
publish-date=04082010