The Go Blog

Публикация Go модулей

Tyler Bui-Palsulich
26 сентября 2019

Введение

Эта статья является частью серии.

Примечание: Для документации по разработке модулей, смотрите Разработка и публикация модулей.

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

Обратите внимание: данная статья охватывает разработку до и включая v1. Если вас интересует v2, пожалуйста, ознакомьтесь с Go Modules: v2 and Beyond.

В этой статье используются примеры с Git. Также поддерживаются Mercurial, Bazaar и другие системы управления версиями.

Настройка проекта

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

<code>$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote/v3 v3.1.0
$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
$ cat hello.go
package hello
import "rsc.io/quote/v3"
func Hello() string {
  return quote.HelloV3()
}
func Proverb() string {
  return quote.Concurrency()
}
$ cat hello_test.go
package hello
import (
  "testing"
)
func TestHello(t *testing.T) {
  want := "Hello, world."
  if got := Hello(); got != want {
    t.Errorf("Hello() = %q, want %q", got, want)
  }
}
func TestProverb(t *testing.T) {
  want := "Concurrency is not parallelism."
  if got := Proverb(); got != want {
    t.Errorf("Proverb() = %q, want %q", got, want)
  }
}
$
</code>

Далее создайте новый git репозиторий и добавьте начальный коммит. Если вы публикуете свой собственный проект, обязательно включите файл LICENSE. Перейдите в каталог, содержащий go.mod, а затем создайте репозиторий:

<code>$ git init
$ git add LICENSE go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: initial commit"
$
</code>

Семантические версии и модули

Каждый требуемый модуль в go.mod имеет семантическую версию, минимальную версию зависимости, которую следует использовать для сборки модуля.

Семантическая версия имеет вид vMAJOR.MINOR.PATCH.

Вы можете указывать предрелизные версии, добавляя дефис и точкой разделённые идентификаторы (например, v1.0.1-alpha или v2.2.2-beta.2). Обычные релизы предпочтительнее команды go по сравнению с предрелизными версиями, поэтому пользователям необходимо явно запрашивать предрелизные версии (например, go get example.com/hello@v1.0.1-alpha), если ваш модуль имеет какие-либо обычные релизы.

Версии с v0 и предрелизные версии не гарантируют обратную совместимость. Они позволяют вам уточнить ваш API перед тем, как делать обязательства по стабильности для пользователей. Однако версии с v1 и выше требуют обратной совместимости в пределах одной основной версии.

Версия, указанная в go.mod, может быть явно тегированной версией в репозитории (например, v1.5.2), или же это может быть псевдо-версия, основанная на конкретном коммите (например, v0.0.0-20170915032832-14c0d48ead0c). Псевдо-версии — это специальный тип предрелизной версии. Псевдо-версии полезны, когда пользователю нужно зависеть от проекта, который не опубликовал никаких тегов семантических версий, или когда разрабатывается против коммита, который ещё не был помечен тегом. Однако пользователи не должны полагаться на то, что псевдо-версии обеспечивают стабильный или хорошо протестированный API. Тегирование ваших модулей явными версиями сигнализирует пользователям о том, что конкретные версии полностью протестированы и готовы к использованию.

Как только вы начнёте тегировать ваш репозиторий версиями, важно продолжать тегировать новые релизы по мере разработки вашего модуля. Когда пользователи запрашивают новую версию вашего модуля (с помощью go get -u или go get example.com/hello), команда go выберет наибольшую доступную семантическую версию, даже если эта версия была выпущена несколько лет назад и содержит множество изменений по сравнению с основной веткой. Продолжение тегирования новых релизов позволит вашим пользователям получать ваши последние улучшения.

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

v0: первоначальная, нестабильная версия

Тегируем модуль с использованием семантической версии v0. Версия v0 не даёт никаких гарантий стабильности, поэтому почти все проекты должны начинать с v0, пока они не определят свой публичный API.

Тегирование новой версии включает несколько шагов:

  1. Выполните go mod tidy, что удалит все зависимости, которые могло бы набраться модулем, но больше не требуются.

  2. Выполните go test ./... ещё раз, чтобы убедиться, что всё работает корректно.

  3. Тегируйте проект новой версией с помощью git tag.

  4. Отправьте новый тег в репозиторий origin.

<code>$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v0.1.0"
$ git tag v0.1.0
$ git push origin v0.1.0
$
</code>

Теперь другие проекты могут зависеть от версии v0.1.0 пакета example.com/hello. Для собственного модуля вы можете выполнить команду go list -m example.com/hello@v0.1.0, чтобы подтвердить, что последняя версия доступна (этот пример модуля не существует, поэтому версии недоступны). Если вы не видите последнюю версию сразу и используете прокси Go-модулей (по умолчанию с Go 1.13), попробуйте снова через несколько минут, чтобы дать прокси время загрузить новую версию.

Если вы добавляете что-то в публичный API, вносите разрушающие изменения в v0 модуль или обновляете младшую или старшую версию одной из ваших зависимостей, увеличьте значение MINOR версии для следующего релиза. Например, следующий релиз после v0.1.0 будет v0.2.0.

Если вы исправляете ошибку в существующей версии, увеличьте значение PATCH версии. Например, следующий релиз после v0.1.0 будет v0.1.1.

v1: первая стабильная версия

Как только вы полностью уверены, что API вашего модуля стабилен, вы можете выпустить v1.0.0. Версия v1 указывает пользователям, что никакие несовместимые изменения не будут внесены в API модуля. Пользователи могут обновляться до новых минорных и патч-версий v1, и их код не должен сломаться. Подписи функций и методов не изменятся, экспортируемые типы не будут удаляться и так далее. Если в API происходят изменения, они будут обратно совместимы (например, добавление нового поля в структуру) и будут включены в новую минорную версию. Если есть исправления ошибок (например, исправление уязвимости), они будут включены в патч-релиз (или как часть минорного релиза).

Иногда сохранение обратной совместимости может привести к неуклюжем API. Это нормально. Несовершенный API лучше, чем поломка существующего кода пользователей.

Стандартная библиотека пакета strings является отличным примером сохранения обратной совместимости за счёт несогласованности API.

Однако, Replace принимает количество экземпляров строки, которые нужно заменить, начиная с начала (в отличие от Split).

Учитывая Split и SplitN, можно было бы ожидать функции вроде Replace и ReplaceN. Но мы не могли изменить существующую Replace, не сломав вызовы, что мы обещали не делать. Поэтому в Go 1.12 мы добавили новую функцию, ReplaceAll. Результатом стало немного необычное API, поскольку Split и Replace ведут себя по-разному, но такая несогласованность предпочтительнее, чем поломка существующего кода.

Допустим, вы довольны API пакета example.com/hello и хотите выпустить v1 как первую стабильную версию.

Тегирование v1 происходит по тому же процессу, что и тегирование версии v0: выполните go mod tidy и go test ./..., создайте тег версии и отправьте его в репозиторий-источник:

<code>$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v1.0.0"
$ git tag v1.0.0
$ git push origin v1.0.0
$
</code>

На этом этапе API v1 пакета example.com/hello закреплено. Это сообщает всем, что наше API стабильно, и пользователи могут быть уверены в его использовании.

Заключение

В этой статье описан процесс тегирования модуля с использованием семантических версий и когда следует выпускать v1. В будущей статье будет рассказано, как поддерживать и публиковать модули начиная с v2 и далее.

Для предоставления обратной связи и помощи в формировании будущего управления зависимостями в Go, пожалуйста, отправляйте нам отчёты об ошибках или отчёты о практическом опыте.

Спасибо за всю вашу обратную связь и помощь в улучшении модулей Go.

Следующая статья: Работа с ошибками в Go 1.13
Предыдущая статья: Выпущен Go 1.13
Индекс блога

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang