пятница, 1 ноября 2019 г.

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

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

Обратите внимание: этот пост посвящен разработке до v1 включительно.

Этот пост использует Git в примерах. Mercurial, Bazaar и другие также поддерживаются.

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

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

$ 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)
    }
}

$

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

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

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

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

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

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

Увеличивайте версию MINOR, когда вы вносите обратно совместимые изменения в API, такие как изменение зависимостей или добавление новой функции, метода, поля структуры или типа.

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

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

v0 major версии и предварительные (pre-release) версии не гарантируют обратную совместимость. Они позволяют вам усовершенствовать ваш API, прежде чем брать на себя обязательства по обеспечению стабильности для ваших пользователей. Однако major версии v1 и более поздние требуют обратной совместимости с этой major версией.

Версия, на которую ссылается go.mod, может быть явным релизом, помеченным (имеющим тег) в репозитории (например, v1.5.2), или это может быть псевдо-версия, основанная на конкретном коммите (например, v0.0.0-20170915032832-14c0d48ead0c). Псевдо-версии - это особый тип предварительной (pre-release) версии. Псевдо-версии полезны, когда пользователь должен зависеть от проекта, который не опубликовал какие-либо теги семантической версии, или разрабатываться на основе коммита, который еще не был помечен тегом, но пользователи не должны предполагать, что псевдо-версии обеспечивают стабильную или хорошую протестированную версию 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. Отправьте (push) новый тег в исходное хранилище.

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

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

Split разбивает строку на все подстроки, разделенные разделителем, и возвращает срез подстрок между этими разделителями.

SplitN может использоваться для контроля количества возвращаемых подстрок. Однако 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 ./..., пометьте версию тегом и поместите тег в исходный репозиторий:

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

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

Заключение

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


Читайте также:


Комментариев нет:

Отправить комментарий