Модули
Каждый современный язык программирования имеет свой зрелый инструмент управления зависимостями, например Gradle для Java, Pip для Python, Npm для Node.js. Хороший инструмент управления зависимостями может сэкономить время разработчика и повысить эффективность разработки. Однако в ранних версиях Go не было зрелого решения для управления зависимостями. Весь код размещался в директории GOPATH, что было очень неудобно для проектов: хаос версий, сложности управления зависимостями. Для решения этой проблемы сообщество разработчиков предлагало различные решения, появлялись такие инструменты, как Vendor. Наконец, в версии Go 1.11 был официально представлен инструмент управления зависимостями Go Mod, который положил конец хаосу и продолжал совершенствоваться в последующих версиях, вытеснив старые инструменты. На момент написания этой статьи версия Go уже 1.20, и почти все проекты на Go используют Go Mod. Поэтому в этой статье мы рассмотрим только Go Mod. Официальная документация Go содержит очень подробное описание модулей: Справочник по модулям Go.
Написание модулей
Go Module основан на VCS (системе управления версиями). При загрузке зависимостей фактически выполняется команда VCS, например git. Поэтому, если вы хотите поделиться своей библиотекой, достаточно выполнить три условия:
- Репозиторий с исходным кодом должен быть общедоступен, и VCS должна быть одной из следующих:
- git
- hg (Mercurial)
- bzr (Bazaar)
- svn
- fossil
- Проект должен соответствовать спецификации go mod
- Должно соблюдаться семантическое версионирование
Вам нужно просто разрабатывать, используя VCS, создавать теги с версиями, соответствующими стандартам, и другие пользователи смогут загружать вашу библиотеку по имени модуля. Ниже мы рассмотрим несколько шагов разработки модулей на примере.
Пример репозитория: 246859/hello: say hello (github.com)
Подготовка
Перед началом убедитесь, что ваша версия Go полностью поддерживает go mod (Go >= 1.17) и что Go Module включён. Проверьте, включён ли он, с помощью команды:
$ go env GO111MODULEЕсли не включён, включите Go Module командой:
$ go env -w GO111MODULE=onСоздание
Сначала вам нужен общедоступный репозиторий с исходным кодом. Есть много вариантов, я рекомендую Github. Создайте новый проект с именем hello. Имя репозитория не имеет особых ограничений, но рекомендуется не использовать специальные символы, так как это может повлиять на имя модуля.

После создания URL репозитория будет https://github.com/246859/hello, соответствующее имя модуля Go — github.com/246859/hello.

Затем клонируйте его локально и инициализируйте модуль командой go mod init.
$ git clone git@github.com:246859/hello.git
Cloning into 'hello'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.
$ cd hello && go mod init github.com/246859/hello
go: creating new go.mod: module github.com/246859/helloНаписание
Теперь можно приступать к разработке. Функциональность очень проста — всего одна функция:
// hello.go
package hello
import "fmt"
// Hello возвращает приветственное сообщение
func Hello(name string) string {
if name == "" {
name = "world"
}
return fmt.Sprintf("hello %s!", name)
}Также напишем тестовый файл для модульного тестирования:
// hello_test.go
package hello_test
import (
"testing"
"fmt"
"github.com/246859/hello"
)
func TestHello(t *testing.T) {
data := "jack"
expected := fmt.Sprintf("hello %s!", data)
result := hello.Hello(data)
if result != expected {
t.Fatalf("ожидаемый результат %s, но получен %s", expected, result)
}
}Далее напишем консольную программу для вывода приветствия. Её функциональность также очень проста. Для консольных программ по规范 рекомендуется создавать их в директории проекта cmd/app_name/, поэтому файлы консольной программы hello будут находиться в директории cmd/hello/, где и будет написан соответствующий код.
// cmd/hello/main.go
package main
import (
"flag"
"github.com/246859/hello"
"os"
)
var name string
func init() {
flag.StringVar(&name, "name", "world", "имя для приветствия")
}
func main() {
flag.Parse()
msg := hello.Hello(name)
_, err := os.Stdout.WriteString(msg)
if err != nil {
os.Stderr.WriteString(err.Error())
}
}Тестирование
После написания отформатируйте исходный код и протестируйте:
$ go fmt && go vet ./...
$ go test -v .
=== RUN TestHello
--- PASS: TestHello (0.00s)
PASS
ok github.com/246859/hello 0.023sЗапуск консольной программы:
$ go run ./cmd/hello -name jack
hello jack!Документация
Наконец, нужно написать лаконичный и понятный README, чтобы другие разработчики сразу поняли, как использовать библиотеку:
# hello
просто говорит hello
## Установка
импорт кода
```bash
go get github.com/246859/hello@latest
```
установка cmd
```bash
go install github.com/246859/hello/cmd/hello@latest
```
## Пример
Вот простой пример:
```go
package main
import (
"fmt"
"github.com/246859/hello"
)
func main() {
result := hello.Hello("jack")
fmt.Println(result)
}
```Это очень простой README, вы можете его расширить.
Загрузка
Когда весь код написан и протестирован, можно закоммитить изменения и отправить их в удалённый репозиторий.
$ git add go.mod hello.go hello_test.go cmd/ example/ README.md
$ git commit -m "chore(mod): mod init" go.mod
[main 5087fa2] chore(mod): mod init
1 file changed, 3 insertions(+)
create mode 100644 go.mod
$ git commit -m "feat(hello): complete Hello func" hello.go
[main 099a8bf] feat(hello): complete Hello func
1 file changed, 11 insertions(+)
create mode 100644 hello.go
$ git commit -m "test(hello): complete hello testcase" hello_test.go
[main 76e8c1e] test(hello): complete hello testcase
1 file changed, 17 insertions(+)
create mode 100644 hello_test.go
$ git commit -m "feat(hello): complete hello cmd" cmd/hello/
[main a62a605] feat(hello): complete hello cmd
1 file changed, 22 insertions(+)
create mode 100644 cmd/hello/main.go
$ git commit -m "docs(example): add hello example" example/
[main 5c51ce4] docs(example): add hello example
1 file changed, 11 insertions(+)
create mode 100644 example/main.go
$ git commit -m "docs(README): update README" README.md
[main e6fbc62] docs(README): update README
1 file changed, 27 insertions(+), 1 deletion(-)Всего шесть коммитов — это немного. После завершения создайте тег для последнего коммита:
$ git tag v1.0.0
$ git tag -l
v1.0.0
$ git log --oneline
e6fbc62 (HEAD -> main, tag: v1.0.0, origin/main, origin/HEAD) docs(README): update README
5c51ce4 docs(example): add hello example
a62a605 feat(hello): complete hello cmd
76e8c1e test(hello): complete hello testcase
099a8bf feat(hello): complete Hello func
5087fa2 chore(mod): mod init
1f422d1 Initial commitЗатем отправьте тег в удалённый репозиторий:
$ git push --tags
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 16 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (21/21), 2.43 KiB | 1.22 MiB/s, done.
Total 21 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), done.
To github.com:246859/hello.git
1f422d1..e6fbc62 main -> main
* [new tag] v1.0.0 -> v1.0.0После отправки создайте релиз (достаточно тега, релиз просто соответствует спецификации github):

Таким образом, написание модуля завершено. Выше описан базовый процесс разработки модуля. Теперь другие разработчики могут использовать код или устанавливать консольные инструменты по имени модуля.
Использование
Используйте go get для подключения библиотеки:
$ go get github.com/246859/hello@latest
go: downloading github.com/246859/hello v1.0.0
go: added github.com/246859/hello v1.0.0Используйте go install для установки консольной программы:
$ go install github.com/246859/hello/cmd/hello@latest && hello -name jack
hello jack!Или используйте go run для непосредственного запуска:
$ go run -mod=mod github.com/246859/hello/cmd/hello -name jack
hello jack!После подключения библиотеки Go Package автоматически создаст для неё страницу. Этот процесс выполняется автоматически, разработчику не нужно ничего делать. Например, у библиотеки hello есть собственная страница документации, как показано ниже.

Более подробную информацию о загрузке модулей можно найти в разделе Add a package.
Информацию об удалении модулей можно найти в разделе Removing a package.
Настройка прокси
Хотя в Go нет центрального репозитория, подобного Maven Repo, PyPi, NPM, есть официальный прокси-репозиторий: Go modules services (golang.org). Он кэширует загруженные разработчиками модули по версии и имени модуля. Однако, поскольку серверы расположены за границей, скорость доступа для пользователей из Китая не очень хорошая. Поэтому нам нужно изменить адрес прокси-сервера модулей по умолчанию. В Китае есть несколько хороших вариантов:

Здесь мы выберем прокси от Qiniu. Выполните следующую команду для изменения прокси Go, где direct означает, что при неудаче загрузки через прокси следует обращаться напрямую к репозиторию исходного кода, минуя кэш прокси.
$ go env -w GOPROXY=https://goproxy.cn,directПосле успешного изменения прокси загрузка зависимостей станет очень быстрой.
Загрузка зависимостей
После изменения прокси попробуем установить стороннюю зависимость. У Go есть специальный сайт для поиска зависимостей: Go Packages.
Подключение кода
Найдём известный веб-фреймворк Gin.

Появится много результатов поиска. При использовании сторонних зависимостей нужно учитывать количество ссылок и время обновления, чтобы решить, использовать ли эту зависимость. Здесь мы выберем первую.

Перейдя на соответствующую страницу, можно увидеть, что это страница документации зависимости с очень подробной информацией. При дальнейшем чтении документации также можно обращаться сюда.

Здесь нужно просто скопировать адрес зависимости, затем в созданном ранее проекте использовать команду go get:
$ go get github.com/gin-gonic/ginВ процессе будет загружено много зависимостей. Если ошибок нет, значит загрузка успешна.
$ go get github.com/gin-gonic/gin
go: added github.com/bytedance/sonic v1.8.0
go: added github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
go: added github.com/gin-contrib/sse v0.1.0
go: added github.com/gin-gonic/gin v1.9.0
go: added github.com/go-playground/locales v0.14.1
go: added github.com/go-playground/universal-translator v0.18.1
go: added github.com/go-playground/validator/v10 v10.11.2
go: added github.com/goccy/go-json v0.10.0
go: added github.com/json-iterator/go v1.1.12
go: added github.com/klauspost/cpuid/v2 v2.0.9
go: added github.com/leodido/go-urn v1.2.1
go: added github.com/mattn/go-isatty v0.0.17
go: added github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go: added github.com/modern-go/reflect2 v1.0.2
go: added github.com/pelletier/go-toml/v2 v2.0.6
go: added github.com/twitchyliquid64/golang-asm v0.15.1
go: added github.com/ugorji/go/codec v1.2.9
go: added golang.org/x/arch v0.0.0-20210923205945-b76863e36670
go: added golang.org/x/crypto v0.5.0
go: added golang.org/x/net v0.7.0
go: added golang.org/x/sys v0.5.0
go: added golang.org/x/text v0.7.0
go: added google.golang.org/protobuf v1.28.1
go: added gopkg.in/yaml.v3 v3.0.1После завершения проверьте файл go.mod:
$ cat go.mod
module golearn
go 1.20
require github.com/gin-gonic/gin v1.9.0
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)Можно увидеть, что по сравнению с предыдущим состоянием появилось много нового. Также в директории появился файл с именем go.sum:
$ ls
go.mod go.sum main.goПока оставим это, изменим файл main.go:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
gin.Default().Run()
}Запустим проект снова:
$ go run golearn
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080Таким образом, одной строкой кода мы запустили простейший веб-сервер. Когда зависимость больше не нужна, её можно удалить с помощью команды go get. В качестве примера удалим Gin:
$ go get github.com/gin-gonic/gin@none
go: removed github.com/gin-gonic/gin v1.9.0Добавив @none после адреса зависимости, можно удалить её. Результат также подтверждает успешное удаление. При повторной проверке файла go.mod можно увидеть, что зависимость Gin исчезла:
$ cat go.mod | grep github.com/gin-gonic/ginДля обновления до последней версии можно добавить суффикс @latest или самостоятельно查询 доступные версии Release:
$ go get -u github.com/gin-gonic/gin@latestУстановка консольных программ
Команда go install загружает сторонние зависимости локально и компилирует их в бинарные файлы. Благодаря скорости компиляции Go этот процесс обычно не занимает много времени. Затем Go помещает их в директорию $GOPATH/bin или $GOBIN, чтобы можно было выполнять эти бинарные файлы глобально (при условии, что эти пути добавлены в переменные окружения).
TIP
При использовании команды install необходимо указывать версию.
Например, загрузим отладчик delve, написанный на Go:
$ go install github.com/go-delve/delve/cmd/dlv@latest
go: downloading github.com/go-delve/delve v1.22.1
go: downloading github.com/cosiner/argv v0.1.0
go: downloading github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d
go: downloading github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62
go: downloading github.com/google/go-dap v0.11.0
go: downloading github.com/hashicorp/golang-lru v1.0.2
go: downloading golang.org/x/arch v0.6.0
go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.2
go: downloading go.starlark.net v0.0.0-20231101134539-556fd59b42f6
go: downloading github.com/cilium/ebpf v0.11.0
go: downloading github.com/mattn/go-runewidth v0.0.13
go: downloading github.com/russross/blackfriday/v2 v2.1.0
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
$ dlv -v
Error: unknown shorthand flag: 'v' in -v
Usage:
dlv [command]
Available Commands:
attach Attach to running process and begin debugging.
completion Generate the autocompletion script for the specified shell
connect Connect to a headless debug server with a terminal client.
core Examine a core dump.
dap Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).
debug Compile and begin debugging main package in current directory, or the package specified.
exec Execute a precompiled binary, and begin a debug session.
help Help about any command
test Compile test binary and begin debugging program.
trace Compile and begin tracing program.
version Prints version.
Additional help topics:
dlv backend Help about the --backend flag.
dlv log Help about logging flags.
dlv redirect Help about file redirection.
Use "dlv [command] --help" for more information about a command.Управление модулями
Всё вышеизложенное описывает только базовое использование Go Mod, но для полноценного изучения Go Mod этого совершенно недостаточно. Официальное определение модуля: набор пакетов с версиями. В приведённом определении пакеты — это уже знакомое понятие, а версии должны следовать семантическому версионированию и определяться в формате v(major).(minor).(patch), например, версия Go v1.20.1, где основная версия — 1, минорная версия — 20, патч-версия — 1, вместе получается v1.20.1. Ниже приведено более подробное объяснение:
major: при изменении основной версии проект претерпел несовместимые изменения. При обновлении старого проекта до новой версии он, скорее всего, не будет работать корректно.minor: при изменении минорной версии проект получил новые функции, но основа предыдущей версии просто расширена новыми функциями.patch: при изменении патч-версии были исправлены ошибки, без добавления новых функций.
Часто используемые команды
| Команда | Описание |
|---|---|
go mod download | Загрузка зависимостей текущего проекта |
go mod edit | Редактирование файла go.mod |
go mod graph | Вывод графа зависимостей модулей |
go mod init | Инициализация go mod в текущей директории |
go mod tidy | Очистка модулей проекта |
go mod verify | Проверка легальности зависимостей проекта |
go mod why | Объяснение, где в проекте используются зависимости |
go clean -modcache | Удаление кэша зависимостей модулей проекта |
go list -m | Список модулей |
Перейдите в раздел go mod cmd, чтобы узнать больше о командах.
Хранение модулей
При использовании Go Mod для управления проектом кэш модулей по умолчанию хранится в директории $GOPATH/pkg/mod. Также можно изменить $GOMODCACHE, чтобы указать другое место хранения.
$ go env -w GOMODCACHE=путь_к_кэшу_модулейВсе проекты Go Module на одной машине共享 кэш в этой директории. Кэш не имеет ограничений по размеру и не удаляется автоматически. Исходные файлы зависимостей в кэше доступны только для чтения. Для очистки кэша выполните команду:
$ go clean -modcacheВ директории $GOMODCACHE/cache/download хранятся оригинальные файлы зависимостей, включая файлы хэшей, исходные архивы и т.д. Например:
$ ls $(go env GOMODCACHE)/cache/download/github.com/246859/hello/@v -1
list
v1.0.0.info
v1.0.0.lock
v1.0.0.mod
v1.0.0.zip
v1.0.0.ziphashСтруктура распакованных зависимостей выглядит следующим образом — это исходный код указанного модуля:
$ ls $(go env GOMODCACHE)/github.com/246859/hello@v1.0.0 -1
LICENSE
README.md
cmd/
example/
go.mod
hello.go
hello_test.goВыбор версии
Go при выборе версий зависимостей следует принципу выбора минимальной версии. Вот пример с официального сайта: главный модуль ссылается на версию 1.2 модуля A и версию 1.2 модуля B. Одновременно версия 1.2 модуля A ссылается на версию 1.3 модуля C, а версия 1.2 модуля B ссылается на версию 1.4 модуля C. При этом версии 1.3 и 1.4 модуля C одновременно ссылаются на версию 1.2 модуля D. Согласно принципу минимальной доступной версии, Go в конечном итоге выберет версии A1.2, B1.2, C1.4 и D1.2. Светло-синим цветом показаны загруженные файлы go.mod, в рамке — выбранные версии.
На официальном сайте также приведены другие примеры, смысл примерно тот же.
go.mod
При создании каждого проекта Go Mod генерируется файл go.mod, поэтому необходимо ознакомиться с ним. Однако в большинстве случаев нет необходимости вручную изменять файл go.mod.
module golearn
go 1.20
require github.com/gin-gonic/gin v1.9.0
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)В файле можно заметить, что большинство адресов зависимостей содержат слова вроде github. Это связано с тем, что в Go нет общего репозитория зависимостей, и большинство проектов с открытым исходным кодом размещаются на Github. Есть также те, кто самостоятельно строит репозитории, например google.golang.org/protobuf, golang.org/x/crypto. Обычно такая строка URL одновременно является именем модуля проекта Go, что создаёт проблему: URL нечувствителен к регистру, но директории хранения зависимостей чувствительны. Поэтому go get github.com/gin-gonic/gin и go get github.com/gin-gonic/Gin ссылаются на одну и ту же зависимость, но локальные пути хранения различаются. В этом случае Go не использует заглавные буквы напрямую как путь хранения, а преобразует их в !строчные буквы. Например, github.com/BurntSushi в конечном итоге преобразуется в github.com/!burnt!sushi.
module
Ключевое слово module объявляет имя модуля текущего проекта. В одном файле go.mod может быть только одно ключевое слово module. В примере:
module golearnозначает, что имя текущего модуля — golearn. Например, открыв файл go.mod зависимости Gin, можно увидеть её имя module:
module github.com/gin-gonic/ginИмя модуля Gin — это адрес, используемый при загрузке зависимости. Это также рекомендуемый формат имени модуля: домен/пользователь/имя_репозитория.
TIP
Следует отметить, что при основной версии больше 1 номер основной версии должен быть отражён в имени модуля. Например:
github.com/my/exampleЕсли версия обновлена до v2.0.0, то имя модуля нужно изменить на:
github.com/my/example/v2Если старый проект ссылается на старую версию и новая версия не различается, то при подключении зависимости из-за одинаковости путей пользователь не сможет различить несовместимые изменения, вызванные изменением основной версии, что может привести к ошибкам в программе.
Deprecation
Комментарий Deprecated в начале строки перед module указывает, что модуль устарел. Например:
// Deprecated: use example.com/mod/v2 instead.
module example.com/modgo
Ключевое слово go указывает версию Go, используемую для написания текущего проекта. Номер версии должен следовать правилам семантического версионирования. В зависимости от версии Go модуль Go может вести себя по-разному. Ниже приведён простой пример. Доступные версии Go можно найти в официальной документации.
go 1.20require
Ключевое слово require указывает на внешнюю зависимость. Например:
require github.com/gin-gonic/gin v1.9.0Формат: require имя_модуля версия. При нескольких зависимостях можно использовать скобки:
require (
github.com/bytedance/sonic v1.8.0 // indirect
)Зависимости с комментарием // indirect означают, что они не используются напрямую в текущем проекте. Возможно, зависимости, которые используются напрямую, ссылаются на эти зависимости, поэтому для текущего проекта они являются косвенными. Ранее упоминалось, что при изменении основной версии это должно быть отражено в имени модуля. Модули, не следующие этому правилу, называются некорректными модулями. При require к ним добавляется комментарий incompatible:
require example.com/m v4.1.2+incompatibleПсевдо-версии
В приведённом выше файле go.mod можно заметить, что версии некоторых зависимостей не являются семантическими номерами версий, а представляют собой непонятные строки. На самом деле это CommitID соответствующей версии. Семантические версии обычно относятся к определённому Release. Псевдо-версии позволяют указать конкретный Commit. Обычный формат: vx.y.z-yyyyMMddHHmmss-CommitId. Поскольку vx.y.z не обязательно существует, они называются псевдо-версиями. Например, v0.0.0 в следующем примере не существует, действителен последующий 12-значный CommitID:
// CommitID обычно берёт первые 12 символов
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirectАналогично, при загрузке зависимости можно указать CommitID вместо семантического номера версии:
go get github.com/chenzhuoyu/base64x@fe3a3abad311exclude
Ключевое слово exclude указывает, что определённая версия зависимости не должна загружаться. Если одновременно require ссылается на ту же версию, она будет проигнорирована. Это ключевое слово действует только в главном модуле. Например:
exclude golang.org/x/net v1.2.3
exclude (
golang.org/x/crypto v1.4.5
golang.org/x/text v1.6.7
)replace
replace заменяет указанную версию зависимости. Можно использовать путь модуля и версию для замены или указать локальный путь к файлу. Пример:
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
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
golang.org/x/net v1.2.3 => ./fork/net
golang.org/x/net => ./fork/net
)Заменяется только версия слева от =>, другие версии той же зависимости остаются доступными. При использовании локального пути или пути модуля для замены, если заменяемый модуль имеет файл go.mod, его директива module должна соответствовать пути заменяемого модуля.
retract
Директива retract указывает, что не следует использовать указанную версию или диапазон версий зависимости. Например, если после выпуска новой версии обнаружена серьёзная проблема, можно использовать директиву retract.
Отзыв некоторых версий:
retract (
v1.0.0 // Опубликован случайно.
v1.0.1 // Содержит только отзывы.
)Отзыв диапазона версий:
retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
v1.0.0
[v1.0.0, v1.9.9]
)go.sum
Файл go.sum не существует при создании проекта. Он генерируется только после подключения внешних зависимостей. Файл go.sum не предназначен для чтения человеком и не рекомендуется изменять его вручную. Его основная задача — решение проблемы согласованной сборки: разные люди в разных средах при использовании одного и того же проекта должны использовать абсолютно одинаковые пакеты зависимостей. Одного файла go.mod для этого недостаточно.
Рассмотрим, что делает Go при загрузке зависимости. Сначала используем команду для загрузки зависимости:
go get github.com/bytedance/sonic v1.8.0Команда go get сначала загружает пакет зависимости в локальную директорию кэша. Обычно это директория $GOMODCACHE/cache/download/. Эта директория разделена по доменам для хранения зависимостей с разных сайтов, поэтому вы можете увидеть следующую структуру директорий:
$ ls
cloud.google.com/ go.opencensus.io/ gopkg.in/ nhooyr.io/
dmitri.shuralyov.com/ go.opentelemetry.io/ gorm.io/ rsc.io/
github.com/ go.uber.org/ honnef.co/ sumdb/
go.etcd.io/ golang.org/ lukechampine.com/
go.mongodb.org/ google.golang.org/ modernc.org/Путь к загруженной зависимости в приведённом примере:
$GOMODCACHE/cache/download/github.com/bytedance/sonic/@v/Возможная структура директорий:
$ ls
list v1.8.0.lock v1.8.0.ziphash v1.8.3.mod
v1.5.0.mod v1.8.0.mod v1.8.3.info v1.8.3.zip
v1.8.0.info v1.8.0.zip v1.8.3.lock v1.8.3.ziphashОбычно в этой директории всегда есть файл list, содержащий известные номера версий зависимости. Для каждой версии существуют следующие файлы:
zip: архив с исходным кодом зависимостиziphash: хэш, вычисленный по архиву зависимостиinfo: метаданные версии в формате JSONmod: файлgo.modэтой версииlock: временный файл (официально не указано назначение)
Обычно Go вычисляет хэши двух файлов: архива и go.mod, затем查询 сервер, указанный в GOSUMDB (по умолчанию sum.golang.org), для получения хэша зависимости. Если вычисленный локально хэш не совпадает с полученным, выполнение прекращается. Если хэши совпадают, обновляется файл go.mod и в файл go.sum вставляются две записи примерно следующего вида:
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=TIP
Если GOSUMDB отключён, Go напрямую записывает вычисленный локально хэш в файл go.sum. Обычно это не рекомендуется делать.
Обычно для каждой зависимости есть две записи: первая — хэш архива, вторая — хэш файла go.mod зависимости. Формат записи: имя_модуля версия алгоритм:хэш. Некоторые старые зависимости могут не иметь файла go.mod, поэтому у них не будет второй хэш-записи. Когда проект собирается в другой среде, Go вычисляет хэш локальной зависимости, указанной в go.mod, и сравнивает его с хэш-записью в go.sum. Если хэши не совпадают, это означает, что версии зависимостей различаются, и сборка отклоняется. В этом случае локальная зависимость или файл go.sum могли быть изменены, но поскольку go.sum проверен через GOSUMDB, предпочтение отдаётся файлу go.sum.
Приватные модули
Большинство инструментов Go Mod предназначены для проектов с открытым исходным кодом, но Go также поддерживает приватные модули. Для приватных проектов обычно необходимо настроить следующие переменные окружения для обработки приватности модулей:
GOPROXY: список прокси-серверов зависимостейGOPRIVATE: список общих префиксов путей модулей для приватных модулей. Если имя модуля соответствует правилу, он считается приватным. Поведение аналогично GONOPROXY и GONOSUMDB.GONOPROXY: список префиксов путей модулей, которые не загружаются через прокси. При соответствии правилу загрузка модулей обходит GOPROXY и пытается загрузить напрямую из системы управления версиями.GONOSUMDB: список префиксов путей модулей, которые не проходят публичную проверку GOSUMDB. При соответствии правилу проверка контрольной суммы не обращается к публичной базе данных.GOINSECURE: список префиксов путей модулей, которые можно получать через HTTP и другие небезопасные протоколы.
Рабочие области
Ранее упоминалось, что файл go.mod поддерживает директиву replace, что позволяет временно использовать локальные изменения, которые ещё не опубликованы:
replace (
github.com/246859/hello v1.0.1 => ./hello
)При компиляции Go будет использовать локальный модуль hello. После публикации новой версии в будущем его можно удалить.
Однако использование директивы replace изменяет содержимое файла go.mod, и это изменение может быть ошибочно отправлено в удалённый репозиторий. Это нежелательно, поскольку target, указанный в директиве replace, является локальным путём к файлу, а не сетевым URL. Путь, работающий на одной машине, может не работать на другой. Пути к файлам также представляют большую проблему для кроссплатформенности. Для решения подобных проблем были введены рабочие области.
Рабочая область (workspace) — это новое решение для управления несколькими модулями, введённое в Go 1.18. Оно предназначено для лучшей локальной разработки с несколькими модулями. Ниже мы рассмотрим пример.
Пример репозитория: 246859/work: go work example (github.com)
Пример
В проекте есть два независимых модуля Go: auth и user:
$ ls -1
LICENSE
README.md
auth
go.work
userМодуль auth зависит от структуры User из модуля user. Содержимое:
package auth
import (
"errors"
"github.com/246859/work/user"
)
// Verify проверяет учётные данные пользователя
func Verify(user user.User) (bool, error) {
password, err := query(user.Name)
if err != nil {
return false, err
}
if password != user.Password {
return false, errors.New("authentication failed")
}
return true, nil
}
func query(username string) (string, error) {
if username == "jack" {
return "jack123456", nil
}
return "", errors.New("user not found")
}Содержимое модуля user:
package user
type User struct {
Name string
Password string
Age int
}В этом проекте мы можем написать файл go.work следующим образом:
go 1.22
use (
./auth
./user
)Содержимое очень легко понять: директива use указывает, какие модули участвуют в компиляции. Далее запустим код в модуле auth:
// auth/example/main.go
package main
import (
"fmt"
"github.com/246859/work/auth"
"github.com/246859/work/user"
)
func main() {
ok, err := auth.Verify(user.User{Name: "jack", Password: "jack123456"})
if err != nil {
panic(err)
}
fmt.Printf("%v", ok)
}Выполним команду, из результата видно, что модуль успешно импортирован:
$ go run ./auth/example
trueВ предыдущих версиях для двух независимых модулей, если модуль auth хотел использовать код из модуля user, было только два способа:
- Закоммитить изменения модуля user и отправить в удалённый репозиторий, выпустить новую версию, затем изменить файл
go.modна указанную версию. - Изменить файл
go.mod, перенаправив зависимость на локальный файл.
Оба метода требуют изменения файла go.mod. Рабочие области существуют для того, чтобы можно было импортировать другие модули без изменения go.mod. Однако следует понимать, что файл go.work используется только в процессе разработки. Его существование只是为了更方便地进行本地开发,而不是进行依赖管理。它只是暂时让你略过了提交到发版的这一过程,可以让你马上使用 user 模块的新修改而无需进行等待。当 user 模块测试完毕后,最后依旧需要发布新版本,并且 auth 模块最后仍然要修改 go.mod 文件引用最新版本(这一过程可以用 go work sync 命令来完成)。因此在正常的 go 开发过程中,go.work 也不应该提交到 VCS 中(示例仓库中的 go.work 仅用于演示),因为其内容都是依赖于本地的文件,且其功能也仅限于本地开发。
Команды
Ниже приведены некоторые команды рабочей области:
| Команда | Описание |
|---|---|
| edit | Редактирование go.work |
| init | Инициализация новой рабочей области |
| sync | Синхронизация зависимостей модулей рабочей области |
| use | Добавление нового модуля в go.work |
| vendor | Копирование зависимостей в формате vendor |
Перейдите в раздел go work cmd, чтобы узнать больше о командах.
Директивы
Содержимое файла go.work очень простое, всего три директивы:
go— указывает версию Gouse— указывает используемые модулиreplace— указывает заменяемые модули
За исключением директивы use, остальные две практически идентичны директивам в go.mod, за исключением того, что директива replace в go.work действует на все модули. Полный go.work выглядит следующим образом:
go 1.22
use(
./auth
./user
)
repalce github.com/246859/hello v1.0.0 => /home/jack/code/hello