17. Go Modules — управление зависимостями
Глубокий разбор системы модулей в Go: от
go.modдо Minimal Version Selection (MVS), workspaces и приватных модулей. Зачем это нужно: модули — это контракт воспроизводимости сборки. Без понимания внутренностей легко получить “у меня собирается, у тебя нет” и потерять часы на отладку.
Содержание
Заголовок раздела «Содержание»1. Базовое API
Заголовок раздела «1. Базовое API»1.1. Что такое Go module
Заголовок раздела «1.1. Что такое Go module»Go module — единица версионирования и распространения кода. Это коллекция Go-пакетов в дереве каталогов с файлом go.mod в корне.
- Введены в Go 1.11 (август 2018) как экспериментальная фича.
- С Go 1.16 — режим модулей включён по умолчанию (
GO111MODULE=on). - В 2026 году (Go 1.22+) —
GOPATH-режим фактически мёртв, все новые проекты используют модули.
Назначение модулей:
- Воспроизводимые сборки (одинаковый код собирается одинаково на любой машине).
- Версионирование зависимостей по семантике semver.
- Возможность работать с кодом вне
$GOPATH/src.
1.2. GOPATH era (исторически — кратко)
Заголовок раздела «1.2. GOPATH era (исторически — кратко)»До модулей весь Go-код жил в едином дереве $GOPATH/src/.... Импорт был привязан к физическому пути:
$GOPATH/ src/ github.com/user/myapp/ main.go github.com/sirupsen/logrus/ logrus.goПроблемы GOPATH:
- Нет версионирования —
go getвсегда тянулmaster. - Невозможно иметь две версии одной библиотеки в проекте.
- Зависимости были глобальными.
- Workaround:
dep,glide,godep,vendor/— зоопарк инструментов.
Сейчас стоит знать только что GOPATH существовал, и его модель полностью устарела.
1.3. Структура go.mod
Заголовок раздела «1.3. Структура go.mod»module github.com/example/myapp
go 1.22
toolchain go1.22.3
require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4)
require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect)
replace github.com/old/lib => github.com/new/lib v1.2.0
exclude github.com/buggy/dep v1.0.5
retract ( v1.0.0 // Опубликовано по ошибке [v1.2.0, v1.2.3] // Содержит security baга)Директивы построчно
Заголовок раздела «Директивы построчно»module — каноническое имя модуля. Это путь, по которому модуль импортируется. Обычно совпадает с git-репозиторием.
module github.com/example/myappgo — минимальная версия Go, нужная для сборки модуля. В Go 1.21+ это уже строгое требование (раньше — только подсказка).
go 1.22toolchain (Go 1.21+) — предложение использовать конкретную toolchain (минорную версию + патч). Команда go автоматически скачает нужную версию toolchain через прокси, если текущая старее.
toolchain go1.22.3require — прямые зависимости. Можно одной директивой или блоком.
require github.com/sirupsen/logrus v1.9.3
// или
require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0)// indirect — зависимость, которая используется не нами напрямую, а нашими зависимостями. Появляется в go.mod, потому что в выбранной нами версии напрямую её нет (например, если зависимость без go.mod).
replace — подменяет источник зависимости. Применяется только в корневом модуле (не в зависимостях).
// Локальная разработка — указывает на папку рядомreplace github.com/example/lib => ../lib
// Подмена на форкreplace github.com/old/lib => github.com/me/lib v1.2.3
// Версия + версияreplace github.com/foo/bar v1.0.0 => github.com/foo/bar v1.0.1exclude — запрещает использование конкретной версии.
exclude github.com/buggy/dep v1.0.5retract (Go 1.16+) — автор объявляет свои версии “отозванными”. go get не подтянет их по умолчанию.
retract ( v1.0.0 // Wrong API [v1.2.0, v1.3.0] // Security issue)1.4. Файл go.sum — checksums
Заголовок раздела «1.4. Файл go.sum — checksums»go.sum содержит криптографические хэши (SHA-256, base64) для каждой версии каждой зависимости.
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=Что лежит в строках:
- Хэш самого архива модуля (содержимое).
- Хэш файла
go.modмодуля.
Назначение:
- Защита от подмены: при
go buildGo сверяет загруженный код с хэшем вgo.sum. - Это safety net, даже если кто-то перепишет тег в репо или взломает прокси.
go.sum обязательно коммитится в репозиторий.
1.5. Основные команды
Заголовок раздела «1.5. Основные команды»go mod init <module-path> — создаёт go.mod.
go mod init github.com/example/myappgo mod tidy — синхронизирует go.mod/go.sum с реальным состоянием кода:
- Добавляет недостающие зависимости.
- Удаляет неиспользуемые.
- Обновляет
// indirect.
Запускать перед каждым коммитом.
go mod tidyС Go 1.17+ можно передать целевую версию Go:
go mod tidy -go=1.22go mod download — скачивает модули в локальный кэш ($GOMODCACHE, по умолчанию $GOPATH/pkg/mod).
go mod downloadgo mod download -x # с подробным выводомgo mod download -json # JSON-выход для скриптовgo mod vendor — копирует все зависимости в папку vendor/ рядом с go.mod.
go mod vendorЕсли папка vendor/ существует, go build автоматически использует её (-mod=vendor).
go mod why <package> — объясняет, зачем нужна зависимость (через какую цепочку импортов).
go mod why github.com/davecgh/go-spew# Вывод:# github.com/stretchr/testify/assert# github.com/davecgh/go-spew/spewgo mod graph — выводит граф зависимостей (модуль → зависимость).
go mod graph | head -5# github.com/example/myapp github.com/sirupsen/logrus@v1.9.3# github.com/example/myapp github.com/stretchr/testify@v1.8.4go get — добавляет/обновляет зависимости.
# Latest stablego get github.com/sirupsen/logrus
# Конкретная версияgo get github.com/sirupsen/logrus@v1.9.3
# Tag/branchgo get github.com/sirupsen/logrus@master
# Удалитьgo get github.com/sirupsen/logrus@none
# Обновить все прямые зависимостиgo get -u ./...
# Обновить с patch-версиямиgo get -u=patch ./...С Go 1.17+ go get только для модификации зависимостей, не для установки бинарников.
go install — установить бинарник в $GOPATH/bin (или $GOBIN):
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.0@version обязателен — это правило с Go 1.16+.
1.6. Semver в Go
Заголовок раздела «1.6. Semver в Go»Go использует семантическое версионирование (semver): MAJOR.MINOR.PATCH.
MAJOR— несовместимые API-изменения.MINOR— новый функционал, обратно совместимый.PATCH— багфиксы, обратно совместимые.
Версии — это git-теги в формате vX.Y.Z:
v1.2.3✓v0.0.1✓1.2.3✗ (безv)
1.7. Pseudo-versions
Заголовок раздела «1.7. Pseudo-versions»Если в репо нет тегов или нужна конкретная коммитная версия, Go генерирует псевдо-версию:
v0.0.0-20231215120000-abcdef123456└──┬──┘ └──────┬──────┘ └────┬────┘ base timestamp UTC commit hash (12 символов)Используется автоматически при go get github.com/user/repo@<commit-sha>.
Структура:
- База — мажорная версия или
v0.0.0, если нет тегов. - Timestamp — UTC время коммита, формат
YYYYMMDDhhmmss. - Hash — первые 12 символов SHA-1 коммита.
1.8. Major version >= 2
Заголовок раздела «1.8. Major version >= 2»С версии v2.0.0 модуль обязан иметь /v2 в module path. Это правило Semantic Import Versioning.
// v2 modulemodule github.com/example/lib/v2
go 1.22Импорт:
import "github.com/example/lib/v2/pkg/foo"Зачем? Чтобы в одном проекте можно было использовать v1 и v2 одновременно — они для Go разные модули.
Особые случаи:
v0.x.yиv1.x.y— без суффикса.v2.x.y+— с суффиксом/v2,/v3, и т.д.
1.9. Minimal Version Selection (MVS)
Заголовок раздела «1.9. Minimal Version Selection (MVS)»Алгоритм выбора версий в Go — Minimal Version Selection (MVS). Это ключевое отличие от npm/Cargo, где используется SAT-solver.
Идея: для каждой зависимости берётся максимальная из минимально требуемых версий по всем go.mod в дереве зависимостей.
Модуль A требует:
B v1.2.0C v1.5.0
Модуль B требует:
C v1.7.0
Какую версию C выберет MVS? v1.7.0 — потому что это максимум из всех минимально требуемых версий (1.5.0 от A и 1.7.0 от B).
Почему именно MVS
Заголовок раздела «Почему именно MVS»- Детерминизм: один и тот же
go.modвсегда даёт одинаковый результат, независимо от истории. - Предсказуемость: не надо хранить lock-файл с конкретными версиями — версии в
go.modсами по себе lock. - Простота: алгоритм описывается в одну строчку (
max(requirements)). - Безопасность: никогда не выбирается версия выше, чем явно потребовали разработчики.
Чем отличается от других
Заголовок раздела «Чем отличается от других»| Менеджер | Алгоритм |
|---|---|
| npm, yarn | SAT solver + lockfile (package-lock.json) |
| Cargo | SAT solver + Cargo.lock |
| Go | MVS — детерминированный без отдельного lock |
1.10. replace для разных задач
Заголовок раздела «1.10. replace для разных задач»Локальная разработка
Заголовок раздела «Локальная разработка»replace github.com/example/lib => ../libКогда разрабатываешь lib параллельно с приложением. Лучше использовать workspaces (см. ниже).
replace github.com/upstream/lib => github.com/me/lib v1.2.3-patchedКогда временно используешь свой форк, ожидая мержа PR.
Подмена версии
Заголовок раздела «Подмена версии»replace github.com/foo/bar v1.0.0 => github.com/foo/bar v1.0.1Когда нужна конкретная версия (например, с security patch), но обновлять все остальные не хочется.
replace не работает в зависимостях
Заголовок раздела «replace не работает в зависимостях»Если зависимость B имеет replace, и наш проект импортирует B, директива replace в B игнорируется. Только корневой go.mod управляет replace.
1.11. Indirect dependencies
Заголовок раздела «1.11. Indirect dependencies»Комментарий // indirect ставится в require у пакетов, которые:
- Не импортируются нашим кодом напрямую.
- Но нужны, потому что их используют наши зависимости.
require ( github.com/sirupsen/logrus v1.9.3 github.com/davecgh/go-spew v1.1.1 // indirect)go mod tidy сам ставит и убирает эти комментарии. Руками их трогать не надо.
1.12. Vendor
Заголовок раздела «1.12. Vendor»Папка vendor/ — копия всех зависимостей в репо.
go mod vendorКогда нужна:
- Сборки без интернета (CI, изолированные сети).
- Аудит зависимостей.
- Гарантия, что код собирается даже если зависимость удалена с GitHub.
Минусы:
- Раздувает репо.
- Дополнительный шаг при обновлении (
go mod vendorпослеgo mod tidy).
С Go 1.14+ vendor включается автоматически, если папка существует:
go build # автоматически -mod=vendorgo build -mod=mod # явно отключить vendor1.13. Private modules
Заголовок раздела «1.13. Private modules»Модули из приватных репозиториев (GitHub Enterprise, GitLab, Bitbucket).
GOPRIVATE
Заголовок раздела «GOPRIVATE»export GOPRIVATE=github.com/mycompany/*,gitlab.com/myorg/*Эта переменная — список glob-паттернов. Модули, попадающие под паттерн:
- Не идут через
proxy.golang.org. - Не проверяются через
sum.golang.org. - Качаются напрямую из VCS.
GONOSUMCHECK / GONOSUMDB
Заголовок раздела «GONOSUMCHECK / GONOSUMDB»Устаревшие — теперь GOPRIVATE решает всё.
Более тонко:
GONOPROXY— какие модули не через прокси.GONOSUMDB— какие модули не через sum DB.
Аутентификация
Заголовок раздела «Аутентификация»Для HTTPS:
machine github.com login mytoken password x-oauth-basicДля SSH (часто):
git config --global url.git@github.com:.insteadOf https://github.com/1.14. Module proxy
Заголовок раздела «1.14. Module proxy»По умолчанию модули скачиваются через Google’s module proxy: https://proxy.golang.org.
Что делает прокси:
- Кэширует модули (CDN).
- Архивирует версии (даже если git-репо удалят).
- Ускоряет сборки.
Переменные:
GOPROXY=https://proxy.golang.org,directGOSUMDB=sum.golang.orgСемантика GOPROXY:
- Список URL через запятую.
direct— качать напрямую из VCS.off— никаких загрузок (только из кэша).- При ошибке
404или410— fallback к следующему.
Для приватных модулей часто свой прокси (Athens, Artifactory).
1.15. Workspaces (Go 1.18+)
Заголовок раздела «1.15. Workspaces (Go 1.18+)»Когда работаешь над несколькими модулями одновременно, без replace помогают workspaces — файл go.work.
myproject/ go.work app/ go.mod main.go lib/ go.mod lib.gogo work init ./app ./libСоздаёт go.work:
go 1.22
use ( ./app ./lib)Теперь в app импорт github.com/example/lib (даже если он не опубликован) идёт из ./lib. Без модификации go.mod.
go.work НЕ коммитится обычно — это локальный файл разработчика. Файл go.work.sum — checksums для модулей, добавленных через go work (не в go.sum ни одного модуля).
Команды:
go work init ./mod1 ./mod2go work use ./mod3 # добавитьgo work sync # синхронизировать с require'ами модулейgo work edit -replace ...1.16. Полезные env vars
Заголовок раздела «1.16. Полезные env vars»| Переменная | Значение |
|---|---|
GOPROXY | Прокси для модулей (по умолчанию https://proxy.golang.org,direct) |
GOSUMDB | Sum database (по умолчанию sum.golang.org) |
GOPRIVATE | Glob приватных модулей |
GONOPROXY | Модули не через прокси |
GONOSUMDB | Модули не через sum DB |
GOFLAGS | Глобальные флаги для go команд (например, -mod=mod) |
GOMODCACHE | Папка локального кэша модулей (по умолчанию $GOPATH/pkg/mod) |
GOTOOLCHAIN | Управление автоматическим переключением toolchain (auto/local/go1.22.0+auto) |
go envgo env GOPROXY GOPRIVATE2. Под капотом
Заголовок раздела «2. Под капотом»2.1. Локальный кэш модулей
Заголовок раздела «2.1. Локальный кэш модулей»$GOPATH/pkg/mod/├── cache/│ ├── download/│ │ └── github.com/.../@v/│ │ ├── v1.9.3.info│ │ ├── v1.9.3.mod│ │ ├── v1.9.3.zip│ │ └── v1.9.3.ziphash│ └── lock└── github.com/.../<module>@v1.9.3/ └── ... распакованные исходники.info— JSON с метаданными версии (timestamp, hash)..mod— копияgo.modмодуля..zip— архив исходников..ziphash— sha256 для verification.
Распакованная папка — read-only (chmod 555). Это намеренно: модули неизменны.
2.2. Алгоритм MVS детально
Заголовок раздела «2.2. Алгоритм MVS детально»Псевдокод:
func MVS(rootModule) map[string]Version { result := map[string]Version{} queue := []Module{rootModule} visited := map[string]bool{}
for len(queue) > 0 { m := queue[0] queue = queue[1:]
if visited[m.Path] { continue } visited[m.Path] = true
for _, req := range m.Requires { existing, ok := result[req.Path] if !ok || semver.Compare(req.Version, existing) > 0 { result[req.Path] = req.Version } queue = append(queue, loadModule(req.Path, req.Version)) } } return result}Сложность: O(V + E) по графу зависимостей. Линейно, без backtracking.
2.3. Как считается go.sum
Заголовок раздела «2.3. Как считается go.sum»При первой загрузке модуля:
- Скачивается
.zipс прокси. - Считается SHA-256 от ZIP-файла.
- Считается SHA-256 от
go.mod. - Хэши пишутся в
go.sumв формате h1:hash. - Если есть
GOSUMDB, хэш сверяется с базой sum.golang.org.
При последующих сборках:
- Если модуль в кэше — проверка по
.ziphash. - Если нет — заново скачивается + проверка по
go.sum.
Несовпадение хэшей → ошибка checksum mismatch → сборка падает.
2.4. Как Go выбирает toolchain (1.21+)
Заголовок раздела «2.4. Как Go выбирает toolchain (1.21+)»С Go 1.21+ команда go понимает директиву toolchain и автоматически переключается между минорными версиями Go.
Алгоритм:
- Прочитать
goиtoolchainизgo.mod. - Если установленная версия
>=toolchain → использовать установленную. - Иначе — скачать toolchain через
GOTOOLCHAINмеханизм. - Запустить нужную команду через скачанную toolchain.
Управление:
GOTOOLCHAIN=auto(default) — автоматический выбор.GOTOOLCHAIN=local— использовать только локальную, ошибка если не подходит.GOTOOLCHAIN=go1.22.3— принудительно эту.GOTOOLCHAIN=go1.22.3+auto— минимум эта, может быть выше.
2.5. Lazy loading (Go 1.17+)
Заголовок раздела «2.5. Lazy loading (Go 1.17+)»До Go 1.17 go.mod содержал только прямые зависимости. Косвенные не записывались — Go их вычислял “на лету”, загружая go.mod каждой зависимости рекурсивно. Это было медленно.
С Go 1.17 в go.mod пишутся все зависимости (включая // indirect), и Go может не загружать go.mod зависимостей в большинстве случаев. Это lazy loading.
Эффект:
go.modстал больше (зато сборки быстрее).go mod tidy -go=1.17+создаёт более полный список.
2.6. Module resolution flow
Заголовок раздела «2.6. Module resolution flow»При import "github.com/example/lib/pkg":
- Найти модуль с самым длинным префиксом этого импорта в
go.mod(github.com/example/lib). - Найти в
requireверсию. - Применить
replace, если есть. - Загрузить модуль из кэша (или скачать).
- Импортировать пакет
pkgиз модуля.
Резолюция модуля по импорт-пути — попытки:
github.com/example/lib/pkg→ модульgithub.com/example/lib/pkggithub.com/example/lib→ модульgithub.com/example/lib, пакетpkggithub.com/example→ модульgithub.com/example, пакетlib/pkg
Берётся самый длинный совпавший модуль.
2.7. GOSUMDB — public checksum database
Заголовок раздела «2.7. GOSUMDB — public checksum database»sum.golang.org — приложение, которое:
- Хранит хэши всех публичных модулей.
- Подписывает их Merkle tree (Trillian).
- Гарантирует “no equivocation” — нельзя выдать разные хэши разным клиентам.
При go get:
- Скачали
.zipи.mod. - Запросили хэши у
sum.golang.org. - Сверили локально посчитанные хэши с базой.
- Если совпало — пишем в
go.sum.
Это защищает от компрометации прокси или git-репо.
2.8. go.mod парсер — формат
Заголовок раздела «2.8. go.mod парсер — формат»Внутренне go.mod парсится пакетом golang.org/x/mod/modfile. Формат:
- Один синтаксис похожий на Go (директивы + блоки в скобках).
- Поддерживает комментарии
//и/* */. // indirect— специальный markdown-комментарий.
Можно программно манипулировать:
import "golang.org/x/mod/modfile"
data, _ := os.ReadFile("go.mod")f, _ := modfile.Parse("go.mod", data, nil)
for _, r := range f.Require { fmt.Println(r.Mod.Path, r.Mod.Version)}3. Gotchas
Заголовок раздела «3. Gotchas»3.1. “missing go.sum entry”
Заголовок раздела «3.1. “missing go.sum entry”»go: github.com/foo/bar@v1.0.0: missing go.sum entryПоявляется, когда в go.mod есть зависимость, но в go.sum нет её хэша. Решение:
go mod tidy# илиgo mod downloadЧаще всего происходит, когда кто-то редактировал go.mod вручную или мержил конфликт.
3.2. // indirect не удалять руками
Заголовок раздела «3.2. // indirect не удалять руками»Если убрать комментарий // indirect руками, go mod tidy его вернёт. Если удалить саму строку — а потом окажется, что она нужна — go mod tidy добавит обратно. Не трогай.
3.3. v2+ без /v2 в module path
Заголовок раздела «3.3. v2+ без /v2 в module path»Самая частая ошибка авторов библиотек. Если тег v2.0.0, а module path остался github.com/example/lib, то:
go get github.com/example/lib@v2.0.0падает.- Поведение: Go ругается, что мажорная версия не совпадает с module path.
Правильно: module github.com/example/lib/v2 и импорт github.com/example/lib/v2.
3.4. Забыл go mod tidy → сломанный CI
Заголовок раздела «3.4. Забыл go mod tidy → сломанный CI»import "github.com/foo/bar" // не в go.modЛокально может работать (если модуль в кэше), а в CI — cannot find module providing package. Лекарство: всегда go mod tidy перед коммитом.
В CI можно проверить:
go mod tidygit diff --exit-code go.mod go.sumЕсли go.mod/go.sum отличаются после tidy — fail.
3.5. Разный go.sum на разных машинах (было до Go 1.17)
Заголовок раздела «3.5. Разный go.sum на разных машинах (было до Go 1.17)»В старых версиях Go go mod tidy иногда добавлял разные // indirect зависимости в зависимости от того, что было в кэше. С Go 1.17 это решено — теперь алгоритм детерминированный.
Если до сих пор такое видишь — обнови Go.
3.6. replace не передаётся транзитивно
Заголовок раздела «3.6. replace не передаётся транзитивно»// в зависимости Breplace github.com/foo/bar => ../barЭта replace не работает, когда твой проект импортирует B. Только корневой go.mod диктует replace.
Это намеренно — иначе зависимости могли бы внезапно подменить что угодно.
3.7. Кэш модулей read-only
Заголовок раздела «3.7. Кэш модулей read-only»$GOPATH/pkg/mod/github.com/foo/bar@v1.0.0/— chmod 555. Попытка cd туда и vim file.go — permission denied. Это намеренно. Если надо отредактировать зависимость:
- Сделать форк и
replace. - Скопировать в
vendor/. - Использовать
go.work.
3.8. go get -u агрессивно обновляет
Заголовок раздела «3.8. go get -u агрессивно обновляет»go get -u ./...Обновит все прямые зависимости до latest. Это часто ломает API. Лучше:
go get -u=patch ./... # только patch-версии# или адресноgo get github.com/foo/bar@latest3.9. Версии v0.x.y могут ломаться
Заголовок раздела «3.9. Версии v0.x.y могут ломаться»Semver гласит: v0.x.y — нестабильная. В Go это значит, что breaking changes возможны в любом minor-апгрейде v0.1 → v0.2. Большинство популярных библиотек живут в v0.x годами (например, golang.org/x/*).
3.10. +incompatible суффикс
Заголовок раздела «3.10. +incompatible суффикс»github.com/foo/bar v2.0.0+incompatibleЭто значит: репо имеет тег v2.0.0, но go.mod либо отсутствует, либо без /v2. Go разрешает использовать такие “несовместимые” версии для обратной совместимости со старым кодом. Но это анти-паттерн: автор должен починить go.mod.
3.11. Pseudo-version под капотом не “случайные”
Заголовок раздела «3.11. Pseudo-version под капотом не “случайные”»v0.0.0-20231215120000-abcdef123456Это:
v0.0.0— базовая (нет тегов).20231215120000— UTC коммита.abcdef123456— первые 12 hex от sha-1.
Если репо имеет тег v1.2.0, и ты go get @<commit-after-tag>, то будет:
v1.2.1-0.20231215120000-abcdef123456v1.2.1-0.<...> означает “после v1.2.0, до v1.2.1, конкретный коммит”.
3.12. go.work в репо
Заголовок раздела «3.12. go.work в репо»go.work обычно не коммитят — это локальная настройка разработчика. Если случайно закоммитил — CI может использовать локальные пути и сломаться.
Добавь go.work в .gitignore, если работа над несколькими модулями локальная.
3.13. Изменение module path сломает зависимых
Заголовок раздела «3.13. Изменение module path сломает зависимых»Если переименовать module github.com/foo/bar → module github.com/foo/baz, все, кто импортирует первый путь, сломаются. Обычно это делается через v2 (major bump).
3.14. exclude редко нужен
Заголовок раздела «3.14. exclude редко нужен»exclude запрещает конкретную версию, но только в корневом модуле. На практике почти всегда лучше replace или просто более новая require.
3.15. Прокси-кэш может протухнуть
Заголовок раздела «3.15. Прокси-кэш может протухнуть»Если в репо переписали тег (force-push), proxy.golang.org всё равно отдаст старую версию. Это безопасность. Решение: создать новую версию (никогда не переписывай теги в публичных репо!).
3.16. GO111MODULE=auto уже не работает как раньше
Заголовок раздела «3.16. GO111MODULE=auto уже не работает как раньше»В старых версиях Go auto означал “включить модули, если есть go.mod”. С Go 1.16+ модули по умолчанию всегда включены, и GO111MODULE=auto — синоним on.
3.17. go install с pkg + version (Go 1.16+)
Заголовок раздела «3.17. go install с pkg + version (Go 1.16+)»go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestЭто устанавливает бинарь, не модифицируя go.mod/go.sum текущего проекта. Хорошо для тулинга. До Go 1.16 эта команда использовала go.mod проекта (и могла его сломать).
3.18. go mod tidy при разных версиях Go
Заголовок раздела «3.18. go mod tidy при разных версиях Go»go mod tidy -go=1.22Без флага -go используется директива go из go.mod. Если поменял go 1.20 → go 1.22, лучше явно пересоздать tidy.
4. Best practices
Заголовок раздела «4. Best practices»4.1. Tidy перед коммитом
Заголовок раздела «4.1. Tidy перед коммитом»В hook’е pre-commit:
#!/bin/shgo mod tidygit diff --exit-code go.mod go.sum || (echo "go.mod/go.sum changed after tidy" && exit 1)4.2. Pin версии явно
Заголовок раздела «4.2. Pin версии явно»Не оставляй latest — всегда конкретная версия в go.mod (это и так делает go get, но иногда правят руками).
4.3. Минимальная директива go
Заголовок раздела «4.3. Минимальная директива go»Указывай минимальную требуемую версию Go: go 1.22, если используешь фичи Go 1.22. Не пиши go 1.18, если фактически нужен for-range int.
4.4. replace — sparingly
Заголовок раздела «4.4. replace — sparingly»replace — это код-смрад в чужих модулях. Если ты публикуешь библиотеку, твой go.mod не должен иметь replace (или это специально для тестов).
Если временно — отметь TODO.
4.5. Не игнорь go.sum
Заголовок раздела «4.5. Не игнорь go.sum»go.sum всегда в репо. Без него go.mod не воспроизводимый.
4.6. vendor/ — только если реально нужен
Заголовок раздела «4.6. vendor/ — только если реально нужен»Большинству проектов не нужен vendor/. Используй, если:
- Сборка в изолированной сети.
- Аудит/compliance требует копии.
4.7. Module path = URL репо
Заголовок раздела «4.7. Module path = URL репо»module github.com/myorg/myapp совпадает с GitHub URL. Это не строгое правило, но если иначе — go get не работает. Кастомные домены требуют meta-tags (см. документацию).
4.8. go.work для локальной мульти-модули разработки
Заголовок раздела «4.8. go.work для локальной мульти-модули разработки»Вместо replace для локальной работы — go.work. Не коммитится.
4.9. Используй tools.go для dev-инструментов
Заголовок раздела «4.9. Используй tools.go для dev-инструментов»Если проект зависит от golangci-lint, mockgen и т.п. — отдельный файл tools.go:
//go:build tools// +build tools
package tools
import ( _ "github.com/golang/mock/mockgen" _ "github.com/golangci/golangci-lint/cmd/golangci-lint")Это держит их в go.mod/go.sum, но не включает в финальный бинарь. С Go 1.24+ есть прямая поддержка tool directive в go.mod — стоит использовать её.
4.10. Регулярно go list -m -u all
Заголовок раздела «4.10. Регулярно go list -m -u all»Чтобы видеть, какие апдейты доступны:
go list -m -u allПокажет колонку с доступной более новой версией:
github.com/sirupsen/logrus v1.9.0 [v1.9.3]4.11. Не используй master/main в go.mod
Заголовок раздела «4.11. Не используй master/main в go.mod»require github.com/foo/bar master // ✗Это превратится в pseudo-версию автоматически, но в go.mod лучше явная версия.
4.12. CI проверка go.mod/go.sum
Заголовок раздела «4.12. CI проверка go.mod/go.sum»- name: Verify modules run: | go mod tidy git diff --exit-code go.mod go.sum
- name: Verify downloads run: go mod download -x5. Вопросы на собесе
Заголовок раздела «5. Вопросы на собесе»5.1. Что такое Go module?
Заголовок раздела «5.1. Что такое Go module?»Единица версионирования и распространения Go-кода. Дерево пакетов с go.mod в корне. Версионирование по semver.
5.2. Чем go.mod отличается от package.json (npm)?
Заголовок раздела «5.2. Чем go.mod отличается от package.json (npm)?»- В
go.modзависимости + их версии — это сразу lock-файл. - В npm
package.json— только диапазоны, конкретные версии вpackage-lock.json. - В Go используется MVS, в npm — SAT solver.
5.3. Что такое MVS?
Заголовок раздела «5.3. Что такое MVS?»Minimal Version Selection — алгоритм Go: выбирается максимум из минимально требуемых версий по всему графу зависимостей. Детерминирован, не нуждается в отдельном lock-файле.
5.4. Чем replace отличается от require?
Заголовок раздела «5.4. Чем replace отличается от require?»requireобъявляет зависимость и версию.replaceподменяет источник или версию зависимости (только в корневом модуле).
5.5. Что значит // indirect?
Заголовок раздела «5.5. Что значит // indirect?»Зависимость, которая нужна не нашему коду напрямую, а нашим зависимостям, но которой нет в go.mod зависимости (или версия в нашем go.mod выше). Управляется go mod tidy.
5.6. Зачем нужен go.sum?
Заголовок раздела «5.6. Зачем нужен go.sum?»Содержит криптографические хэши зависимостей. Защищает от подмены кода: при сборке хэши пересчитываются и сверяются. Обязательно коммитится.
5.7. Что такое pseudo-version?
Заголовок раздела «5.7. Что такое pseudo-version?»Автоматически генерируемая версия для конкретного коммита: v0.0.0-YYYYMMDDhhmmss-<12hex>. Используется, когда нет тега или нужна точная коммитная версия.
5.8. Что такое semantic import versioning?
Заголовок раздела «5.8. Что такое semantic import versioning?»Правило Go: модуль с мажорной версией ≥ 2 должен иметь /v2, /v3 и т.д. в module path. Это позволяет иметь несколько мажорных версий в одном проекте как разные модули.
5.9. Что делает go mod tidy?
Заголовок раздела «5.9. Что делает go mod tidy?»- Добавляет недостающие импорты в
go.mod. - Удаляет неиспользуемые.
- Синхронизирует
go.sum. - Обновляет
// indirectкомментарии.
Запускать перед коммитом.
5.10. Чем go get отличается от go install?
Заголовок раздела «5.10. Чем go get отличается от go install?»go get— модифицирует зависимости (go.mod/go.sum). С Go 1.17 — только для модулей.go install— устанавливает бинарь в$GOBIN/$GOPATH/bin. С Go 1.16 — требует@version.
5.11. Зачем GOPROXY?
Заголовок раздела «5.11. Зачем GOPROXY?»Прокси кэширует и архивирует модули. Дефолт — proxy.golang.org. Ускоряет сборки, защищает от исчезновения репо. Можно настроить свой прокси (Athens) для корпоративных нужд.
5.12. Что такое GOPRIVATE?
Заголовок раздела «5.12. Что такое GOPRIVATE?»Список glob-паттернов для приватных модулей. Эти модули:
- Не идут через прокси.
- Не проверяются через sum DB.
- Качаются напрямую из VCS.
5.13. Что такое workspaces (go.work)?
Заголовок раздела «5.13. Что такое workspaces (go.work)?»Файл для работы с несколькими модулями локально без replace. Перечисляет папки с модулями, которые надо подменить на локальные. Обычно не коммитится в репо.
5.14. Что произойдёт, если удалить go.sum?
Заголовок раздела «5.14. Что произойдёт, если удалить go.sum?»При следующей сборке Go снова скачает все зависимости и пересоздаст go.sum. Но это может протащить новые версии (если у кого-то поменялся go.mod в зависимости). Лучше всегда коммитить go.sum.
5.15. Может ли в одном проекте быть две версии одной библиотеки?
Заголовок раздела «5.15. Может ли в одном проекте быть две версии одной библиотеки?»Только если разные мажорные версии (через /v2). MVS гарантирует, что для одного module path выбирается ровно одна версия.
5.16. Что такое retract?
Заголовок раздела «5.16. Что такое retract?»Директива в go.mod, которой автор модуля помечает версии как “отозванные” (например, опубликованы по ошибке, содержат уязвимость). go get не подтянет такие версии по умолчанию.
5.17. Чем директива toolchain отличается от go?
Заголовок раздела «5.17. Чем директива toolchain отличается от go?»go 1.22— минимальная версия языка/runtime для сборки.toolchain go1.22.3— рекомендуемая конкретная toolchain. Go автоматически скачает её, если локальная старше.
5.18. Что покажет go mod why github.com/davecgh/go-spew?
Заголовок раздела «5.18. Что покажет go mod why github.com/davecgh/go-spew?»Цепочку импортов, через которую этот пакет попал в зависимости. Например:
github.com/example/app/pkggithub.com/stretchr/testify/assertgithub.com/davecgh/go-spew/spew5.19. Что такое +incompatible?
Заголовок раздела «5.19. Что такое +incompatible?»Маркер для версий v2+ модулей, у которых go.mod не содержит /v2 (legacy). Например: github.com/foo/bar v2.0.0+incompatible. Не лучшая практика — авторам стоит мигрировать на /v2.
5.20. Зачем vendor/ папка?
Заголовок раздела «5.20. Зачем vendor/ папка?»Локальная копия всех зависимостей. Используется для:
- Сборок без интернета.
- Аудита.
- Гарантии воспроизводимости даже при недоступности репо.
С Go 1.14+ vendor/ подхватывается автоматически.
6. Practice
Заголовок раздела «6. Practice»6.1. Создать модуль
Заголовок раздела «6.1. Создать модуль»mkdir myapp && cd myappgo mod init github.com/example/myappСоздай main.go:
package main
import ( "github.com/sirupsen/logrus")
func main() { logrus.Info("hello")}go mod tidygo run main.goПосмотри go.mod и go.sum. Запусти go mod graph.
6.2. MVS наглядно
Заголовок раздела «6.2. MVS наглядно»Создай 3 модуля:
A/ (требует B v1.0.0, C v1.0.0)B/ (требует C v1.1.0)C/ (тег v1.0.0 и v1.1.0)Используй replace (или workspaces) и проверь, что go list -m all в A показывает C v1.1.0 — победила максимальная требуемая.
6.3. replace для локальной разработки
Заголовок раздела «6.3. replace для локальной разработки»Без workspaces:
mkdir lib && cd libgo mod init github.com/example/lib# создай pkg lib
cd ../appgo mod init github.com/example/appВ app/go.mod:
replace github.com/example/lib => ../lib
require github.com/example/lib v0.0.06.4. Workspaces
Заголовок раздела «6.4. Workspaces»То же самое, но через go.work:
cd ../ # на уровень выше lib и appgo work init ./app ./libВ app/main.go импортируй github.com/example/lib. Не нужно replace.
6.5. Воспроизведи checksum mismatch
Заголовок раздела «6.5. Воспроизведи checksum mismatch»go mod tidy# отредактируй go.sum руками (испорти hash одной строки)go build # должна быть ошибка checksum mismatchgo mod tidy # восстанавливает6.6. Migration на v2
Заголовок раздела «6.6. Migration на v2»mkdir lib && cd libgo mod init github.com/example/lib# создай v1.0.0git tag v1.0.0# breaking change: создай v2# в go.mod: module github.com/example/lib/v2# git tag v2.0.0Проверь, что go get github.com/example/lib/v2@v2.0.0 работает.
6.7. Tools.go pattern
Заголовок раздела «6.7. Tools.go pattern»В проекте создай tools/tools.go:
//go:build tools
package tools
import ( _ "golang.org/x/tools/cmd/goimports")go mod tidygo install golang.org/x/tools/cmd/goimports6.8. Private repo
Заголовок раздела «6.8. Private repo»Сэмулируй приватный модуль через GitHub-private. Настрой:
export GOPRIVATE=github.com/myorg/*git config --global url.git@github.com:.insteadOf https://github.com/go get github.com/myorg/private-lib@latestПроверь, что прокси proxy.golang.org не запрашивался.
7. Источники
Заголовок раздела «7. Источники»-
Официальная документация Go modules — https://go.dev/ref/mod Главный референс. Полное описание формата
go.mod, MVS, командов. -
Russ Cox: Go & Versioning — https://research.swtch.com/vgo Серия статей от автора модулей. Глубокая теория MVS и semver.
-
Go Modules tutorial — https://go.dev/doc/tutorial/create-module Пошаговый туториал от Go team.
-
The Go Blog: Why Modules? — https://go.dev/blog/v2-go-modules Объяснение semantic import versioning.
-
Workspaces proposal — https://go.googlesource.com/proposal/+/master/design/45713-workspace.md Дизайн
go.workот Майкла Найта. -
GitHub: golang/go — https://github.com/golang/go/wiki/Modules Wiki с FAQ и edge cases (часть мигрировала в официальную доку).