Общие среды разработки в системах MacOS и Linux для согласованных сборок.

Мы разрабатываем большое приложение React Native, которое в значительной степени опирается на собственные компоненты, уже написанные на Java, C ++ и Objective-C. Это означает, что нам нужно было разработать, построить и протестировать множество различных платформ в сложных средах разработки и средствах сборки, которые часто меняются с обновлениями платформы.

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

Некоторые предлагали Dockerize среду, но после нескольких попыток Nix стал нашим любимым инструментом. Nix позволяет нам использовать одну и ту же среду разработки для Linux и macOS с точными зависимостями для таких инструментов, как CMake, Ninja, Android NDK и т. Д. При установленном Nix при открытии репозитория разработчик встречает все необходимые зависимости, доступные в его оболочке. . Мы используем Linux для сборок Android и macOS для сборок как для Android, так и для Apple.

Итак, что такое Nix?

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

Nix уделяет приоритетное внимание согласованности, и для достижения этого он заставляет вас явно объявлять все зависимости и входные данные, одновременно изолировав среду сборки из среды оболочки и Интернета. Из исходного кода создается не только пакет, но и его зависимости и их зависимости, которые могут зависеть друг от друга как узлы в сетке графа.

Nix-env, менеджер пакетов

С nix-env вы можете управлять пользовательскими средами. nix-env создает слой абстракции над bin каталогами в вашем PATH с символическими ссылками на /nix/store. Поскольку он использует ссылки на символические ссылки, он может делать несколько важных вещей:

  1. Он отслеживает версии вашей среды и в O (1) раз может откатиться к другой версии, изменив символическую ссылку на предыдущий профиль.
  2. Установка и удаление являются атомарными. На более позднюю версию не будет ссылаться, пока установка не будет завершена.
  3. Поскольку зависимости не устанавливаются в глобальную папку, несколько пользователей на машине не могут переопределить или скомпрометировать зависимости друг друга, и поэтому им разрешено устанавливать пакеты без прав.

Это возможно, потому что каждая версия пакета устанавливается в отдельный каталог в /nix/store, и удаление зависимости не приводит к ее удалению с диска до тех пор, пока ссылка на нее не будет полностью разорвана и с нее не будет удален сборщик мусора.

Nix берет управление версиями в свои руки, хэшируя инструкции сборки и вводимые данные. Даже малейшее изменение составляет новую версию, поскольку хеш-код другой. Компоненты находятся в Nix Store вместе со всеми их зависимостями, например:

/nix/store/f2rrk276criwxn19bf82cglym4dkv9gr-ninja-1.9.0.drv
/nix/store/iwm3knkdi294rj50w9ai5rdwaglgr362-ninja-1.9.0/

Последние символы - это атрибут name, читаемый человеком. Nix-env управляется командой nix-env и каталогом .nix-profile.

Проблема с установкой на Mac

Nix можно установить как для одного пользователя (владеющего /nix), так и для многопользовательского (в котором root владеет /nix). Однако на Mac ни одна из них больше не будет работать, поскольку корневая файловая система (все, что находится под /) была доступна только для чтения с macOS 10.15. Nix не может легко изменить путь к Nix Store, так как весь их двоичный кеш был скомпилирован с /nix/store в качестве пути. Текущий обходной путь - изменить путь, но смонтировать его как незашифрованный (зашифрованный в состоянии покоя) том APFS.

$ sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume --daemon

Программа установки объяснит, что она будет делать, и запросит доступ суперпользователя, который будет вызывать десятки раз. Вот как выглядит том Nix Store с Дисковой утилитой:

И вот оно в Finder:

Nix-shell, виртуальная среда

Однако именно nix-shell оказал на нас влияние. С nix-shell мы можем создавать виртуальные среды для каждого проекта без необходимости устанавливать зависимости на пользовательском или системном уровне с nix-env.

Просто добавьте shell.nix файл в свой проект. Затем, когда вы вводите nix-shell, среда и все зависимости готовы к использованию. Этот файл, конечно же, передан в систему контроля версий и доступен всем разработчикам. В файле перечислены зависимости, переменные среды и обработчики оболочки, запускаемые при загрузке.

Это может быть дополнительно интегрировано в оболочку с помощью Direnv, который автоматически активирует среду при изменении каталога; и Lorri, процесс-демон, который следит за shell.nix проектами на предмет изменений и автоматически перезагружает среду, если она есть. Niv упрощает управление зависимостями проекта с помощью sources.json файла, как менеджер пакетов более высокого уровня для Nix-shell.

Некоторые предпочитают использовать Nix-shell вместо Nix-env для всех сред пользовательского уровня, поскольку им можно управлять изолированным декларативным способом. Home Manager позволяет настраивать пользовательские (неглобальные) пакеты и точечные файлы. Посмотрите, что вы можете сделать, в NixOS wiki. Наконец, Nix-drawin позволяет настроить ваш Mac так же, как NixOS с файлом configuration.nix.

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

nix-shell --run "node ./index.js".

И можно указать Nix-shell как интерпретатор для файла с шебангом вверху файла:

#! /usr/bin/env nix-shell
#! nix-shell -i real-interpreter -p packages
...

Вышеупомянутый файл будет выполнен внутри nix-shell вместе со своим окружением.

Nix-build, Инструмент сборки

Nix-build - это менеджер сборки, высшим приоритетом которого является правильность. То есть все сборки будут идентичны при одинаковых инструментах сборки и входных данных.

Менеджеры сборки берут источники, такие как исходный код и зависимости, и вызывают генераторы, такие как компиляторы, для создания производных, таких как двоичные файлы. И источники, и производные являются компонентами. Это задача таких инструментов, как Make, CMake, Ant или Gradle.

Сборки Nix основаны на derivation, который представляет собой набор, в котором перечислены точные (хешированные) зависимости и точные (хешированные) сценарии сборки, которые выглядят следующим образом:

Derive([("out","/nix/store/winl36i87aydwj5qgrz0nbc7kq3w0yzi-user-environment","","")],[],["/nix/store/kygr761f08l1nanw27lfxkg8qibf0qn1-env-manifest.nix"],"builtin","builtin:buildenv",[],[("allowSubstitutes",""),("builder","builtin:buildenv"),("derivations","true 5 1 /nix/store/9nqninr2aaicvmq83q10d5a1hwagbzyc-hello-2.10 true 5 1 /nix/store/df26nnjiw55rvv6mxy4kapps9h4kfvw7-niv-0.2.19-bin true 5 1 /nix/store/f3swypnb5zi5yd3w7k2ycwyv6b3sv8fa-direnv-2.28.0 true 5 1 /nix/store/vgdizqicd30k4183ssq7g6i07dvys6xl-home-manager-path true -10 1 /nix/store/4023c0ymrxsg1x36jxmnircqjl1y9fkq-nodejs-14.17.6"),("manifest","/nix/store/kygr761f08l1nanw27lfxkg8qibf0qn1-env-manifest.nix"),("name","user-environment"),("out","/nix/store/winl36i87aydwj5qgrz0nbc7kq3w0yzi-user-environment"),("preferLocalBuild","1"),

Nix Expressions, язык

Вышеупомянутая миниатюрная версия его удобочитаемой версии, функционально написанная с помощью Nix Expression:

Весь файл - это одна функция. Строки с 1 по 6 описывают набор, переданный как единственный параметр. Набор определяет все зависимости, необходимые для создания компонента. : в строке 6 определяет начало тела функции.

Все тело - это вызов stdenv.mkDerivation, который минимизирует данные в выводе, написанном выше. rec - это функция, которая включает рекурсию внутри набора данных, позволяя определять значения в терминах других ключей в наборе.

В дидактических целях синтаксис может быть переписан как лямбда JavaScript следующим образом:

({ stdenv, ... }) => stdenv.mkDerivation(rec({ ... }))

Значение для src извлекается из удаленного URL-адреса и проверяется хешем. src - это ожидаемый ключ для стандартного инструмента сборки, который будет выполнять стандартный сценарий оболочки autoconf (./configure ; make ; make install).

Можно поэкспериментировать с языком Nix в его интерактивной оболочке.

Nixpkgs, репозиторий пакетов

Вышеупомянутая функция еще не вызывается, так как у нас нет параметров для функции. Мы можем достичь того же результата с другим rec, который рекурсивно определяет необходимые компоненты и их параметры. например.,

rec {
  lib1 = import package1/default.nix { };
  program2 = import package2/default.nix { inherit lib1; };
}

Это превращает все зависимости в граф зависимостей, и пока они ациклические, Nix может построить их все. Этот набор можно абстрагировать с помощью функции callPackage. Вот как это сделано в коллекции пакетов Nix в этом удивительном файле all-packages.nix.

Этот файл запрашивается неявно, когда мы устанавливаем пакет в форме:

nix-env -i hello

Это эквивалент:

nix-env -f .../all-packages.nix -i hello

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

Файл для всех пакетов можно изменить с помощью команды nix-channel. Каналы отсортированы по статусу стабильности.

Как я могу установить определенную версию пакета с Nix?

Репозиторий Nixpkgs включает последние версии пакетов (согласно выбранной ветке стабильности). Пакеты зависят друг от друга и строятся как единое целое. Чтобы закрепить конкретную версию зависимости, вы должны полностью переключиться на другую версию Nixpkgs. Отличной утилитой для обратного поиска ревизии Nixpkgs в соответствии с версией пакета является Lazamar’s Nix Package Search.

Лучше всего всегда прикреплять зависимости вашей сборки к определенной версии Nixpkgs для согласованности (как в случае с Docker) и обновлять до последней версии Nixpkgs на Nix-env в соответствии с выбранным вами Nix-каналом ( как если бы вы поступили с Homebrew).

Другие инструменты Nix

  • NixOS - с помощью перечисленных выше примитивов создает и настраивает весь дистрибутив Linux. Вся NixOS определена внутри репозитория Nixpkgs, что невероятно.
  • NixOps - относится к развертыванию облака, развертывает конфигурации системы NixOS на удаленных машинах, а также выделяет облачные ресурсы.
  • Hydra - инструмент CI, который периодически проверяет исходный код проекта, строит его, тестирует и создает отчеты для разработчиков. Hydra используется для проверки статуса стабильности каналов Nix.
  • Flakes - предстоящая функция, которая устранит большую часть хлопот, связанных с закреплением зависимостей с помощью синтаксического сахара. Хеш фиксации каждой зависимости будет храниться в файле flake.lock. Это интуитивно понятно для пользователей NPM / Yarn или Cargo.

Итак, почему не Докер?

Движки Nix и Container, такие как Docker, - два очень разных инструмента. Один - это менеджер пакетов и сборки, другой - механизм изоляции ресурсов, который виртуализирует операционную систему хоста. Оба имеют отличные механизмы кэширования, и оба могут использоваться для согласованных сред на машинах Linux. См. Ниже о том, как Replit перешел с Docker на Nix.

Основная абстракция Docker - это Контейнер: слабо изолированная, легкая, переносимая и инкапсулированная среда, содержащая все необходимое для запуска приложения. Контейнер, который можно запустить, описывается изображением, доступным только для чтения. Образ создается Dockerfile, где каждая директива создает отдельный Layer, помеченный своим криптографическим хешем и кэшируемый.

Как и слои, изображения могут быть построены один поверх другого и вертикально размещены, например, официальный образ узла построен поверх крошечного образа Alpine Linux. Ваше приложение узла, вероятно, будет размещено поверх изображения узла.

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

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

Предпочитайте композицию наследованию

В Docker сложно менять слои, поскольку мы не уверены, что делает каждый компонент и как он повлияет на нижний уровень. Кроме того, разработчики лишены стимулов к изменению более высоких уровней, поскольку они рискуют перестроить все нижние уровни в Dockerfile. Это также является узким местом производительности с точки зрения параллелизма, поскольку Docker создает слои последовательно, и ненужные этапы будут без надобности построены, а затем отброшены.

У Docker есть большое преимущество, которое сразу знакомо как разработчикам, так и операторам. Никс возник как доктор философии. тезис », и иногда такое ощущение. Но дизайн, в котором не учтены изменения, может привести к серьезному изменению дизайна в будущем. Docker хеширует состояния компьютеров, Nix хеширует точные компоненты сборки. Как объяснялось ранее, эти два инструмента служат разным целям.

В нашем случае мы создавали библиотеку для клиентского приложения, поэтому не было необходимости отправлять машинный контейнер, как это было бы в случае разработки микросервиса Node в Kubernetes. Нам просто нужно было совместно использовать согласованную среду сборки для создания воспроизводимых сборок. Кроме того, с nix-shell мы по-прежнему можем использовать наш локальный XCode и остальную часть огороженного сада macOS для наших сборок tvOS и iOS.

Дело о реплите

Replit - это совместная браузерная IDE с поддержкой огромного количества языков. Replit начал с отдельного образа Docker для каждого языка, но пришел к выводу, что проще и эффективнее использовать один монолитный образ: Polygott. Это стало огромным бременем, которое нужно поддерживать, по их собственным словам, поскольку каждый новый пакет создает новый захватывающий способ, которым что-то может сломаться.

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

Как это по сравнению с домашним пивом?

Homebrew - невероятный инструмент, ставший второй натурой для большинства пользователей macOS. Установка работает "из коробки" и интуитивно понятна в использовании.

Как и Nix, Homebrew строит из исходного кода, если не находит «бутылку», то есть предварительно собранный двоичный файл. Точно так же - и по той же причине - Homebrew должен быть установлен в путь по умолчанию (/opt/homebrew в Apple Silicon или /usr/local в Intel), чтобы пользоваться предварительно собранными двоичными файлами. Эта папка называется cellar.

Homebrew использует Ruby для своих формул, которые предоставляют инструкции и метаданные для Homebrew по установке программного обеспечения. Формула определяется как класс, наследуемый от Formula. Это следует объектно-ориентированной парадигме, в отличие от функциональных производных Nix, которые определяются с помощью функции.

class Wget < Formula
  homepage "https://www.gnu.org/software/wget/"
  url "https://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
  sha256 "52126be8cf1bddd7536886e74c053ad7d0ed2aa89b4b630f76785bac21695fcd"

  def install
    system "./configure", "--prefix=#{prefix}"
    system "make", "install"
  end
end

Homebrew можно использовать в Linux (ранее Linuxbrew), хотя в дистрибутивах Linux часто есть популярные менеджеры пакетов. Подобно nix-channels, brew использует Taps, которые являются сторонними репозиториями.

Огромная популярность Homebrew на Mac дает ему преимущество перед надежностью сборки Nix и продуманным графиком зависимостей. Большинство инсталляций предварительно построены и «просто работают».

Заключение

С точки зрения маркетинга я считаю, что Nix не хватает брендов и отличительных названий для своих услуг (кроме Hydra и Flakes), что затрудняет поиск документации. Nix объединил документацию Nix и NixOS, поэтому тривиальный поиск новичков по nix-env легко приводит к решениям по модификации configuration.nix, которая применима только к NixOS.

Использование /nix/store было немного нетрадиционным со стороны Nix, так как оно нарушает директивы FHS. Уместнее было бы положить его где-нибудь под /var. Я не думаю, что macOS следует за FHS, но теперь корневой (/) уровень в macOS доступен только для чтения, и Nix пришлось ломать голову, чтобы найти обходные пути.

Nix не такой интуитивно понятный, как другие инструменты сборки, но он отличается точностью. Таким образом, он нацелен на строгость науки и показывает упорный труд академических кругов. Он был принят сообществами функциональных языков, таких как Haskell и NixOS, что вызвало интерес всего сообщества Linux.