Внешними зависимостями называются пакеты, не входящие в состав стандартной библиотеки.

Кроме пакетов, хранящихся в локальной файловой системе, нужно работать с пакетами из внешних источников. Например, вам может понадобиться какой-нибудь фреймворк или специфичный пакет.

В Go есть два способа загрузить пакеты:

  • вручную установить утилитой go get (подробную документацию можно найти здесь);
  • использовать список зависимостей в go.mod.

Рассмотрим оба варианта, но в дальнейшем будем пользоваться только go.mod, так как в проектах лучше выбирать второй способ. Это позволит хранить описание зависимостей локально и облегчит установку другим разработчикам.

Установка пакетов вручную утилитой go get

В случае использования go get установка стороннего пакета выглядит так:

go get github.com/username/packagename 

Утилита 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), нужно выполнить следующие команды:

git tag v1.2.3
git push --tags 

Подмена зависимостей

Иногда нужно подменить библиотеку в коде её форком (копией), но при этом не менять все пути импорта. Например, в ситуации, когда в библиотеке обнаружен критичный баг, 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