Nix. Что это и с чем это использовать Где это всё взять? Помимо NixOS, где ничего делать не нужно, Nix можно установить на любой (или почти любой) дистрибутив Linux. Для этого достаточно запустить следующую команду: $ sh <(curl -L https://nixos.org/nix/install) Дальше установочный скрипт сам всё сделает. После недавних изменений в MacOS, установка в ней немного осложнилась, раньше было достаточно команды выше. Про установку в последних версиях MacOS можно прочесть  здесь . Язык Nix Когда речь идёт о Nix, часто имеют в виду две разные сущности: Nix как язык и nixpkgs как репозиторий пакетов, в том числе составляющий основу NixOS. Начнём с первого. Nix — функциональный ленивый язык с динамической типизацией. Синтаксис во многом похож на языки семейства ML (SML, OCaml, Haskell), поэтому у тех, кто с ними знаком, особых проблем возникнуть не должно. Начать знакомство с языком можно просто запустив интерпретатор. $ nix repl Welcome to Nix version 2.3.10. Type :? for help. nix-repl> Отдельного синтаксиса для объявления функций в Nix нет. Функции задаются через присваивание, так же как и другие значения. nix-repl> "Hello " + "World!" "Hello World!" nix-repl> add = a: b: a + b nix-repl> add 1 2 3 Как и в языках, повлиявших на Nix, все функции каррированы. nix-repl> addOne = add 1 nix-repl> addOne 3 4 Помимо примитивных типов, таких как числа и строки, Nix поддерживает списки и словари (attribute sets в терминологии Nix). nix-repl> list = [ 1 2 3 ] nix-repl> set = { a = 1; b = list; } nix-repl> set { a = 1; b = [ ... ]; } nix-repl> set.b [ 1 2 3 ] Значения в локальной области видимости можно задать через выражение  let...in . Для примера, простая функция, реализующая факториал, как это принято делать в других статьях по функциональному программированию. fac.nix : let fac = n: if n == 0 then 1 else n * fac (n - 1); in { inherit fac; } Директива  inherit вносит или "наследует" термин из текущей области видимости и даёт ему такое же имя. Пример выше эквивалентен записи let fac = ... in { fac = fac; } . $ nix repl fac.nix Welcome to Nix version 2.3.10. Type :? for help. Loading 'fac.nix'... Added 1 variables. nix-repl> fac 3 6 При загрузке файлов или модулей в REPL, Nix ожидает, что результатом вычисления модуля будет множество, элементы которого будут импортированы в текущую область видимости. Для загрузки кода из других файлов в Nix есть функция  import , принимающая путь к файлу с кодом и возвращающая результат выполнения этого кода. mul.nix : let mul = a: b: a * b; in { inherit mul; } Новый  fac.nix : let multMod = import ./mul.nix; fac = n: if n == 0 then 1 else multMod.mul n (fac (n - 1)); in { inherit fac; } Хотя присваивание модуля в отдельную переменную — довольно частая практика, в данном случае это выглядит несколько нелепо, правда? В Nix есть директива  with , добавляющая в текущую область видимости все имена из множества, переданного в качестве параметра. fac.nix с использованием with : with import ./mul.nix; let fac = n: if n == 0 then 1 else mul n (fac (n - 1)); in { inherit fac; } Сборка программ Сборка программ и отдельных компонентов — это основная функция языка Nix. В случае работы с пакетами, основным инструментом, про который нужно знать, является  Derivation . Сам по себе Derivation — это специальный файл, содержащий рецепт для сборки в машинно-читаемом виде. Для компиляции программы на C, выводящей "Hello World!", derivation выглядит примерно следующим образом: Derive([("out","/nix/store/1nq46fyv3629slgxnagqn2c01skp7xrq-hello-world","","")],[("/nix/store/60xqp516mkfhf31n6ycyvxppcknb2dwr-build-hello.drv",["out"])],["/nix/store/wiviq2xyz0ylhl0qcgfgl9221nkvvxfj-hello.c"],"x86_64-linux","/nix/store/r5lh8zg768swlm9hxxfrf9j8gwyadi72-build-hello",[],[("builder","/nix/store/r5lh8zg768swlm9hxxfrf9j8gwyadi72-build-hello"),("name","hello-world"),("out","/nix/store/1nq46fyv3629slgxnagqn2c01skp7xrq-hello-world"),("src","/nix/store/wiviq2xyz0ylhl0qcgfgl9221nkvvxfj-hello.c"),("system","x86_64-linux")]) Как видно, в этом выражении содержится путь к результату сборки, который получится в итоге, а также пути к исходным файлам, скрипту сборки, и метаданные: имя проекта и платформа. Стоит так же заметить, что пути к исходникам начинаются с  /nix/store . При сборке, Nix копирует всё нужное в эту директорию, после чего сборка происходит в изолированном окружении (sandbox). Таким образом достигается воспроизводимость сборки всех пакетов. Разумеется, никто в здравом уме руками писать такое не станет! Для простых случаев, в Nix есть встроенная функция  derivation , принимающая описание сборки. simple-derivation/default.nix : { pkgs ? import {} }: derivation { name = "hello-world"; builder = pkgs.writeShellScript "build-hello" '' ${pkgs.coreutils}/bin/mkdir -p $out/bin ${pkgs.gcc}/bin/gcc $src -o $out/bin/hello -O2 ''; src = ./hello.c; system = builtins.currentSystem; } Давайте попробуем разобрать этот пример. Весь файл представляет собой определение функции, которая берёт один параметр — словарь, содержащий поле  pkgs . Если оно не было передано при вызове этой функции, используется значение по умолчанию: import {} . derivation — функция, так же принимающая словарь с параметрами сборки: name — имя пакета, builder — сборочный скрипт, src — исходный код, system — система или список систем, под который возможна сборка данного пакета. writeShellScript — функция из nixpkgs , принимающая имя для скрипта и код и возвращающая путь к исполняемому файлу. Для многострочного текста в Nix есть альтернативный синтаксис с двумя парами одинарных кавычек. С помощью команды  nix build , этот рецепт для сборки можно запустить и получить работающий бинарник. $ nix build -f ./simple-derivation/default.nix [1 built] $ ./result/bin/hello Hello World! При запуске nix build , в текущей директории создаётся символическая ссылка result , указывающая на созданный в /nix/store пакет. $ ls -l result lrwxrwxrwx 1 user users 50 Mar 29 17:53 result -> /nix/store/vpcddray35g2jrv40dg1809xrmz73awi-simple $ find /nix/store/vpcddray35g2jrv40dg1809xrmz73awi-simple /nix/store/vpcddray35g2jrv40dg1809xrmz73awi-simple /nix/store/vpcddray35g2jrv40dg1809xrmz73awi-simple/bin /nix/store/vpcddray35g2jrv40dg1809xrmz73awi-simple/bin/hello Сборка программ, продвинутая версия derivation — достаточно низкоуровневая функция, на базе которой в Nix построены куда более мощные примитивы. Для примера, можно рассмотреть сборку широко известной утилиты cowsay . { lib, stdenv, fetchurl, perl }: stdenv.mkDerivation rec { version = "3.03+dfsg2"; pname = "cowsay"; src = fetchurl { url = "http://http.debian.net/debian/pool/main/c/cowsay/cowsay_${version}.orig.tar.gz"; sha256 = "0ghqnkp8njc3wyqx4mlg0qv0v0pc996x2nbyhqhz66bbgmf9d29v"; }; buildInputs = [ perl ]; postBuild = '' substituteInPlace cowsay --replace "%BANGPERL%" "!${perl}/bin/perl" \ --replace "%PREFIX%" "$out" ''; installPhase = '' mkdir -p $out/{bin,man/man1,share/cows} install -m755 cowsay $out/bin/cowsay ln -s cowsay $out/bin/cowthink install -m644 cowsay.1 $out/man/man1/cowsay.1 ln -s cowsay.1 $out/man/man1/cowthink.1 install -m644 cows/* -t $out/share/cows/ ''; meta = with lib; { description = "A program which generates ASCII pictures of a cow with a message"; homepage = "https://en.wikipedia.org/wiki/Cowsay"; license = licenses.gpl1; platforms = platforms.all; maintainers = [ maintainers.rob ]; }; } Оригинал скрипта находится  здесь . stdenv — специальный derivation , содержащий правила сборки для текущей системы: нужный компилятор, флаги и прочие параметры. Основное содержимое — гигантских размеров скрипт на баше под названием setup , который и выступает в роле скрипта builder из нашего простого примера выше. $ nix build nixpkgs.stdenv $ find result/ result/ result/setup result/nix-support $ wc -l result/setup 1330 result/setup mkDerivation — функция, создающая derivation с этим скриптом и заодно заполняющая другие поля. Те читатели, кто раньше писал скрипты для сборки пакетов в Arch Linux или Gentoo, могут увидеть здесь крайне знакомую структуру. Как и в других дистрибутивах, сборка разбита на фазы, присутствует перечисление зависимостей ( buildInputs ) и так далее. Терминология Каррирование (от англ. currying, иногда — карринг) — преобразование функции от многих аргументов в набор вложенных функций, каждая из которых является функцией от одного аргумента.