Статьи

Почему GPM – менеджер пакетов Right Go

Идут споры о том, какой метод лучше всего подходит для управления зависимостями в 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, теоретически аналогична идее вендинга, но с двумя основными отличиями.

  1. Поскольку $GOPATHуказывает на .godepsкаталог, нет необходимости изменять пути к пакетам.
  2. Поскольку (если вы используете 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 .