Перейти к содержимому

17. Go Modules — управление зависимостями

Глубокий разбор системы модулей в Go: от go.mod до Minimal Version Selection (MVS), workspaces и приватных модулей. Зачем это нужно: модули — это контракт воспроизводимости сборки. Без понимания внутренностей легко получить “у меня собирается, у тебя нет” и потерять часы на отладку.

  1. Базовое API
  2. Под капотом
  3. Gotchas
  4. Best practices
  5. Вопросы на собесе
  6. Practice
  7. Источники

Go module — единица версионирования и распространения кода. Это коллекция Go-пакетов в дереве каталогов с файлом go.mod в корне.

  • Введены в Go 1.11 (август 2018) как экспериментальная фича.
  • С Go 1.16 — режим модулей включён по умолчанию (GO111MODULE=on).
  • В 2026 году (Go 1.22+) — GOPATH-режим фактически мёртв, все новые проекты используют модули.

Назначение модулей:

  • Воспроизводимые сборки (одинаковый код собирается одинаково на любой машине).
  • Версионирование зависимостей по семантике semver.
  • Возможность работать с кодом вне $GOPATH/src.

До модулей весь 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 существовал, и его модель полностью устарела.

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/myapp

go — минимальная версия Go, нужная для сборки модуля. В Go 1.21+ это уже строгое требование (раньше — только подсказка).

go 1.22

toolchain (Go 1.21+) — предложение использовать конкретную toolchain (минорную версию + патч). Команда go автоматически скачает нужную версию toolchain через прокси, если текущая старее.

toolchain go1.22.3

require — прямые зависимости. Можно одной директивой или блоком.

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.1

exclude — запрещает использование конкретной версии.

exclude github.com/buggy/dep v1.0.5

retract (Go 1.16+) — автор объявляет свои версии “отозванными”. go get не подтянет их по умолчанию.

retract (
v1.0.0 // Wrong API
[v1.2.0, v1.3.0] // Security issue
)

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 build Go сверяет загруженный код с хэшем в go.sum.
  • Это safety net, даже если кто-то перепишет тег в репо или взломает прокси.

go.sum обязательно коммитится в репозиторий.

go mod init <module-path> — создаёт go.mod.

Окно терминала
go mod init github.com/example/myapp

go mod tidy — синхронизирует go.mod/go.sum с реальным состоянием кода:

  • Добавляет недостающие зависимости.
  • Удаляет неиспользуемые.
  • Обновляет // indirect.

Запускать перед каждым коммитом.

Окно терминала
go mod tidy

С Go 1.17+ можно передать целевую версию Go:

Окно терминала
go mod tidy -go=1.22

go mod download — скачивает модули в локальный кэш ($GOMODCACHE, по умолчанию $GOPATH/pkg/mod).

Окно терминала
go mod download
go 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> — объясняет, зачем нужна зависимость (через какую цепочку импортов).

github.com/example/myapp/internal/app
go mod why github.com/davecgh/go-spew
# Вывод:
# github.com/stretchr/testify/assert
# github.com/davecgh/go-spew/spew

go 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.4

go get — добавляет/обновляет зависимости.

Окно терминала
# Latest stable
go get github.com/sirupsen/logrus
# Конкретная версия
go get github.com/sirupsen/logrus@v1.9.3
# Tag/branch
go 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+.

Go использует семантическое версионирование (semver): MAJOR.MINOR.PATCH.

  • MAJOR — несовместимые API-изменения.
  • MINOR — новый функционал, обратно совместимый.
  • PATCH — багфиксы, обратно совместимые.

Версии — это git-теги в формате vX.Y.Z:

  • v1.2.3
  • v0.0.1
  • 1.2.3 ✗ (без v)

Если в репо нет тегов или нужна конкретная коммитная версия, 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 коммита.

С версии v2.0.0 модуль обязан иметь /v2 в module path. Это правило Semantic Import Versioning.

// v2 module
module 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, и т.д.

Алгоритм выбора версий в Go — Minimal Version Selection (MVS). Это ключевое отличие от npm/Cargo, где используется SAT-solver.

Идея: для каждой зависимости берётся максимальная из минимально требуемых версий по всем go.mod в дереве зависимостей.

Модуль A требует:

  • B v1.2.0
  • C v1.5.0

Модуль B требует:

  • C v1.7.0

Какую версию C выберет MVS? v1.7.0 — потому что это максимум из всех минимально требуемых версий (1.5.0 от A и 1.7.0 от B).

  • Детерминизм: один и тот же go.mod всегда даёт одинаковый результат, независимо от истории.
  • Предсказуемость: не надо хранить lock-файл с конкретными версиями — версии в go.mod сами по себе lock.
  • Простота: алгоритм описывается в одну строчку (max(requirements)).
  • Безопасность: никогда не выбирается версия выше, чем явно потребовали разработчики.
МенеджерАлгоритм
npm, yarnSAT solver + lockfile (package-lock.json)
CargoSAT solver + Cargo.lock
GoMVS — детерминированный без отдельного lock
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), но обновлять все остальные не хочется.

Если зависимость B имеет replace, и наш проект импортирует B, директива replace в B игнорируется. Только корневой go.mod управляет replace.

Комментарий // indirect ставится в require у пакетов, которые:

  • Не импортируются нашим кодом напрямую.
  • Но нужны, потому что их используют наши зависимости.
require (
github.com/sirupsen/logrus v1.9.3
github.com/davecgh/go-spew v1.1.1 // indirect
)

go mod tidy сам ставит и убирает эти комментарии. Руками их трогать не надо.

Папка vendor/ — копия всех зависимостей в репо.

Окно терминала
go mod vendor

Когда нужна:

  • Сборки без интернета (CI, изолированные сети).
  • Аудит зависимостей.
  • Гарантия, что код собирается даже если зависимость удалена с GitHub.

Минусы:

  • Раздувает репо.
  • Дополнительный шаг при обновлении (go mod vendor после go mod tidy).

С Go 1.14+ vendor включается автоматически, если папка существует:

Окно терминала
go build # автоматически -mod=vendor
go build -mod=mod # явно отключить vendor

Модули из приватных репозиториев (GitHub Enterprise, GitLab, Bitbucket).

Окно терминала
export GOPRIVATE=github.com/mycompany/*,gitlab.com/myorg/*

Эта переменная — список glob-паттернов. Модули, попадающие под паттерн:

  • Не идут через proxy.golang.org.
  • Не проверяются через sum.golang.org.
  • Качаются напрямую из VCS.

Устаревшие — теперь GOPRIVATE решает всё.

Более тонко:

  • GONOPROXY — какие модули не через прокси.
  • GONOSUMDB — какие модули не через sum DB.

Для HTTPS:

~/.netrc
machine github.com login mytoken password x-oauth-basic

Для SSH (часто):

Окно терминала
git config --global url.git@github.com:.insteadOf https://github.com/

По умолчанию модули скачиваются через Google’s module proxy: https://proxy.golang.org.

Что делает прокси:

  • Кэширует модули (CDN).
  • Архивирует версии (даже если git-репо удалят).
  • Ускоряет сборки.

Переменные:

Окно терминала
GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org

Семантика GOPROXY:

  • Список URL через запятую.
  • direct — качать напрямую из VCS.
  • off — никаких загрузок (только из кэша).
  • При ошибке 404 или 410 — fallback к следующему.

Для приватных модулей часто свой прокси (Athens, Artifactory).

Когда работаешь над несколькими модулями одновременно, без replace помогают workspaces — файл go.work.

myproject/
go.work
app/
go.mod
main.go
lib/
go.mod
lib.go
Окно терминала
go 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 ./mod2
go work use ./mod3 # добавить
go work sync # синхронизировать с require'ами модулей
go work edit -replace ...
ПеременнаяЗначение
GOPROXYПрокси для модулей (по умолчанию https://proxy.golang.org,direct)
GOSUMDBSum database (по умолчанию sum.golang.org)
GOPRIVATEGlob приватных модулей
GONOPROXYМодули не через прокси
GONOSUMDBМодули не через sum DB
GOFLAGSГлобальные флаги для go команд (например, -mod=mod)
GOMODCACHEПапка локального кэша модулей (по умолчанию $GOPATH/pkg/mod)
GOTOOLCHAINУправление автоматическим переключением toolchain (auto/local/go1.22.0+auto)
Окно терминала
go env
go env GOPROXY GOPRIVATE

$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). Это намеренно: модули неизменны.

Псевдокод:

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.

При первой загрузке модуля:

  1. Скачивается .zip с прокси.
  2. Считается SHA-256 от ZIP-файла.
  3. Считается SHA-256 от go.mod.
  4. Хэши пишутся в go.sum в формате h1:hash.
  5. Если есть GOSUMDB, хэш сверяется с базой sum.golang.org.

При последующих сборках:

  • Если модуль в кэше — проверка по .ziphash.
  • Если нет — заново скачивается + проверка по go.sum.

Несовпадение хэшей → ошибка checksum mismatch → сборка падает.

С Go 1.21+ команда go понимает директиву toolchain и автоматически переключается между минорными версиями Go.

Алгоритм:

  1. Прочитать go и toolchain из go.mod.
  2. Если установленная версия >= toolchain → использовать установленную.
  3. Иначе — скачать toolchain через GOTOOLCHAIN механизм.
  4. Запустить нужную команду через скачанную toolchain.

Управление:

  • GOTOOLCHAIN=auto (default) — автоматический выбор.
  • GOTOOLCHAIN=local — использовать только локальную, ошибка если не подходит.
  • GOTOOLCHAIN=go1.22.3 — принудительно эту.
  • GOTOOLCHAIN=go1.22.3+auto — минимум эта, может быть выше.

До 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+ создаёт более полный список.

При import "github.com/example/lib/pkg":

  1. Найти модуль с самым длинным префиксом этого импорта в go.mod (github.com/example/lib).
  2. Найти в require версию.
  3. Применить replace, если есть.
  4. Загрузить модуль из кэша (или скачать).
  5. Импортировать пакет pkg из модуля.

Резолюция модуля по импорт-пути — попытки:

  • github.com/example/lib/pkg → модуль github.com/example/lib/pkg
  • github.com/example/lib → модуль github.com/example/lib, пакет pkg
  • github.com/example → модуль github.com/example, пакет lib/pkg

Берётся самый длинный совпавший модуль.

sum.golang.org — приложение, которое:

  • Хранит хэши всех публичных модулей.
  • Подписывает их Merkle tree (Trillian).
  • Гарантирует “no equivocation” — нельзя выдать разные хэши разным клиентам.

При go get:

  1. Скачали .zip и .mod.
  2. Запросили хэши у sum.golang.org.
  3. Сверили локально посчитанные хэши с базой.
  4. Если совпало — пишем в go.sum.

Это защищает от компрометации прокси или git-репо.

Внутренне 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)
}

go: github.com/foo/bar@v1.0.0: missing go.sum entry

Появляется, когда в go.mod есть зависимость, но в go.sum нет её хэша. Решение:

Окно терминала
go mod tidy
# или
go mod download

Чаще всего происходит, когда кто-то редактировал go.mod вручную или мержил конфликт.

Если убрать комментарий // indirect руками, go mod tidy его вернёт. Если удалить саму строку — а потом окажется, что она нужна — go mod tidy добавит обратно. Не трогай.

Самая частая ошибка авторов библиотек. Если тег 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.

import "github.com/foo/bar" // не в go.mod

Локально может работать (если модуль в кэше), а в CI — cannot find module providing package. Лекарство: всегда go mod tidy перед коммитом.

В CI можно проверить:

Окно терминала
go mod tidy
git 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.

// в зависимости B
replace github.com/foo/bar => ../bar

Эта replace не работает, когда твой проект импортирует B. Только корневой go.mod диктует replace.

Это намеренно — иначе зависимости могли бы внезапно подменить что угодно.

$GOPATH/pkg/mod/github.com/foo/bar@v1.0.0/

chmod 555. Попытка cd туда и vim file.gopermission denied. Это намеренно. Если надо отредактировать зависимость:

  • Сделать форк и replace.
  • Скопировать в vendor/.
  • Использовать go.work.
Окно терминала
go get -u ./...

Обновит все прямые зависимости до latest. Это часто ломает API. Лучше:

Окно терминала
go get -u=patch ./... # только patch-версии
# или адресно
go get github.com/foo/bar@latest

Semver гласит: v0.x.y — нестабильная. В Go это значит, что breaking changes возможны в любом minor-апгрейде v0.1v0.2. Большинство популярных библиотек живут в v0.x годами (например, golang.org/x/*).

github.com/foo/bar v2.0.0+incompatible

Это значит: репо имеет тег v2.0.0, но go.mod либо отсутствует, либо без /v2. Go разрешает использовать такие “несовместимые” версии для обратной совместимости со старым кодом. Но это анти-паттерн: автор должен починить go.mod.

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-abcdef123456

v1.2.1-0.<...> означает “после v1.2.0, до v1.2.1, конкретный коммит”.

go.work обычно не коммитят — это локальная настройка разработчика. Если случайно закоммитил — CI может использовать локальные пути и сломаться.

Добавь go.work в .gitignore, если работа над несколькими модулями локальная.

Если переименовать module github.com/foo/barmodule github.com/foo/baz, все, кто импортирует первый путь, сломаются. Обычно это делается через v2 (major bump).

exclude запрещает конкретную версию, но только в корневом модуле. На практике почти всегда лучше replace или просто более новая require.

Если в репо переписали тег (force-push), proxy.golang.org всё равно отдаст старую версию. Это безопасность. Решение: создать новую версию (никогда не переписывай теги в публичных репо!).

В старых версиях Go auto означал “включить модули, если есть go.mod”. С Go 1.16+ модули по умолчанию всегда включены, и GO111MODULE=auto — синоним on.

Окно терминала
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

Это устанавливает бинарь, не модифицируя go.mod/go.sum текущего проекта. Хорошо для тулинга. До Go 1.16 эта команда использовала go.mod проекта (и могла его сломать).

Окно терминала
go mod tidy -go=1.22

Без флага -go используется директива go из go.mod. Если поменял go 1.20go 1.22, лучше явно пересоздать tidy.


В hook’е pre-commit:

#!/bin/sh
go mod tidy
git diff --exit-code go.mod go.sum || (echo "go.mod/go.sum changed after tidy" && exit 1)

Не оставляй latest — всегда конкретная версия в go.mod (это и так делает go get, но иногда правят руками).

Указывай минимальную требуемую версию Go: go 1.22, если используешь фичи Go 1.22. Не пиши go 1.18, если фактически нужен for-range int.

replace — это код-смрад в чужих модулях. Если ты публикуешь библиотеку, твой go.mod не должен иметь replace (или это специально для тестов).

Если временно — отметь TODO.

go.sum всегда в репо. Без него go.mod не воспроизводимый.

Большинству проектов не нужен vendor/. Используй, если:

  • Сборка в изолированной сети.
  • Аудит/compliance требует копии.

module github.com/myorg/myapp совпадает с GitHub URL. Это не строгое правило, но если иначе — go get не работает. Кастомные домены требуют meta-tags (см. документацию).

4.8. go.work для локальной мульти-модули разработки

Заголовок раздела «4.8. go.work для локальной мульти-модули разработки»

Вместо replace для локальной работы — go.work. Не коммитится.

Если проект зависит от 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 — стоит использовать её.

Чтобы видеть, какие апдейты доступны:

Окно терминала
go list -m -u all

Покажет колонку с доступной более новой версией:

github.com/sirupsen/logrus v1.9.0 [v1.9.3]
require github.com/foo/bar master // ✗

Это превратится в pseudo-версию автоматически, но в go.mod лучше явная версия.

.github/workflows/ci.yml
- name: Verify modules
run: |
go mod tidy
git diff --exit-code go.mod go.sum
- name: Verify downloads
run: go mod download -x

Единица версионирования и распространения Go-кода. Дерево пакетов с go.mod в корне. Версионирование по semver.

  • В go.mod зависимости + их версии — это сразу lock-файл.
  • В npm package.json — только диапазоны, конкретные версии в package-lock.json.
  • В Go используется MVS, в npm — SAT solver.

Minimal Version Selection — алгоритм Go: выбирается максимум из минимально требуемых версий по всему графу зависимостей. Детерминирован, не нуждается в отдельном lock-файле.

  • require объявляет зависимость и версию.
  • replace подменяет источник или версию зависимости (только в корневом модуле).

Зависимость, которая нужна не нашему коду напрямую, а нашим зависимостям, но которой нет в go.mod зависимости (или версия в нашем go.mod выше). Управляется go mod tidy.

Содержит криптографические хэши зависимостей. Защищает от подмены кода: при сборке хэши пересчитываются и сверяются. Обязательно коммитится.

Автоматически генерируемая версия для конкретного коммита: v0.0.0-YYYYMMDDhhmmss-<12hex>. Используется, когда нет тега или нужна точная коммитная версия.

Правило Go: модуль с мажорной версией ≥ 2 должен иметь /v2, /v3 и т.д. в module path. Это позволяет иметь несколько мажорных версий в одном проекте как разные модули.

  • Добавляет недостающие импорты в go.mod.
  • Удаляет неиспользуемые.
  • Синхронизирует go.sum.
  • Обновляет // indirect комментарии.

Запускать перед коммитом.

  • go get — модифицирует зависимости (go.mod/go.sum). С Go 1.17 — только для модулей.
  • go install — устанавливает бинарь в $GOBIN/$GOPATH/bin. С Go 1.16 — требует @version.

Прокси кэширует и архивирует модули. Дефолт — proxy.golang.org. Ускоряет сборки, защищает от исчезновения репо. Можно настроить свой прокси (Athens) для корпоративных нужд.

Список glob-паттернов для приватных модулей. Эти модули:

  • Не идут через прокси.
  • Не проверяются через sum DB.
  • Качаются напрямую из VCS.

Файл для работы с несколькими модулями локально без replace. Перечисляет папки с модулями, которые надо подменить на локальные. Обычно не коммитится в репо.

При следующей сборке Go снова скачает все зависимости и пересоздаст go.sum. Но это может протащить новые версии (если у кого-то поменялся go.mod в зависимости). Лучше всегда коммитить go.sum.

5.15. Может ли в одном проекте быть две версии одной библиотеки?

Заголовок раздела «5.15. Может ли в одном проекте быть две версии одной библиотеки?»

Только если разные мажорные версии (через /v2). MVS гарантирует, что для одного module path выбирается ровно одна версия.

Директива в go.mod, которой автор модуля помечает версии как “отозванные” (например, опубликованы по ошибке, содержат уязвимость). go get не подтянет такие версии по умолчанию.

  • go 1.22 — минимальная версия языка/runtime для сборки.
  • toolchain go1.22.3 — рекомендуемая конкретная toolchain. Go автоматически скачает её, если локальная старше.

Цепочку импортов, через которую этот пакет попал в зависимости. Например:

github.com/davecgh/go-spew/spew
github.com/example/app/pkg
github.com/stretchr/testify/assert
github.com/davecgh/go-spew/spew

Маркер для версий v2+ модулей, у которых go.mod не содержит /v2 (legacy). Например: github.com/foo/bar v2.0.0+incompatible. Не лучшая практика — авторам стоит мигрировать на /v2.

Локальная копия всех зависимостей. Используется для:

  • Сборок без интернета.
  • Аудита.
  • Гарантии воспроизводимости даже при недоступности репо.

С Go 1.14+ vendor/ подхватывается автоматически.


Окно терминала
mkdir myapp && cd myapp
go mod init github.com/example/myapp

Создай main.go:

package main
import (
"github.com/sirupsen/logrus"
)
func main() {
logrus.Info("hello")
}
Окно терминала
go mod tidy
go run main.go

Посмотри go.mod и go.sum. Запусти go mod graph.

Создай 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 — победила максимальная требуемая.

Без workspaces:

Окно терминала
mkdir lib && cd lib
go mod init github.com/example/lib
# создай pkg lib
cd ../app
go mod init github.com/example/app

В app/go.mod:

replace github.com/example/lib => ../lib
require github.com/example/lib v0.0.0

То же самое, но через go.work:

Окно терминала
cd ../ # на уровень выше lib и app
go work init ./app ./lib

В app/main.go импортируй github.com/example/lib. Не нужно replace.

Окно терминала
go mod tidy
# отредактируй go.sum руками (испорти hash одной строки)
go build # должна быть ошибка checksum mismatch
go mod tidy # восстанавливает
Окно терминала
mkdir lib && cd lib
go mod init github.com/example/lib
# создай v1.0.0
git 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 работает.

В проекте создай tools/tools.go:

//go:build tools
package tools
import (
_ "golang.org/x/tools/cmd/goimports"
)
Окно терминала
go mod tidy
go install golang.org/x/tools/cmd/goimports

Сэмулируй приватный модуль через 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 не запрашивался.


  1. Официальная документация Go moduleshttps://go.dev/ref/mod Главный референс. Полное описание формата go.mod, MVS, командов.

  2. Russ Cox: Go & Versioninghttps://research.swtch.com/vgo Серия статей от автора модулей. Глубокая теория MVS и semver.

  3. Go Modules tutorialhttps://go.dev/doc/tutorial/create-module Пошаговый туториал от Go team.

  4. The Go Blog: Why Modules?https://go.dev/blog/v2-go-modules Объяснение semantic import versioning.

  5. Workspaces proposalhttps://go.googlesource.com/proposal/+/master/design/45713-workspace.md Дизайн go.work от Майкла Найта.

  6. GitHub: golang/gohttps://github.com/golang/go/wiki/Modules Wiki с FAQ и edge cases (часть мигрировала в официальную доку).