Добро пожаловать на семнадцатую пилюлю 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 генерирует путь в хранилище, где будет размещена сборка? И как добавить в хранилище файлы, для которых предусмотрена проверка целостности?
 
          