Переопределение пакетов nixpkgs
Добро пожаловать на семнадцатую пилюлю Nix.
В предыдущей шестнадцатой пилюле мы начали погружение в репозиторий nixpkgs.
Теперь мы знаем, что ’nixpkgs — это функция и мы разобрались, для чего ей параметры
systemи
config`.
Сегодня мы поговорим о специальном атрибуте — config.packageOverrides
.
Переопределение пакетов в наборе с неподвижной точкой можно рассматривать как ещё один паттерн проектирования в nixpkgs
.
Переопределение пакета
Вспомните паттерн проектирования переопределение из пилюли 14, где мы сделали косвенный вызов функции с параметром, вместо того, чтобы вызывать её напрямую.
Мы поместили функцию override
в набор атрибутов, возвращаемый оригинальной функцией.
Возьмём, например, пакет graphviz
.
У него есть входящий параметр xorg
.
Если он равен null
, graphviz
будет собран без поддержки X Window System.
$ nix repl
nix-repl> :l <nixpkgs>
Added 4360 variables.
nix-repl> :b graphviz.override { withXorg = false; }
Этот вызов соберёт graphviz
без поддержки X, всё настолько просто.
Теперь пусть, скажем, пакет P
зависит от graphviz
, как сделать, чтобы P
зависел от новой версии graphviz
без поддержки X?
В императивном мире…
…вы могли бы сделать что-то такое:
pkgs = import <nixpkgs> {};
pkgs.graphviz = pkgs.graphviz.override { withXorg = false; };
build(pkgs.P)
Учитывая, что pkgs.P
зависит от pkgs.graphviz
, легко собрать P
с изменённым graphviz
.
В чистом функциональном языке это не так просто, поскольку ваши переменные иммутабельны и вы не можете присваивать им новые значения.
Неподвижная точка
(Комментарий переводчика: автор цикла с места в карьер использует термин неподвижная точка, который неизвестен разработчикам, не сталкивавшимся с функциональным программированием. Это интересная, но не самая простая концепция — трудно объяснить её «на пальцах». Не вдаваясь в подробности, скажу, что неподвижная точка — остроумное решение парадоксальной задачи. В функциональных языках часто используют лямбда-функции, они же анонимные функции, то есть не имеющие имени. Функцию, у которой есть имя, можно сделать рекурсивной — она может вызвать сама себя по имени. А как сделать рекурсивной анонимную функцию? Ответ — с помощью второй функции специального вида, которая и носит название неподвижной точки. Так что, встретив в тексте «неподвижную точку», вы можете мысленно подставлять вместо вместо неё слова «штука для рекурсии». Этого хватит, чтобы прочитать статью, но я бы посоветовал изучить вопрос глубже. Просто для удовольствия.)
Неподвижная точка с ленивым вычислением — не самая удобная, но необходимая часть языка Nix.
С помощью неё можно решить нашу задачу с пакетами P
и graphviz
.
Вот определение неподвижной точки в nixpkgs:
{
# Взять функцию и запустить её с результатом, который она вернула.
fix =
f:
let
result = f result;
in
result;
}
Это функция, которая принимает функцию f
и вызывает её с параметром result
, а результат вызова снова передаёт функции f
, и так далее.
Иными словами, это f(f(f(...
На первый взгляд, это бесконечный цикл. Но с ленивыми вычислениями — нет, поскольку вызов осуществляется только если он нужен.
nix-repl> fix = f: let result = f result; in result
nix-repl> pkgs = self: { a = 3; b = 4; c = self.a+self.b; }
nix-repl> fix pkgs
{ a = 3; b = 4; c = 7; }
У нас получилось сослаться на a
и b
в том же самом наборе без ключевого слова rec
.
- Сначала вызывается
pkgs
с пока ещё не вычисленными параметрами(pkgs(pkgs(...)
- Чтобы установить значение
c
, вычисляютсяself.a
иself.b
. - Функция
pkgs
вызывается снова, чтобы получить значенияa
иb
.
Трюк заключается в том, что значение c
не нужно во внутреннем вызове, поэтому мы не входим в бесконечный цикл.
Не стану вдаваться здесь в дальнейшие объяснения. Хорошее введение в неподвижную точку можно найти здесь (англ.).
Переопределение набора с помощью неподвижной точки
Учитывая, что self.a
и self.b
ссылаются на переданный набор, а не на константы в определении функции pkgs
, мы можем переопределить a
и b
, и получить новое значение для c
:
nix-repl> overrides = { a = 1; b = 2; }
nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs
{ a = 3; b = 4; c = 3; }
nix-repl> let newpkgs = pkgs (newpkgs // overrides); in newpkgs // overrides
{ a = 1; b = 2; c = 3; }
В первом случае мы вычислили pkgs
с переопределённым набором, а во втором, помимо этого, мы включили переопределённые атрибуты в результат.
Переопределение пакетов nixpkgs
Мы разобрались, как переопределять атрибуты в наборе, чтобы зависящие от них атрибуты имели доступ к новым значениям.
Тот же подход можно использовать и для дериваций.
В конце концов, nixpkgs
— это гигантский набор атрибутов, которые зависят друг от друга.
Для этого nixpkgs
предлагает config.packageOverrides
.
Так nixpkgs
возвращает неподвижную точку набора пакетов, с помощью которой можно переопределять параметры.
Создайте файл config.nix
следующего вида:
{
packageOverrides = pkgs: {
graphviz = pkgs.graphviz.override {
# запретить поддержку xorg
withXorg = false;
};
};
}
Теперь мы можем собрать, скажем, asciidoc-full
который использует переопределённый graphviz
:
nix-repl> pkgs = import <nixpkgs> { config = import ./config.nix; }
nix-repl> :b pkgs.asciidoc-full
Обратите внимание, как мы передаём config
с атрибутом packageOverrides
, когда импортируем nixpkgs
.
Здесь pkgs.asciidoc-full
— это деривация с входящим параметром graphviz
(при этом pkgs.asciidoc
— это облегчённая версия, которая в принципе не использует graphviz
).
Поскольку в кэше исполняемых файлов нет версии asciidoc
с graphviz
без поддержки X Window System, Nix перекомпилирует нужные вам компоненты.
Файл ~/.config/nixpkgs/config.nix
Мы уже обсуждали этот файл в предыдущей пилюле.
Обычно там находится что-то похожее на config.nix
, который мы только что написали.
Файл позволяет избавиться от явной передачи параметров всякий раз, когда мы импортируем nixpkgs
, поскольку загружается в nixpkgs
атоматически.
Заключение
Мы узнали о новом паттерне проектирования: использовании неподвижной точки для переопределения пакетов в наборе.
Императивные пакетные менеджеры устанавливают новые версии библиотек поверх старых. В Nix не всё так просто и прямолинейно. Но более аккуратно.
Приложения Nix зависят от определённых версий библиотек, поэтому нам приходится перекомпилировать asciidoc
, чтобы использовать новую библиотеку graphviz
.
Новая версия asciidoc
будет зависеть от новой версии graphviz
, а старая версия asciidoc
без помех продолжит работать со старой версию graphviz
.
В следующей пилюле
…мы на время оставим изучение nixpkgs
и поговорим о путях хранения.
Как Nix генерирует путь в хранилище, где будет размещена сборка?
И как добавить в хранилище файлы, для которых предусмотрена проверка целостности?