Внешними зависимостями называются пакеты, не входящие в состав стандартной библиотеки.
Кроме пакетов, хранящихся в локальной файловой системе, нужно работать с пакетами из внешних источников. Например, вам может понадобиться какой-нибудь фреймворк или специфичный пакет.
В Go есть два способа загрузить пакеты:
- вручную установить утилитой
go get
(подробную документацию можно найти здесь); - использовать список зависимостей в
go.mod
.
Рассмотрим оба варианта, но в дальнейшем будем пользоваться только go.mod
, так как в проектах лучше выбирать второй способ. Это позволит хранить описание зависимостей локально и облегчит установку другим разработчикам.
Установка пакетов вручную утилитой go get
В случае использования go get
установка стороннего пакета выглядит так:
Утилита go get
сходит на https://github.com/username/packagename
и скачает требуемый пакет, если он был найден по переданному URL. Если система контроля версий поддерживает несколько протоколов, Go по очереди попробует все. Например, в случае гита он попробует https://
и git+ssh://
.
После этого скачанные данные будут помещены в GOPATH/src/username/packagename
.
Установка зависимостей из go.mod
Из Модули в Go вы знаете, что система модулей позволяет явно прописать список зависимостей проекта. Для этого используется директива require. После запуска кода Go автоматически скачает пакеты, перечисленные в блоке require
, и закеширует их в директории $GOPATH/pkg/mod
.
Например, если в модуле есть единственная зависимость от библиотеки github.com/stretchr/testify
, то go.mod
будет иметь вид:
module somemodule
go 1.16
require github.com/stretchr/testify v1.7.0
Также будет создан специальный файл go.sum
, обеспечивающий стопроцентную воспроизводимость запусков. Он содержит хеш-суммы всех модулей и тем самым гарантирует воспроизводимую установку модулей на разных окружениях. Подробнее можно прочитать здесь.
Note
Зачастую не нужно вручную прописывать зависимости в
go.mod
. Go может автоматически обновить список зависимостей вgo.mod
при запуске программы (имеется в виду вызовgo run
,go build
,go test
), если путь импорта библиотеки — это URL до репозитория с кодом и не требуется версия библиотеки, отличная от актуальной.
Но что делать, если нужна не самая свежая версия библиотеки? В этом случае можно указать в директиве require
требуемую версию. В примере это была v1.7.0
.
Версионирование
В Go используется семантическое версионирование (Semantic Versioning, или semver).
Семантическое версионирование — это общепринятый формат нумерации релизов (пакетов, модулей, библиотек и т. д.). Версия пишется в формате vX.Y.Z
, где:
X
— мажорная версия (major),Y
— минорная версия (minor),Z
— патч-версия (patch).
Например, у пакета с версией v1.2.3
есть мажорная версия 1
, минорная 2
и патч-версия 3
.
- Патч-версию стоит изменять в случае небольших исправлений в пакете.
- Минорная версия меняется в случае добавления новой функциональности.
- Мажорная версия меняется в случае поломки обратной совместимости с предыдущей версией кода.
Увеличение старшего разряда должно происходить с одновременным обнулением младших разрядов. То есть увеличение мажорной версии обнуляет минорную и патч-версию, а увеличение минорной версии обнуляет только патч-версию.
У семантической версии может быть пререлизный суффикс через дефис. Например, v1.2.3-beta
.
В случае работы с Git версия пакета определяется тегом релиза (подробнее о тегах можно прочитать здесь). Теги — это просто метаданные. Есть два типа тегов — легковесные и аннотированные. Первые просто помечают коммит версией, вторые содержат дополнительно кучу информации — имя автора, дату создания, контрольную сумму и так далее.
Если требуется увеличить версию пакета до некоторого значения (например, v1.2.3
), нужно выполнить следующие команды:
Подмена зависимостей
Иногда нужно подменить библиотеку в коде её форком (копией), но при этом не менять все пути импорта. Например, в ситуации, когда в библиотеке обнаружен критичный баг, PR отправлен, но нет времени ждать, когда его зальют.
На помощь приходит директива replace. В прошлом уроке её использовали, чтобы определить положение локального модуля в файловой системе. Но эта директива также позволяет заменить один внешний модуль (или определённую его версию) на другой.
Это можно сделать так:
replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
golang.org/x/net => example.com/fork/net v1.4.5
)
Сохранение зависимостей
Итак, модули позволяют исчерпывающе описать, от каких библиотек зависит пакет, и разработчики получают возможность скачать все требуемые зависимости.
Но что, если код зависит от репозитория, который внезапно был удалён или стал приватным?
Если надо каждый раз ходить за ним в соответствующие репозитории систем контроля версий, это проблема. Репозиторий может быть недоступен из-за блокировок, либо это может быть локальный репозиторий, к которому нужно подключаться через proxy, либо меинтейнер может просто удалить свой репозиторий по каким-то своим причинам.
Есть два решения: вендоринг и go proxy
.
Вендоринг
Вендоринг — это практика, при которой исходный код зависимостей хранится прямо в модуле.
Note
В Go завендоренные библиотеки должны храниться в папке
vendor
рядом с файломgo.mod
. Чтобы завендорить все используемые библиотеки, нужно выполнить командуgo mod vendor
.
Но у вендоринга есть недостатки:
- Папка
vendor
не используется по умолчанию, если при запуске программы не выставлен флаг-mod=vendor
. - В случае больших монореп эта папка растёт бесконтрольно, что замедляет клонирование репозитория (а это происходит постоянно при прогонах автотестов и прочего CI/CD).
- Пулл-реквесты, на 99% состоящие из завендоренных файлов, сложно ревьюить.
Большую часть этих проблем можно решить прокси-серверами модулей.
Прокси-серверы модулей
Прокси-сервер модулей — это выделенный сервер для хранения модулей в скачанном и скомпилированном виде.
Использование прокси-сервера модулей имеет ряд преимуществ:
- Папка
vendor
больше не нужна. Следовательно, в репозитории нет лишних файлов, на ревью они не будут загрязнять диф. - Прокси-сервера позволяют сильно ускорить загрузку по сравнению с клиентами систем контроля версий.
Использование прокси-серверов
Адрес прокси-сервера модулей задаётся переменной окружения GOPROXY
. Значение этой переменной — перечисленные через запятую адреса прокси-серверов или специальное значение direct
. Например:
GOPROXY=https://proxy.golang.org/,direct
При скачивании зависимостей go get
идёт по URL из этой переменной и пытается скачать требуемую зависимость. Если ни на одном сервере нет нужной зависимости или было встречено специальное значение direct
, то go get
отправится прямо в репозиторий соответствующей системы контроля версий.
Прокси-сервер по умолчанию — proxy.golang.org
. Этот публичный прокси-сервер поддерживается компанией Google и непрерывно кеширует все скачиваемые через него модули.
Checksum
В файле go.sum
хранится информация для стопроцентной воспроизводимости билдов. Но есть проблема: на момент первого билда нет информации о контрольных суммах модулей, и в этот момент злонамеренный прокси-сервер в состоянии отправить нам модифицированный код. Контрольные суммы — это просто хеши, по которым можно определить, изменилось ли содержимое модуля.
Note
Для решения этой проблемы в экосистеме Go есть база данных контрольных сумм модулей — checksum.
При скачивании нового модуля Go сходит в сконфигурированную базу контрольных сумм и проверит, совпадает ли контрольная сумма скачанного кода с той, что хранится в базе. Если это не так, Go выдаст ошибку и закончит работу.
А если в файле go.sum
уже есть контрольная сумма скачиваемого модуля, то checksum
использоваться не будет.
Адрес используемой checksum
определяется переменной окружения GOSUMDB
(по умолчанию — sum.golang.org
).
Также можно полностью выключить использование checksum
, установив GOSUMDB=off
.
Приватные зависимости
Часто в разрабатываемом коде используются как публичные, так и приватные модули. И нужно, чтобы приватные модули не утекли в публичный прокси-сервер зависимостей. Например, вы разрабатываете модуль для внутренних нужд компании и не хотите передавать код конкурентам.
Есть несколько вариантов, как этого можно избежать:
- Поднять свой приватный прокси (раз, два, три).
- Установить
GOPROXY
в значениеdirect
, чтобы всегда ходить в обход прокси-серверов. - Установить переменную
GOPRIVATE
. Значение этой переменной равно маске путей импорта модулей (например,GOPRIVATE=*.internal.company.com
), для которых не нужно использовать прокси-серверы зависимостей, а нужно ходить напрямую в системы контроля версий.
Как искать подходящие зависимости
Вот несколько советов о том, как среди множества похожих пакетов выбрать тот, который стоит использовать:
- Чем больше звёзд у проекта, тем лучше.
- Чем больше контрибьюторов, тем меньше вероятность, что проект умрёт.
- Чем дольше не было коммитов, тем больше вероятность, что их уже не будет и баги в либе придётся чинить самим.
- Чем лучше покрытие библиотеки тестами, тем меньше вероятность словить баг в зависимости.
- Если репозиторий фигурирует в известном списке репозиториев, скорее всего ему можно доверять (в случае Go можно обращаться, например, к https://github.com/avelino/awesome-go).
- Чем лучше проект документирован, тем больше вероятность, что не придётся искать ответы на свои вопросы в исходниках.
📂 Go | Последнее изменение: 23.08.2024 09:09