gb — менеджмент зависимостей для Go

Отсутствие в Go нативного менеджера зависимостей и версий является одним из самых частых пунктов в критике языка. В этой статье мы рассмотрим проблему детальнее и познакомимся с новым проектом, с лаконичным именем gb, который набирает популярность в Go-коммьюнити и обещает вскоре стать де-факто стандартом для управления зависимостями и версиями в Go.90c2fddc123a4df6a145fac5bf93284a.png(Credit orig.photo: Nathan Youngman)

Для начала давайте разберемся, из-за чего весь шум и почему в Go изначально не было продвинутого менеджера зависимостей.

Что такое менеджмент зависимостей? Что же подразумевается под «продвинутым» менеджером зависимостей и какие задачи он должен решать. Как-никак, но в Go простой менеджмент зависимостей присутствует.В Go есть стандартная команда «go get», которой достаточно указать название проекта на, скажем, Github-е, чтобы код проекта был установлен, собран и готов к использованию. «go get» всегда скачивает последнюю версию, из master-ветки, и общий консенсус для open-source библиотек заключается в том, чтобы не ломать обратную совместимость никакой ценой. Об этом написано в FAQ, и в подавляющем большинстве случаев сторонних библиотек, это правило соблюдается.

Вы, в зависимости от вашего предыдущего опыта, можете подобную идею отвергнуть на корню, или предвидеть, что очень скоро эта схема окажется нежизнеспособной, но пока что, через 3 года существования Go 1, эта схема работает и даже используется в продакшене в больших компаниях, хоть изначально и больше расчитана на open-source экосистему. Скажу так — это вопрос соотношения сложности и рисков, которые готовы брать на себя разработчики.

Но перейдя от open-source мира в мир коммерческой разработки, сразу же встает надобность гарантировать, что код определенной ревизии будет всегда компилироваться и выдавать одинаковый результат, и не зависеть от внешних факторов. Под внешними факторами могут подразумеваться, как новые, ломающие совместимость, изменения в third-party библиотеках, так и отключение интернета или падение Github-а.f4a0f2ac097840d1902df355d3faa9d6.png

Задача эта решается двумя способами, которые, зачастую идут вместе — версионированием зависимостей (versioning) и вендорингом (vendoring) — включением third-party кода в вашу кодовую базу. Первое позволяет гарантировать, что новые коммиты в стороннюю библиотеку не поломают ваш билд, а второе — что даже при падении интернета, весь код, необходимый для сборки, будет доступен для сборки локально.

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

Почему этого нет в Go? Краткий ответ — потому что это чертовски сложно сделать так, чтобы всех устраивало. Не то что сложно — это невозможно, если вы хотите сделать максимально удобный инструмент, подходящий для всех случаев, случающихся в реальной практике.Авторы Go изначально признали сложность задачи, и честно отказались навязывать всем какое-то решение, предложив варианты. Этот вопрос хорошо изложен в официальном FAQ и ответ звучит следующим образом:

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

Достаточно честно, согласитесь.Далее в FAQ даются рекомендации, как и что делать, и как эту задачу Google решает в своем случае (опять же, делая акцент, что «то что подходит для нас, может не подходить для вас»). И если существует «идеальное решение», то у коммьюнити есть все карты на руках, чтобы его создать и предложить в качестве стандартного для Go.

Как показала практика, универсального решения для всех тут нет. Зато зоопарк решений для разных случаев и постоянное обсуждение вопроса привело к достаточно неплохому консенсусу на то, что же всё таки положит конец спорам про продвинутый менеджмент зависимостей в Go.

Краткая история попыток решить проблему. Весь зоопарк решений сводится к утилитам, которые решают вопрос либо версионирования, либо вендоринга, либо того и другого. Спектр решений широк — от простых Makefile-c с заменой GOPATH и git-самодулей до перезаписи путей в импортах и полноценных all-in-one утилит, самой популярной из которых является Godep.Но все они по своей сути являются врапперами над стандартными командами go get и go build. И именно это было источником главных несостыковок. Workflow при работе с go get/build великолепно работает, если вы пишете open-source проект — все просто и удобно. Но если вы пишете отдельный рабочий проект, который никак вообще даже не пересекается с вашей open-source деятельностью, то все решения для версионирования/вендоринга становятся занозой в пятке и лишают определенных удобств и добавляют сложности. Ещё запутанней всё становится, если вы пытаетесь смешивать оба сценария.

Осознание этих двух главных различных сценариев разработки (open-source vs private closed project) привело к пониманию того, что различные сценарии требуют различных решений. И не так давно, Dave Cheney (один из Go-контрибьюторов и вообще, знатный, Go-популяризатор) предложил четко разделить эти два сценария, создав для второго отдельный инструмент, который будет похож на стандартный go get/build, но изначально созданный для работы с project-ориентированным деревом исходников, с четким разделением на «свой код» и «зависимости».

gb — project-based build tool 676b42d68fa64a8895cbdeecaf8e63e0.pngОсновные тезисы, лежащие в основе gb:

отдельная утилита, заменяющая стандартную go get/build/test всё определяет структура директории никаких специальных файлов описаний никаких изменений кода (в т.ч. перезаписи импортов) четкое разделение кода на «проект» и «зависимости» Если вы испугались на фразе «заменяет стандартную go get/build/test», то это нормальная реакция :) На самом деле проект для gb можно абсолютно спокойно использовать и со стандартными go build/test, но gb build позволит вам не заморачиваться на пути к завендоренным пакетам.Теперь по-порядку.

Отдельная утилита, заменяющая стандартную go get/build/testЭто именно так, и вам придётся поставить в свою рабочую систему ещё одну команду. Делается это просто:

go get github.com/constabulary/gb/… Всё определяет структура директорииПод «всё» подразумевается факт того, что gb сможет работать с этим проектом. Правило тут простое — в директории должна быть папка src/. И этого достаточно, чтобы начать работать с gb.Зачастую, когда Go используется только для одного проекта, рекомендуют подход «GOPATH per project» — фактически, вас просят поменять ваш GOPATH на путь к данному проекту и работать в нём. gb реализует что-то подобное, только не трогая ваш системный GOPATH. Чуть подробнее ниже.

Никаких специальных файлов описанийСовременные проекты и так состоят из десятка .dot-файлов с различными манифестами и описаниями. Ещё один такой файл, необходимый для сборки проекта — это было бы чересчур и не в Go-стиле.

Никаких изменений кодаКод проекта всегда остается таким, каким он и написан. Никаких перезаписей импортов в стиле 'import «github.com/user/pkg» → import «vendor/github.com/user/pkg»' нет и не будет.

Четкое разделение кода на «проект» и «зависимости«Возвращаясь к пункту про структуру директории, gb трактует всё, что находится в src/, как код вашего проекта. Все зависимые пакаджи устанавливаются в директорию vendor/ и именно оттуда код берется при сборке с помощью gb.

Пример использования Самый лучший способ понять инструмент, это использовать его.1592be4793764dff850d0be9ebb24fdd.pngДля начала, создадим новый проект, ~/demoproject. Директория значения не имеет, хоть в /tmp. При работе с gb можете забыть про ваш стандартный GOPATH вообще. mkdir ~/demoproject && cd!$ mkdir -p src/myserver cat > src/myserver/main.go <

import ( «github.com/labstack/echo» «net/http» )

func hello (c *echo.Context) error { return c.String (http.StatusOK, «Hello, World!\n») }

func main () { e:= echo.New () e.Get (»/», hello) e.Run (»:1323») } END Дерево проекта у нас пока выглядит вот так:

$ tree $(pwd) /Users/user/demoproject └── src └── myserver └── main.go Запускаем билд с помощью команды: $ gb build gb build должен выдать ошибку о том, что зависимость (github.com/labstack/echo) не найдена: FATAL command «build» failed: failed to resolve import path «myserver»: cannot find package «github.com/labstack/echo» in any of: /usr/local/go/src/github.com/labstack/echo (from $GOROOT) /Users/user/demoproject/src/github.com/labstack/echo (from $GOPATH) /Users/user/demoproject/vendor/src/github.com/labstack/echo Если внимательно посмотреть, то видно, что gb ищет зависимый пакадж сначала в GOROOT (так как stdlib-пакаджи лежат там), затем в GOPATH, который определен для этого проекта (demoproject/src), и, в последнюю очередь, в demoproject/vendor. Поскольку пакета пока нигде нет, получаем ошибку. Можно стянуть пакет руками в vendor (и не забыть удалить папку .git), но у gb для этого есть функционал: gb vendor. $ gb vendor fetch github.com/labstack/echo Cloning into '/var/folders/qp/6bvmky410dn8p1yhn3b19yxr0000gn/T/gb-vendor-097548747'… remote: Counting objects: 1531, done. remote: Compressing objects: 100% (45/45), done. remote: Total 1531 (delta 12), reused 0 (delta 0), pack-reused 1482 Receiving objects: 100% (1531/1531), 317.40 KiB | 191.00 KiB/s, done. Resolving deltas: 100% (911/911), done. Checking connectivity… done. Проверяем структуру директории проекта: $ tree -L 6 $(pwd) /Users/user/demoproject ├── src │ └── myserver │ └── main.go └── vendor ├── manifest └── src └── github.com └── labstack └── echo ├── LICENSE ├── README.md ├── context.go ├── context_test.go ├── echo.go ├── echo_test.go ├── examples ├── group.go ├── group_test.go ├── middleware ├── response.go ├── response_test.go ├── router.go ├── router_test.go └── website

10 directories, 14 files В manifest-файле записаны вся информация о версиях зависимостей:$ cat vendor/manifest { «version»: 0, «dependencies»: [ { «importpath»: «github.com/labstack/echo», «repository»: «https://github.com/labstack/echo», «revision»:»1ac5425ec48d1301d35a5b9a520326d8fca7e036», «branch»: «master» } ] } Повторяем gb vendor fetch для остальных зависимостей и пробуем теперь собрать код: $ gb build github.com/bradfitz/http2/hpack github.com/labstack/gommon/color github.com/mattn/go-colorable golang.org/x/net/websocket github.com/bradfitz/http2 github.com/labstack/echo myserver $ tree bin/ bin/ └── myserver

0 directories, 1 file Бинарник кладется, как и в привычном сценарии работы в одном GOPATH в директорию bin/.

Теперь всю директорию можно смело вносить в систему контроля версий, и тут уже вам, как хозяину проекта решать — хотите вы оставить только версионирование, или вендорить все зависимости тоже, или всё завендорить, а версии менеджить вручную. В ваших руках свобода выбора.

только версионирование: добавляете vendor/src в .gitignore только вендоринг: добавляете vendor/manifest в .gitignore и версионирование и вендоринг: оставляете .gitignore без изменений В случае с версионированием-only любой разработчик из вашей команды после того, как склонировал репозиторий на свою машину, должен запустить: $ gb vendor update -all чтобы получить 1-в-1 дерево зависимостей для сборки проекта.В целом этого описания должно быть достаточно, чтобы понять принципы работы и подход gb к решению вопроса версионирования и вендоринга.

Послесловие Проект gb пока находится в ранней стадии развития и принятия сообществом, но судя по реакции последнего и бурной поддержке — это очень удачное решение.У проекта пока достаточно много открытых issues в трекере, но они быстро закрываются и проект активно развивается.По ещё не большому личному опыту использования — есть пока сложности с кросс-платформенной сборкой. В остальном же пока полёт нормальный.

Ссылки Официальный сайт: getgb.ioGithub-репозиторий: github.com/constabulary/gbБлог пост от автора: dave.cheney.net/2015/06/09/gb-a-project-based-build-tool-for-the-go-programming-languageDesign rationale: getgb.io/rationaleTheory of operation: getgb.io/theory

© Habrahabr.ru