Управление пакетами с помощью Go-модулей: практическое руководство
Go Modules - это способ борьбы с зависимостями в Go. В начале эксперимента предполагается ввести игровое поле в 1.13 в качестве нового значения по умолчанию для управления пакетами.
Я нахожу это немного необычным для новичка, пришедшего с других языков, и поэтому я хотел собрать некоторые мысли и советы, чтобы помочь другим, таким как я, получить представление об управлении пакетами Go. Мы начнем с некоторых мелочей, а затем перейдем к менее очевидным аспектам, включая использование папки vendor
, использование модулей с Docker в production, зависимости от инструментов и т.д.
Если вы уже давно пользуетесь модулями Go и знаете вики как свои пять пальцев, эта статья, вероятно, не окажется для вас очень полезной. Для некоторых, однако, это может сэкономить несколько часов проб и ошибок.
Так что пристегнитесь и наслаждайтесь поездкой.
Быстрый старт
Если ваш проект уже находится в управлении версиями, вы можете просто запустить
go mod init
Или вы можете указать путь к модулю вручную. Это как имя, URL и путь импорта для вашего пакета:
go mod init github.com/you/hello
Эта команда создаст файл go.mod
, который одновременно определяет требования к проектам и блокирует зависимости к их правильным версиям (чтобы дать вам некоторую аналогию, это похоже на package.json и package-lock.json, объединенные в одно):
module github.com/you/hello
go 1.12
Запустите go get
, чтобы добавить новую зависимость в ваш проект:
Обратите внимание, что хотя вы не можете указать диапазон версий с помощью go get, вы все равно определяете здесь минимальную версию, а не точную версию. Как мы увидим позже, есть способ изящно увеличивать зависимости в соответствии с semver.
# use Git tags
go get github.com/go-chi/chi@v4.0.1
# or Git branch name
go get github.com/go-chi/chi@master
# or Git commit hash
go get github.com/go-chi/chi@08c92af
Теперь наш файл go.mod выглядит так:
module github.com/you/hello
go 1.12
require github.com/go-chi/chi v4.0.2+incompatible // indirect
Суффикс +incompatible
добавляется ко всем пакетам, которые еще не включены в Go Modules или нарушают его правила управления версиями.
Поскольку мы еще не импортировали пакет нигде в нашем проекте, он был помечен как // indirect
. Мы можем привести в порядок наши зависимости следующей командой:
go mod tidy
В зависимости от текущего состояния вашего репо, это приведет к удалению неиспользуемого модуля или удалению комментария // indirect
.
Если конкретная зависимость сама по себе не имеет go.mod (например, она еще не включила использование модулей), тогда все ее зависимости будут записаны в родительский файл go.mod (например, ваш файл go.mod). вместе с комментарием // indirect
, чтобы указать, что это не из прямого импорта в вашем модуле.
Цель go mod tidy
также состоит в том, чтобы добавить любые зависимости, необходимые для других комбинаций ОС, архитектуры и тегов сборки. Обязательно запускайте это перед каждым выпуском.
Обратите внимание, что файл go.sum также был создан после добавления зависимости. Вы можете предположить, что это файл блокировки. Но на самом деле go.mod уже предоставляет достаточно информации для 100% воспроизводимых сборок. Другой файл только для целей проверки: он содержит ожидаемые криптографические контрольные суммы содержимого определенных версий модуля.
Частично, поскольку go.sum не является файлом блокировки, он сохраняет записанные контрольные суммы для версии модуля даже после того, как вы перестанете использовать модуль. Это позволяет проверять контрольные суммы, если вы позже возобновите их использование, что обеспечивает дополнительную безопасность.
Команды, такие как go build
или go test
, автоматически загрузят все отсутствующие зависимости, хотя вы можете сделать это явно с go mod download
, чтобы предварительно заполнить локальные кэши, которые могут оказаться полезными в CI.
По умолчанию все наши пакеты из всех проектов загружаются в каталог $GOPATH/pkg/mod
. Мы обсудим это подробно позже в статье.
Обновление версий пакета
Вы можете использовать go get -u
или go get -u=patch
для обновления зависимостей до последних второстепенных или исправлений соответственно.
Хотя вы не можете сделать это для основных версий. Код, включающий Go Modules, должен технически соответствовать следующим правилам:
- Использовать semver (например теги VCS
v1.2.3
). - Если модуль версии v2 или выше, основная версия модуля должна быть указана
/vN
в конце пути к модулю, используемого в файлах go.mod, и в пути импорта пакета:
go mod edit -replace github.com/go-chi/chi=./packages/chi
Результат:
module github.com/you/hello
go 1.12
require github.com/go-chi/chi v4.0.2+incompatible
replace github.com/go-chi/chi => ./packages/chi
Вы можете удалить строку вручную или запустить:
go mod edit -dropreplace github.com/go-chi/chi
Управление зависимостями по проекту
Исторически весь код Go хранился в одном гигантском монорепо, потому что именно так Google организовывает свою кодовую базу внутренне, и это сказалось на дизайне языка.
Go Modules - это своего рода отступление от этого подхода. Вам больше не нужно держать все свои проекты под $GOPATH
.
Тем не менее, технически все ваши загруженные зависимости все еще находятся в $GOPATH/pkg/mod
. Если вы используете контейнеры Docker при локальной разработке, это может стать проблемой, потому что зависимости хранятся вне пути проекта (совместно используется с файловой системой хоста через том, смонтированный с привязкой). По умолчанию они просто не видны из вашей IDE.
Обычно это не проблема для других языков, но то, с чем я впервые столкнулся при работе с базой кода Go.
К счастью, существует несколько (недокументированных) способов решения проблемы.
Вариант 1: установите GOPATH в директории вашего проекта
Поначалу это может показаться нелогичным, но если вы запускаете Go из контейнера , вы можете переопределить его GOPATH, чтобы он указывал на каталог проекта, чтобы пакеты были доступны с хоста:
version: '3.7'
services:
app:
command: tail -f /dev/null
image: golang:1.12.6-stretch
environment:
# All of your dependencies will be found right here under /code/.go/pkg/mod
- GOPATH=/code/.go
ports:
- 8000:8000
volumes:
- ./:/code:cached
working_dir: /code
Популярные IDE должны включать возможность установки GOPATH на уровне проекта (рабочей области):
Единственным недостатком этого подхода является отсутствие взаимодействия с средой выполнения Go на хост-компьютере. Вы должны выполнить все команды Go внутри контейнера.
Вариант 2. Поставьте свои зависимости
Другой способ - скопировать зависимости вашего проекта в папку vendor:
go mod vendor
Обратите внимание на словарный запас здесь: мы НЕ включаем Go для прямой загрузки материалов в папку vendor: это невозможно с модулями. Мы просто копируем уже загруженные пакеты.
Фактически, если вы передаете свои зависимости, как в примере выше, затем очистите $GOPATH/pkg/mod
и попробуйте добавить некоторые новые зависимости в ваш проект, вы увидите следующее:
- Go восстановит кеш загрузки для всех пакетов на
$GOPATH/pkg/mod/cache
. - Все загруженные модули будут скопированы в
$GOPATH/pkg/mod
. - И, наконец, Go скопирует эти модули в папку
vendor
, удаляя примеры, тесты и некоторые другие файлы, от которых вы напрямую не зависите.
На самом деле в этой недавно созданной папке vendor пропущено много вещей:
Типичный файл Docker Compose для разработки выглядит следующим образом (обратите внимание на привязки томов):
version: '3.7'
services:
app:
command: tail -f /dev/null
image: golang:1.12.6-stretch
ports:
- 8000:8000
volumes:
# This is go modules cache, without it you will have to
# re-download all dependencies after restarting container
- modules:/go/pkg/mod/cache
- ./:/code:cached
working_dir: /code
volumes:
modules:
driver: local
Обратите внимание, что я НЕ добавляю эту папку вендора для контроля версий и не собираюсь использовать ее в production. Это строго локальный сценарий разработки, который обычно можно найти на некоторых других языках.
Однако, когда я читаю комментарии от некоторых разработчиков Go и некоторые предложения, связанные с частичным продвижением (WUT?), у меня складывается впечатление, что это не тот случай использования этой функции.
Один из комментаторов из Reddit помог мне пролить свет на это:
Обычно люди передают свои зависимости по причинам, таким как желание иметь герметичные сборки без доступа к сети, а также проверку копии зависимостей на случай, если github выйдет из строя или исчезнет репозиторий, и возможность более легкого аудита изменений в зависимостях с использованием стандартных инструментов VCS и др.
Да, не похоже, что я могу быть заинтересован
Go team предлагает вам регулярно подключаться к вендорам, устанавливая переменную среды GOFLAGS=-mod=vendor
. Я не рекомендую делать это. Использование флагов просто сломает go get
, не предоставляя никаких других преимуществ вашему ежедневному рабочему процессу:
На самом деле, единственное место, где вам нужно зарегистрироваться, это ваша IDE:
После некоторых проб и ошибок я пришел со следующей подпрограммой для добавления зависимостей вендора в этом подходе.
Шаг 1. Require
Вы требуете зависимости с go get
:
go get github.com/rs/zerolog@v1.14.3
Шаг 2. Import
Затем вы импортируете его куда-нибудь в свой код:
import (
_ "github.com/rs/zerolog"
)
Шаг 3. Vendor
Наконец, продвигайте ваши зависимости заново:
go mod vendor
Существует ожидающее предложение, позволяющее поставщику модов Go принимать определенные шаблоны модулей, которые могут решить или не решить некоторые проблемы с этим рабочим процессом.
go mod vendor
уже автоматически требует пропустить импорт, поэтому шаг 1 является необязательным в этом рабочем процессе (если вы не хотите указывать ограничения версии). Без шага 2 он не получит загруженный пакет.
Этот подход лучше взаимодействует с хост-системой, но он довольно запутан, когда дело доходит до редактирования ваших зависимостей.