- PVSM.RU - https://www.pvsm.ru -

Дистрибуция приложений. Часть 1: создание Formula для Homebrew

Дистрибуция приложений. Часть 1: создание Formula для Homebrew - 1

Вступление к серии

Передо мной недавно встала задача, как распространять одну консольную утилиту? Обычные мои инструменты вроде pip, npm и gem не подходили в силу языка самой утилиты — bash. Тогда стало понятно, что нужно распространять свое приложение в том числе и через системные пакетные менеджеры. Для Mac — в силу отсутствия встроенного — таких пакетных менеджеров несколько. И у каждого из них есть свои особенности и недостатки. И в первой части я хочу более подробно остановиться на Homebrew [1], и как создавать пакеты для него.

Ну а чтобы установить приложения на Linux, то нужно будет собирать пакеты таких форматов: .tar.gz, .deb и .rpm. О чем я расскажу во второй части.

Терминология

Прежде всего договоримся о терминах [2], ведь в мире Homebrew достаточно специфических словечек. В статье я буду использовать оригинальные термины, чтобы смотрелось не очень комично. Итак:

Термин Описание Пример
Formula (Формула) Определение пакета /usr/local/Library/Formula/foo.rb
Keg (Бочонок) Префикс для установки Formula /usr/local/Cellar/foo/0.1
opt prefix (префикс) Симлинк на активную версию Keg /usr/local/opt/foo
Cellar (Погреб) Сюда устанавливаются все Keg /usr/local/Cellar
Tap (Кран) Дополнительные репозитории с Formula или плагинами /usr/local/Library/Taps/homebrew/homebrew-versions
Bottle (Бутылка) Собранная версия Keg qt-4.8.4.mavericks.bottle.tar.gz

Как устроен Homebrew

Нельзя оставить без внимания устройство самого инструмента, о нем прекрасно рассказал [3] один из его создателей [4]:

Homebrew основан на двух вещах: Git и Ruby. Когда вы устанавливаете Homebrew в первый раз, то он создает копию официального репозитория у вас в /usr/local. Каждый раз, когда вы обновляете Homebrew командой brew update — происходит git pull, а еще вам покажут какие формулы изменились. Когда вы устанавливаете что-либо командой brew install, то Homebrew скачивает архив (в дальнейшем файлы будут браться из кеша), проверяет его хеш и затем выполняет установку. Но на самом деле Formula может быть намного более сложной: в ней могут быть зависимости, особенности загрузки: svn, ftp и прочее. А еще Homebrew, когда возможно, предоставляет уже собранные пакеты (автоматически созданные ботом).

Создание своей собственной Formula

Допустим у вас уже есть некий релиз на github (а если нет, то создайте его [5] при помощи git tag). Я буду работать на примере своего скрипта. Теперь самый простой способ создать Formula будет вызвать команду brew create https://github.com/sobolevn/git-secret/archive/v0.1.0.tar.gz:

Вот что получается

# Documentation: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md
#                http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
class GitSecret < Formula
  desc ""
  homepage ""
  url "https://github.com/sobolevn/git-secret/archive/v0.1.0.tar.gz"
  version "0.1.0"
  sha256 "107c5555c203ad3b1498e2995fa8aa81942840189316d13933fcf0947180d10b"

  # depends_on "cmake" => :build
  depends_on :x11 # if your formula requires any X11/XQuartz components

  def install
    # ENV.deparallelize  # if your formula fails when building in parallel

    # Remove unrecognized options if warned by configure
    system "./configure", "--disable-debug",
                          "--disable-dependency-tracking",
                          "--disable-silent-rules",
                          "--prefix=#{prefix}"
    # system "cmake", ".", *std_cmake_args
    system "make", "install" # if this fails, try separate make/make install steps
  end

  test do
    # `test do` will create, run in and delete a temporary directory.
    #
    # This test will fail and we won't accept that! It's enough to just replace
    # "false" with the main program this formula installs, but it'd be nice if you
    # were more thorough. Run the test with `brew test git-secret`. Options passed
    # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
    #
    # The installed folder is not in the path, so use the entire path to any
    # executables being tested: `system "#{bin}/program", "do", "something"`.
    system "false"
  end
end

В дальнейшем, чтобы редактировать формулу можно использовать команду brew edit.
Обратим внимание на главный класс, который наследует абстрактный класс Formula* [6]. У него есть несколько важных атрибутов:

  • desc* [7] — описание вашего пакета, виден пользователям, когда они выполняет команду brew info git-secret
  • homepage* [8] — страница приложения, может быть доступна при команде brew home git-secret
  • url* [9] — ссылка, используемая для скачивания исходников приложения, существует возможность указывать стратегии скачивания (:git, :svn) и другие опции
  • sha256* [10] — параметр безопасности: хеш файла, который указан по ссылке url, если кто-то получит доступ к вашему репозиторию и заменит файл, то установить его не выйдет. Еще одно следствие: при каждом изменении сборки — необходимо обновлять хеш

Опции

Иногда возникает необходимость добавлять различные опции при установке, добавлять ли необязательные пакеты, какие параметры выставить при сборке и так далее. В Homebrew есть на такой случай option* [11]. Скажем, вот пример опций из формулы git.rb* [12]:

Пример опций

  option "with-blk-sha1", "Compile with the block-optimized SHA1 implementation"
  option "without-completions", "Disable bash/zsh completions from 'contrib' directory"
  option "with-brewed-openssl", "Build with Homebrew OpenSSL instead of the system version"
  option "with-brewed-curl", "Use Homebrew's version of cURL library"
  option "with-brewed-svn", "Use Homebrew's version of SVN"
  option "with-persistent-https", "Build git-remote-persistent-https from 'contrib' directory"

Для доступа к текущим опциям из кода используется конструкция build.with? "option_name" или build.without? "option_name".

Зависимости

У вашего приложения могут быть зависимости, для них существует специальный метод depends_on* [13]. Важно отметить, что зависимости могут быть разные* [14] как по типу установки, так и по принадлежности: только формула или сгодится и системный пакет. Существует несколько типов зависимостей (по установке):

  • стандартная, нужна для работы приложения
  • :build — нужна только при сборке
  • :recommended — устанавливается по-умолчанию, добавляет опцию --without-foo
  • :optional — не устанавливается по-умолчанию, но автоматически добавляет опцию --with-foo

Пример зависимостей

  depends_on "jpeg"
  depends_on "gtk+" => :optional
  depends_on "readline" => :recommended
  depends_on "boost" => "with-icu"
  depends_on :x11 => :optional

Для тех зависимостей, которые не являются частью Homebrew [15] есть специальный блок resource* [16], котором должны быть объявлены url и sha256.

"Собирай пакеты заранее"

У Homebrew есть замечательный бот* [17], который ходит по формулам и собирает их в Bottles. Для того, чтобы сказать ему, что собирать используется специальный блок bottle* [18], где можно указывать url для скачивания, путь для сохранения файла и параметры sha256 для разных версий Mac OS X. Полный пример из документации:

Bottles

bottle do
  root_url "https://example.com"
  prefix "/opt/homebrew"
  cellar "/opt/homebrew/Cellar"
  revision 4
  sha256 "4921af80137af9cc3d38fd17c9120da882448a090b0a8a3a19af3199b415bfca" => :yosemite
  sha256 "c71db15326ee9196cd98602e38d0b7fb2b818cdd48eede4ee8eb827d809e09ba" => :mavericks
  sha256 "85cc828a96735bdafcf29eb6291ca91bac846579bcef7308536e0c875d6c81d7" => :mountain_lion
end

А можно указать bottle :unneeded и сборка производится не будет. Кстати, для проверки правильности сборки, наш добрый бот выполняет тесты, которые мы указываем для формулы. О них ниже.

Установка последней версии из репозитория

Атрибут head* [19] — позволяет добавлять ссылку на репозиторий, чтобы можно было устанавливать последнюю версию прямо из текущей ветки (которую можно определить при помощи параметра :branch). Чтобы установить формулу таким образом, нужно использовать brew install --HEAD $formula.

Процесс установки формулы

Вот тут и пришло время поговорить про главный метод — install* [20], который выполняет команды необходимые для установки. В основном он утилизирует вызов системных команд при помощи Kernel#system* [21]. Здесь следует упомянуть о специальных значениях переменных, которые можно передавать в свой скрипт для его конфигурации, они доступны по ссылке [22].

Общение с пользователем

Для вывода сообщений* [23] есть специальные возможности:

  • ohai для общей информации
  • opoo для предупреждений
  • odie для сообщений об ошибке и немедленном выходе

А так же есть метод caveats* [24], который напишет пользователю всякие подробности о вашем пакете после успешной установки или при запуске команды brew info $formula.

Тестирование установки

Дело за малым: проверить, установилась ли формула, собралась ли. Для проверки корректности [25] работы в Homebrew есть специальный блок test* [26]. Он выполняется внутри сендбокса, и если он выполняется то формула считается корректно собранной. Как говорят сами создатели, они не против, если в test будет лишь проверяться версия. Главное, что программа собралась. Но будет лучше, если пройдет тест посолиднее. Важно понимать, что тут тестируется не функциональность, а лишь сборка. Можно привести вот такой пример [27] теста:

Пример теста

  test do
    (testpath/".zshrc").write "source `brew --prefix`/share/antigen.zsh"
    system "/bin/zsh", "--login", "-i", "-c", "antigen help"
  end

Чтобы вызвать тест локально используется команда brew test $formula.

Формула выходит в мир

Все, наша формула почти готова! Теперь нужно удостовериться, что она проходит внутренние проверки Homebrew: запустим команду brew audit --strict --online $formula. Если ошибок нет, то продолжаем. Если есть, то правим. Нам сразу подскажут, что нужно изменить. Теперь формула готова к выходу в открытый мир. А из открытого мира существует несколько способов установки формулы:

  • Официальный репозиторий Homebrew
  • Личный Tap

Подробнее про каждый.

Заливаем формулу в официальный репозиторий Homebrew

Сложный путь. Наша формула должна удовлетворять многим требованиям [28] Homebrew, чтобы быть принятой. Вот некоторые из них:

  • Не дубликат. Здесь идет речь не только об основном репозитории, но и официальных Tap
  • Приложение не должно обновлять себя само
  • Оно не должно ничего докачивать при установке
  • У формулы есть стабильные версии
  • Формула не должна быть доступна через gem или pip
  • Формула известна людям, ее поддерживают, у нее должна быть домашняя страница
  • Она не собирает .app, таким формулам место в Cask

Но как утверждают авторы, исключения бывают [29].

Если ваша формула подходит, то теперь нужно залить ее в официальный репозиторий. О том как сделать Pull-Request лучше расскажет официальный гид [30] по участию в проекте, там много тонкостей и правил.

Ну а если же она не подходит, или вы намеренно не хотите отправлять ее туда, то вам нужно создать свой Tap.

Создаем свой Tap

Tap* [31] — по сути просто репозиторий, в котором вы храните свои собственные формулы. Чтобы создать Tap — создаем на github репозиторий с префиксом homebrew-. Например: homebrew-mytap. Сами формулы могут быть либо в корне, либо в папке Formula (или HomebrewFormula). Теперь чтобы установить вашу формулу есть два способа, первый:

  • brew tap <your-github-username>/mytap — создаст локальную копию вашего репозитория
  • brew install $formula — установит из него формулу

Или одной командой:

  • brew install <your-github-username>/mytap/$formula — такой способ сначала сделает brew <your-github-username>/mytap а потом вызовет команду install

Готово!

Заключение

Я попытался бегло окинуть взглядом и словом ряд ключевых возможностей Homebrew. Конечно всего за один раз не расскажешь, остались в стороне такие темы как: патчи, выбор компилятора, написание своих собственных стратегий загрузки и множество тонкостей при работе с Python, Ruby (и прочими) программами [32], у которых есть свои особенности и возможности "из-коробки". Но желающие всегда могут ознакомиться с соответствующими статьями в официальной документации, которая, кстати, хороша.

Писать свои формулы очень просто, ведь Homebrew предоставляет очень качественный API. Даже если не знать Ruby, то все должно получиться довольно быстро. Вот что [33] получилось у меня.

Автор: sobolevn

Источник [34]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby/115576

Ссылки в тексте:

[1] Homebrew: http://brew.sh/

[2] терминах: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md

[3] рассказал: https://www.quora.com/How-does-Homebrew-work-internally

[4] создателей: https://github.com/bfontaine

[5] создайте его: https://help.github.com/articles/creating-releases/

[6] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula

[7] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#desc%3D-class_method

[8] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#homepage%3D-class_method

[9] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#url-class_method

[10] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#sha256%3D-class_method

[11] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#option-class_method

[12] *: https://github.com/Homebrew/homebrew/blob/master/Library/Formula/git.rb

[13] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#depends_on-class_method

[14] *: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#specifying-other-formulae-as-dependencies

[15] не являются частью Homebrew: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#specifying-gems-python-modules-go-projects-etc-as-dependencies

[16] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#resource-class_method

[17] *: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Brew-Test-Bot.md

[18] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#bottle-class_method

[19] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#head-class_method

[20] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#install-instance_method

[21] *: http://ruby-doc.org/core-2.3.0/Kernel.html#method-i-system

[22] ссылке: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#variables-for-directory-locations

[23] *: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#messaging

[24] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#caveats-instance_method

[25] проверки корректности: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#add-a-test-to-the-formula

[26] *: http://www.rubydoc.info/github/Homebrew/homebrew/master/Formula#test-class_method

[27] пример: https://github.com/Homebrew/homebrew/blob/master/Library/Formula/antigen.rb

[28] многим требованиям: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Acceptable-Formulae.md

[29] исключения бывают: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Acceptable-Formulae.md#sometimes-there-are-exceptions

[30] официальный гид: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/How-To-Open-a-Homebrew-Pull-Request-(and-get-it-merged).md

[31] *: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/How-to-Create-and-Maintain-a-Tap.md

[32] Python, Ruby (и прочими) программами: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Gems%2C-Eggs-and-Perl-Modules.md

[33] Вот что: https://github.com/sobolevn/homebrew-tap

[34] Источник: https://habrahabr.ru/post/279687/