Вступление к серии
Передо мной недавно встала задача, как распространять одну консольную утилиту? Обычные мои инструменты вроде pip
, npm
и gem
не подходили в силу языка самой утилиты — bash
. Тогда стало понятно, что нужно распространять свое приложение в том числе и через системные пакетные менеджеры. Для Mac — в силу отсутствия встроенного — таких пакетных менеджеров несколько. И у каждого из них есть свои особенности и недостатки. И в первой части я хочу более подробно остановиться на Homebrew, и как создавать пакеты для него.
Ну а чтобы установить приложения на Linux, то нужно будет собирать пакеты таких форматов: .tar.gz
, .deb
и .rpm
. О чем я расскажу во второй части.
Терминология
Прежде всего договоримся о терминах, ведь в мире 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
Нельзя оставить без внимания устройство самого инструмента, о нем прекрасно рассказал один из его создателей:
Homebrew основан на двух вещах: Git и Ruby. Когда вы устанавливаете Homebrew в первый раз, то он создает копию официального репозитория у вас в
/usr/local
. Каждый раз, когда вы обновляете Homebrew командойbrew update
— происходитgit pull
, а еще вам покажут какие формулы изменились. Когда вы устанавливаете что-либо командойbrew install
, то Homebrew скачивает архив (в дальнейшем файлы будут браться из кеша), проверяет его хеш и затем выполняет установку. Но на самом деле Formula может быть намного более сложной: в ней могут быть зависимости, особенности загрузки: svn, ftp и прочее. А еще Homebrew, когда возможно, предоставляет уже собранные пакеты (автоматически созданные ботом).
Создание своей собственной Formula
Допустим у вас уже есть некий релиз на github (а если нет, то создайте его при помощи 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
*. У него есть несколько важных атрибутов:
desc
* — описание вашего пакета, виден пользователям, когда они выполняет командуbrew info git-secret
homepage
* — страница приложения, может быть доступна при командеbrew home git-secret
url
* — ссылка, используемая для скачивания исходников приложения, существует возможность указывать стратегии скачивания (:git, :svn
) и другие опцииsha256
* — параметр безопасности: хеш файла, который указан по ссылкеurl
, если кто-то получит доступ к вашему репозиторию и заменит файл, то установить его не выйдет. Еще одно следствие: при каждом изменении сборки — необходимо обновлять хеш
Опции
Иногда возникает необходимость добавлять различные опции при установке, добавлять ли необязательные пакеты, какие параметры выставить при сборке и так далее. В Homebrew есть на такой случай option
*. Скажем, вот пример опций из формулы git.rb
*:
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
*. Важно отметить, что зависимости могут быть разные* как по типу установки, так и по принадлежности: только формула или сгодится и системный пакет. Существует несколько типов зависимостей (по установке):
- стандартная, нужна для работы приложения
: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 есть специальный блок resource
*, котором должны быть объявлены url
и sha256
.
"Собирай пакеты заранее"
У Homebrew есть замечательный бот*, который ходит по формулам и собирает их в Bottles. Для того, чтобы сказать ему, что собирать используется специальный блок bottle
*, где можно указывать url для скачивания, путь для сохранения файла и параметры sha256
для разных версий Mac OS X. Полный пример из документации:
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
* — позволяет добавлять ссылку на репозиторий, чтобы можно было устанавливать последнюю версию прямо из текущей ветки (которую можно определить при помощи параметра :branch
). Чтобы установить формулу таким образом, нужно использовать brew install --HEAD $formula
.
Процесс установки формулы
Вот тут и пришло время поговорить про главный метод — install
*, который выполняет команды необходимые для установки. В основном он утилизирует вызов системных команд при помощи Kernel#system
*. Здесь следует упомянуть о специальных значениях переменных, которые можно передавать в свой скрипт для его конфигурации, они доступны по ссылке.
Общение с пользователем
Для вывода сообщений* есть специальные возможности:
ohai
для общей информацииopoo
для предупрежденийodie
для сообщений об ошибке и немедленном выходе
А так же есть метод caveats
*, который напишет пользователю всякие подробности о вашем пакете после успешной установки или при запуске команды brew info $formula
.
Тестирование установки
Дело за малым: проверить, установилась ли формула, собралась ли. Для проверки корректности работы в Homebrew есть специальный блок test
*. Он выполняется внутри сендбокса, и если он выполняется то формула считается корректно собранной. Как говорят сами создатели, они не против, если в test
будет лишь проверяться версия. Главное, что программа собралась. Но будет лучше, если пройдет тест посолиднее. Важно понимать, что тут тестируется не функциональность, а лишь сборка. Можно привести вот такой пример теста:
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
Сложный путь. Наша формула должна удовлетворять многим требованиям Homebrew, чтобы быть принятой. Вот некоторые из них:
- Не дубликат. Здесь идет речь не только об основном репозитории, но и официальных Tap
- Приложение не должно обновлять себя само
- Оно не должно ничего докачивать при установке
- У формулы есть стабильные версии
- Формула не должна быть доступна через
gem
илиpip
- Формула известна людям, ее поддерживают, у нее должна быть домашняя страница
- Она не собирает
.app
, таким формулам место вCask
Но как утверждают авторы, исключения бывают.
Если ваша формула подходит, то теперь нужно залить ее в официальный репозиторий. О том как сделать Pull-Request лучше расскажет официальный гид по участию в проекте, там много тонкостей и правил.
Ну а если же она не подходит, или вы намеренно не хотите отправлять ее туда, то вам нужно создать свой Tap.
Создаем свой Tap
Tap* — по сути просто репозиторий, в котором вы храните свои собственные формулы. Чтобы создать 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 (и прочими) программами, у которых есть свои особенности и возможности "из-коробки". Но желающие всегда могут ознакомиться с соответствующими статьями в официальной документации, которая, кстати, хороша.
Писать свои формулы очень просто, ведь Homebrew предоставляет очень качественный API. Даже если не знать Ruby, то все должно получиться довольно быстро. Вот что получилось у меня.
Автор: sobolevn