- Как работает загрузка через прокси-сервер
- Как найти запрашиваемый пакет в прокси
- Как найти последнюю версию пакета
- Как публиковать модуль
- Как добавлять обратно несовместимые изменения
- Выводы
В Go используется нестандартная система публикации модулей. В отличие от большинства других популярных языков, в Go не нужно самостоятельно загружать код в централизованные репозитории. Каждый Go-модуль публикуется только в репозиториях исходного кода, например, на GitHub или GitLab.
В этом уроке мы рассмотрим, как работает публикация модулей в Go.
Как работает загрузка через прокси-сервер
Команда go
не загружает пакеты напрямую из хранилищ исходного кода, а опрашивает прокси-сервер.
Этот сервер хранит все публичные Go-модули во всех версиях. Если модуль или запрошенная версия отсутствуют на сервере, то прокси попытается сохранить их у себя, прежде чем отдать пользователю. Такая система защищает пользователей от потери доступа к пакету, если автор изменит нужный тег, перезапишет его или удалит пакет целиком.
Еще пользователя оберегает база данных контрольных сумм, которая хранит записи из файлов go.sum. Представьте, что вы загружаете модуль командой go
. В этот момент команда считывает хэш загружаемого модуля и сверяет его с данными из базы контрольных сумм. Если они не совпадают, то установка завершается с ошибкой.
Как найти запрашиваемый пакет в прокси
Проследим установку пакета на примере популярной библиотеки lo. Возьмем базовый пример:
package hxlt
import (
"github.com/samber/lo"
)
func isEven(x int, _ int) bool {
return x%2 == 0
}
func Even(vals []int) []int {
return lo.Filter[int](vals, isEven)
}
Вызов команды go get github.com/samber/lo@latest
проверит переменную
окружения GOPROXY
. Допустим, она содержит список прокси-серверов:
https://corp.example.com,https://proxy.golang.org
Тогда команда go
выполнит следующие шаги:
- Запросит последнюю версию
github.com/samber/lo
от сервераhttps://corp.example.com/
- Запросит последнюю версию
github.com/samber
от сервераhttps://corp.example.com/
- Запросит последнюю версию
github.com
от сервераhttps://corp.example.com/
Представим, что все запросы к https://corp.example.com/ выдали ошибку 404 или 410. Тогда команда перейдет к следующей записи:
- Запросит последнюю версию
github.com/samber/lo
от сервераhttps://proxy.golang.org/
- Запросит последнюю версию
github.com/samber
от сервераhttps://proxy.golang.org/
- Запросит последнюю версию
github.com
от сервераhttps://proxy.golang.org/
Если в результате найдется один модуль с именем github.com/samber/lo
и пакетом lo
, то команда go
обновит файлы go.mod и go.sum и
скачает сам пакет.
Как найти последнюю версию пакета
Продолжим пример с запросом go get github.com/samber/lo@latest
. Здесь можно использовать атрибут @latest
, и тогда go определит последнюю версию запрашиваемого пакета.
Рассмотрим, как это работает. Допустим в системе контроля версий проекта есть несколько веток и тегов:
- Ветка main
- Ветка v2
- Тег v1.0.0
- Тег 1101-1663105068
В этом случае последней версией будет считаться тег v1.0.0, потому что Go отдает приоритет тегам перед ветками. Обратите внимание, что используются только те теги, которые начинаются с буквы v
и следуют семантическому версионированию.
Если в проекте нет тегов, команда создает псевдо-версию, используя последний коммит.
Как публиковать модуль
Попробуем опубликовать собственный модуль. Этот процесс состоит из нескольких шагов.
Создаем go-модуль:
go mod init github.com/hexlet-components/hxlt
go: creating new go.mod: module github.com/hexlet-components/hxlt
Добавляем код в модуль, созданный выше:
package hxlt
import (
"github.com/samber/lo"
)
func isEven(x int, _ int) bool {
return x%2 == 0
}
func Even(vals []int) []int {
return lo.Filter[int](vals, isEven)
}
Запускаем go mod tidy
, чтобы загрузить недостающие пакеты и удалить лишние:
go mod tidy
go: finding module for package github.com/samber/lo
go: found github.com/samber/lo in github.com/samber/lo v1.28.0
Дальше нужно отформатировать код с помощью встроенного инструмента go fmt
. Не забывайте запускать форматирование перед публикацией пакета — так вы убедитесь, что вывод команды не содержит ошибок.
После этого проверяем код линтером. Для Go написано множество линтеров, поэтому появились утилиты-аггрегаторы, запускающие несколько линтеров разом. Мы рекомендуем пользоваться golangci-lint.
Остался последний шаг — добавляем новый тег в репозиторий:
git commit -am "feat: changes for v0.0.1"
git tag v0.0.1
git push origin v0.0.1
Модуль готов к скачиванию! Чтобы пользователи смогли найти его до загрузки, можно запустить команду go list
— она добавит модуль в
индекс go.pkg.
Как добавлять обратно несовместимые изменения
В работе над проектом разработчики могут переименовывать функции, изменять ее сигнатуры, удалять устаревший код. Подобные действия могут вносить ломающие изменения в API. Это значит, что пользователям придется вносить изменения в собственную кодовую базу.
Go-разработчики серьезно относятся к подобным изменениям в API. Если вам нужно внести ломающие изменения, не получится ограничиться добавлением тега с увеличенной мажорной версией. Рассмотрим подробнее, как устроено внесение таких изменений.
Возьмем модуль из примера выше. Его структура выглядит так:
.
├── go.mod
├── go.sum
└── hxlt.go
При этом файл go.mod выглядит так:
module github.com/hexlet-components/hxlt
go 1.19
require github.com/samber/lo v1.28.0
require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
Чтобы создать версию 2.0.0 этого модуля, нужно обновить go.mod, чтобы он
содержал в названии новую мажорную версию пакета. Сохраним текущий код в
ветке v1
:
git branch v1
Обновим go.mod файл, добавив ему в название v2
:
go mod edit -module github.com/hexlet-components/hxlt/v2
Если бы в проекте было несколько пакетов, то для папки с v2
пришлось бы обновить все импорты, чтобы они включали суффикс v2
. Добавим тег и опубликуем:
git tag v2.0.0
git push origin v2.0.0
Выводы
- Чтобы опубликовать Go-модуль, нужно внести его в хранилище исходного кода: то есть добавить сам код, файлы go.mod и go.sum в систему контроля версий
- Имя модуля при этом должно содержать путь до модуля в этом хранилище
- Модули по умолчанию загружаются не из хранилищ кода, а из центрального репозитория пакетов
- Чтобы точнее указать требования к версии пакета, можно использовать
version query. Отсутствующий
version query
имеет дефолтное значениеlatest
- Перед публикацией не забывайте форматировать код и проверять его линтером
- В работе над публичными проектами на Go добавляйте новые версии, а не изменяйте уже существующие
Самостоятельная работа
-
Опубликуйте ваш модуль на Github, указав версию v1.0.0 (команду
go list
выполнять не нужно) -
Внесите изменения в ваш код, например так, чтобы программа выводила
"Hexlet for Brave!"
-
Обновите версию модуля в go.mod. Опубликуйте новую версию на Github, указав новый тег
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.