Разработка с помощью nix-shell
Добро пожаловать на десятую пилюлю Nix.
В предыдущей девятой пилюле мы познакомились с одной из мощных возможностей Nix: автоматическим обнаружения зависимостей времени выполнения.
Заодно мы завершили разработку пакета GNU hello
.
В этой пилюле мы познакомимся с утилитой nix-shell
и попробуем с её помощью взломать программу hello
.
Мы узнаем, что nix-shell
создаёт для нас изолированную среду с возможностью редактирования исходных файлов проекта, точно также, как nix-build
создаёт изолированную среду во время сборки деривации.
В конце концов мы сделаем наш скрипт сборки более эргономичным, ориентируясь на возможности nix-shell
Что такое nix-shell
?
Утилита nix-shell помещает нас в командную оболочку с настроенными переменными окружения, необходимыми для сборки деривации. Она не запускает сборку; она готовит плацдарм для пошаговой сборки проекта.
Давайте вспомним, что в Nix у нас нет доступа к библиотекам или программам до тех пор, пока они не установлены с помощью nix-env
.
Впрочем, установка библиотек через nix-env
не считается хорошей практикой.
Вместо этого мы предпочитаем изолированное окружение для разработки, доступное благодаря утилите nix-shell
.
Мы можем передать в nix-shell
любое выражение Nix, возвращающее деривацию, но в переменной PATH
, которая в конечном итоге попадёт в bash
, не будет нужных нам утилит.
$ nix-shell hello.nix
[nix-shell]$ make
bash: make: command not found
[nix-shell]$ echo $baseInputs
/nix/store/jff4a6zqi0yrladx3kwy4v6844s3swpc-gnutar-1.27.1 [...]
Такая оболочка в лучшем случае бесполезна.
Было бы разумно ожидать, что программы, описанные в $buildInputs
, попадают в PATH
(в том числе и программа GNU make
), но в нашем случае это не так.
Однако, у нас есть переменные окружения, которые мы установили в деривации, в частности $baseInputs
, $buildInputs
, $src
и др.
Это значит, что мы можем запустить source
с параметром builder.sh
и она построит деривацию.
На этапе установки у вас может возникнуть ошибка, потому что ваш пользователь не имеет прав записи в /nix/store
:
[nix-shell]$ source builder.sh
...
Деривация не установилась, но она была построена. Обратите внимание вот на что:
- Мы запустили
builder.sh
и он выполнил все шаги сборки, включая настройкуPATH
. - Рабочий каталог — больше не временный каталог, созданный
nix-build
, а каталог, в котором мы запустили оболочку. - Таким образом,
hello-2.10
был распакован в текущий каталог.
Мы можем войти в каталог hello-2.10
и запустить make
, поскольку make
теперь доступен.
Это подтверждает, что nix-shell
помещает нас в оболочку с тем же (или очень похожим) окружением, что и во время сборки.
Сборщик для nix-shell
Предыдущие шаги требуют ручного запуска команд и не оптимизированы для работы с nix-shell
.
Сейчас мы сделаем наш сборщик более дружественным по отношению к nix-shell
.
Вот несколько пунктов, которые нам надо изменить.
Во-первых, когда мы загружаем builder.sh
, мы загружаем его в текущий каталог.
Что мы действительно хотим, так это поместить builder.sh
в хранилище Nix, поскольку утилита nix-build
использует именно этот файл.
Корректный способ в том, чтобы передать в деривацию правильную переменную окружения.
(Обратите внимание, что переменная $builder
уже определена, но она указывает на исполняемый файл bash
вместо builder.sh
.
Наш builder.sh
передаётся в bash
как аргумент.)
Во-вторых, мы не хотим запускать сборку полностью, мы собираемся всего лишь настроить окружение, нужное для ручной сборки проекта.
Так что мы можем разбить builder.sh
на два файла: setup.sh
для настройки окружения и настоящий builder.sh
, который отправится в nix-build
.
В процессе рефакторинга мы завернём этапы сборки в функции, чтобы придать больше структуры нашему дизайну.
Дополнительно, мы перенесём set -e
из файла настройки в файл сборки.
Команда set -e
в nix-shell
раздражает, так как она завершает работу оболочки при возникновении ошибки.
Вот наш исправленный autotools.nix
.
Примечательным является атрибут setup = ./setup.sh
в деривации, который добавляет setup.sh
в хранилище Nix и соответственно инициализирует переменную окружения $setup
в сборщике.
pkgs: attrs:
let
defaultAttrs = {
builder = "${pkgs.bash}/bin/bash";
args = [ ./builder.sh ];
setup = ./setup.sh;
baseInputs = with pkgs; [
gnutar
gzip
gnumake
gcc
coreutils
gawk
gnused
gnugrep
binutils.bintools
patchelf
findutils
];
buildInputs = [ ];
system = builtins.currentSystem;
};
in
derivation (defaultAttrs // attrs)
Благодаря этому мы можем разделить builder.sh
на setup.sh
и builder.sh
.
Задача builder.sh
заключается в том, чтобы загрузить $setup
и вызвать функцию genericBuild
.
Всё остальное — небольшие изменения в скрипте bash
.
Вот исправленная версия builder.sh
:
set -e
source $setup
genericBuild
Вот новая добавленная версия setup.sh
:
unset PATH
for p in $baseInputs $buildInputs; do
export PATH=$p/bin${PATH:+:}$PATH
done
function unpackPhase() {
tar -xzf $src
for d in *; do
if [ -d "$d" ]; then
cd "$d"
break
fi
done
}
function configurePhase() {
./configure --prefix=$out
}
function buildPhase() {
make
}
function installPhase() {
make install
}
function fixupPhase() {
find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null
}
function genericBuild() {
unpackPhase
configurePhase
buildPhase
installPhase
fixupPhase
}
Наконец, вот hello.nix
:
let
pkgs = import <nixpkgs> { };
mkDerivation = import ./autotools.nix pkgs;
in
mkDerivation {
name = "hello";
src = ./hello-2.12.1.tar.gz;
}
Возвращаемся в nix-shell
:
$ nix-shell hello.nix
[nix-shell]$ source $setup
[nix-shell]$
Теперь, скажем, вы можете запустить unpackPhase
, которая распакует $src
и зайдёт в каталог.
И вы можете запускать такие команды, как ./configure
, make
и т.д. вручную, или запускать фазы с помощью соответствующих функций.
Всё правильно: процесс настолько прост, насколько вам кажется. nix-shell
собирает файл .drv
и все его входные зависимости, и затем запускает командную оболочку с настроенными переменными окружения, необходимыми для сборки .drv
.
В частности, переменные окружения в оболочке совпадают с теми, которые передаются в функцию derivation
.
Заключение
С помощью nix-shell
мы можем запустить изолированное окружение, подходящее для разработки проекта.
Это окружение предоставляет необходимые зависимости для оболочки разработчика, подобно тому, как nix-build
предоставляет необходимые зависимости сборщику.
Дополнительно, мы можем собирать и отлаживать проект вручную, выполняя его пошагово, как мы делали бы в любой другой среде разработки.
Заметьте, что мы никогда не устанавливаем такие инструменты, как gcc
или make
в систему; эти инструменты и библиотеки изолированы и доступны попроектно.
В следующей пилюле
В следующей пилюле мы займёмся чисткой хранилища Nix. Мы написали и построили деривации, которые затем добавили в хранилище Nix, но до сего момента мы не беспокоились о том, чтобы удалять неиспользуемые деривации из хранилища.