Идут споры о том, какой метод лучше всего подходит для управления зависимостями в Go. Но я нашел то, что мне кажется правильным решением проблемы, и это крошечный скрипт на Bash под названием GPM .
Go поставляется с разнообразными изумительными инструментами для решения проблем. Но ему не хватает встроенного механизма управления версиями зависимостей. Это немного усугубляется тем фактом, что существует сильная корреляция между именами пакетов и исходным хранилищем для пакета. (Например, github.com/technosophos/foo
предполагается, что пакет находится по этому URL.)
Проблема с управлением пакетами Go
Итак, как мы можем быть уверены, что когда вы создаете пакет, который зависит от github.com/technosophos/foo
меня, и я его создаю, мы создаем одно и то же? Что если я собираюсь использовать версию 1.0, а вы — версию 1.0.1? Короче говоря, мы хотим иметь повторяющиеся сборки и заведомо хорошие зависимости. Для краткости я буду называть эту цель управлением пакетами .
Есть две доминирующие теории о том, как управлять пакетами в Go.
Первое достаточно просто: всегда беги головой . Другими словами, часто обновляйте все свои зависимости и при необходимости корректируйте. До недавнего времени это был мой любимый маршрут. Но неожиданное серьезное недокументированное изменение одной из наших основных зависимостей изменило мое мнение. Переписывание тонны кода для соответствия новому API дало мне достаточно времени, чтобы подумать о том, должны ли мы продолжать жить на кончике исходного кода каждой зависимости.
Вторая теория называется вендорингом . Идея состоит в том, что все ваши зависимости находятся в определенном подкаталоге вашего приложения (обычно _vendor
или vendor
), и вы управляете этими зависимостями встроенным образом. Эта стратегия используется большим количеством языков программирования, включая Node.js и систему PHP Composer. Но в Go есть еще одна загвоздка: поскольку имя пакета связано с местоположением пакета, вы должны переписать источник, чтобы заменить имена пакетов. И это означает не только ваш источник, но и источники всех пакетов, которые продаются. У меня есть много причин, по которым мне не нравится этот подход, но я могу обобщить его, сказав, что это похоже на взлом, который сопровождается изобилием головной боли при обслуживании.
ГОПАТ и ГПМ
Go интенсивно использует определенную переменную среды $GOPATH
. Согласно документации:
Переменная среды GOPATH указывает местоположение вашего рабочего пространства. Вероятно, это единственная переменная окружения, которую вам нужно установить при разработке кода Go. Как написать код Go
Когда я начал работать с Go, я использовал один $GOPATH
код для всего своего кода Go. Но чем дольше я работал с Go, тем больше я чувствовал, что делаю это неправильно. Может быть, я должен использовать один $GOPATH
на проект . Когда я начал думать таким образом, я понял, что правильный путь управления путями Го — это способ, которым GPM это делает.
GPM (Go Package Manager) — это простой инструмент для управления зависимостями проекта с помощью специального файла Godeps
, для объявления зависимостей и, при необходимости, закрепления определенного номера версии.
Например, вот начало одного из моих Godeps
файлов:
github.com/Masterminds/cookoo github.com/aokoli/goutils 1.0.0 github.com/bmizerany/pq 284a1d41dcc27fe8ccc413c339464698ab99f6d1
Это объявляет три зависимости. Первый, Cookoo , всегда указывает на голову. Это здорово для меня, так как я являюсь основным сопровождающим этого проекта и точно знаю его статус.
Во-вторых, goutils прикрепляется к определенному тегу Git. Эта библиотека недавно выпустила 1.0.0, и я хотел бы использовать только этот выпуск.
Третьим, pq , является наш драйвер Postgres. Я прикрепил его к хэшу коммита Git той версии, которую мы сейчас установили и протестировали.
Теперь, если я использую gpm install
, GPM сначала go get
все три из этих пакетов, а затем установить их версии в соответствии с тем, что я указал. По сути, GPM управляет мной $GOPATH
для меня.
ГОПАТ и ГВП
Однако основное предостережение при использовании GPM заключается в том, что каждый проект должен иметь свой собственный $GOPATH
. (Либо это, либо вы должны обязать все проекты согласовать версии зависимостей.)
Разработчик GPM также написал инструмент для управления $GOPATH
. Это называется GVP (Go Versioning Packager). По сути, GVP создает .godeps
каталог внутри вашего проекта, а затем создает $GOPATH
каталог, который включает этот каталог.
Идея, лежащая в основе GVP, теоретически аналогична идее вендинга, но с двумя основными отличиями.
- Поскольку
$GOPATH
указывает на.godeps
каталог, нет необходимости изменять пути к пакетам. - Поскольку (если вы используете GPM) и точное состояние можно восстановить в любое время, вам не нужно хранить
.godeps
каталог в проекте. Это может быть восстановлено на лету.
Пример
Чтобы сделать это более понятным, вот пример. Я уже установил gpm
и то gvp
, и другое , и мой $GOPATH
изначально настроен на мой старый способ иметь все проекты в одной GOPATH.
$ echo $GOPATH /Users/mbutcher/Code/Go $ ls main.go main_test.go
Теперь давайте использовать gvp
для управления и установки $GOPATH
:
$ gvp init $ source gvp in $ echo $GOPATH /Users/mbutcher/Code/Go/src/github.com/technosophos/gpm-example/.godeps:/Users/mbutcher/Code/Go/src/github.com/technosophos/gpm-example
gvp init
Вызов создает необходимую инфраструктуру, и source gvp in
устанавливает свое $GOPATH
переменное окружение. Как видите, $GOPATH
теперь указывает .godeps
и домашний каталог проекта. (Я знаю, что некоторые люди яростно спорят с несколькими каталогами в $ GOPATH, но на самом деле это хорошо работает в нынешней системе.)
Теперь давайте кратко рассмотрим main.go
:
package main import "github.com/aokoli/goutils" func main() { println(DoSomething()) } func DoSomething() string { r, _ := goutils.RandomAscii(5) return r }
Обратите внимание, что я импортирую одну внешнюю библиотеку ( github.com/aokoli/goutils
). Если я попытаюсь запустить этот код, я получу ошибку:
$ go run main.go main.go:3:8: cannot find package "github.com/aokoli/goutils" in any of: /usr/local/Cellar/go/1.2/libexec/src/pkg/github.com/aokoli/goutils (from $GOROOT) /Users/mbutcher/Code/Go/src/github.com/technosophos/gpm-example/.godeps/src/github.com/aokoli/goutils (from $GOPATH) /Users/mbutcher/Code/Go/src/github.com/technosophos/gpm-example/src/github.com/aokoli/goutils
Проще говоря, этой библиотеки нет в моем текущем $GOPATH
. Я могу это исправить, создав Godeps
файл, а затем установив с помощью gpm
. Вот мой Godeps
файл:
github.com/aokoli/goutils 1.0.0
Теперь я могу запустить gpm
:
$ gpm install >> Getting package github.com/aokoli/goutils >> Setting github.com/aokoli/goutils to version 1.0.0 >> All Done
И теперь программа запустится и может быть протестирована:
$ go run main.go I3!eZ $ go test PASS ok _/Users/mbutcher/Code/Go/src/github.com/technosophos/gpm-example 0.011s
Так что у нас это. Проект строится правильно и многократно.
Когда я закончу с проектом, я смогу заново установить $GOPATH
его по умолчанию:
$ source gvp out >> Reverted to system GOPATH. $ echo $GOPATH /Users/mbutcher/Code/Go
И теперь я могу заняться своей другой работой.
Вывод
GPM + GVP хорошо работал для моих нужд, и я нахожу это намного лучше, чем вендорство или жизнь на переднем крае репозитория каждой зависимости. Я реализовал это на проектах больших и малых, и он чувствует себя чистым. Я использовал его для управления сборками и CI. В целом, я нашел, что это отвечает моим потребностям лучше, чем любое другое решение.
Система, однако, не без ее ограничений:
- Создание исходного
Godeps
файла может быть немного утомительным, если вы начинаете с большого проекта. Дажеgpm bootstrap
плагин идет так далеко в начальной загрузке. - Как и другие инструменты, это не разрешает случаи, когда для двух зависимостей требуются разные версии третьей зависимости. Хотя это еще не поразило меня, когда-нибудь это произойдет, и я не уверен, как я это решу.
Я столкнулся с другими мелкими неприятностями, но разрешил их, написав плагины для GPM, такие как gpm-git и gpm-local .