четверг, 31 октября 2019 г.

Миграция на модули Go

В проектах Go используется широкий спектр стратегий управления зависимостями. Вендоринг инструменты, такие как dep и glide, популярны, но они имеют большие различия в поведении и не всегда хорошо работают вместе. Некоторые проекты хранят весь каталог GOPATH в одном Git-репозитории. Другие просто полагаются на go get и ожидают, что в GOPATH будут установлены довольно свежие версии зависимостей.

Система модулей Go, представленная в Go 1.11, предоставляет официальное решение для управления зависимостями, встроенное в команду go. В этом посте описываются инструменты и методы для миграции проекта на модули.

Обратите внимание: если ваш проект уже помечен как v2.0.0 или выше, вам потребуется обновить путь к модулю при добавлении файла go.mod.

Миграция на Go модули в вашем проекте

Проект может находиться в одном из трех состояний при начале перехода к модулям Go:

  • Новый проект Go.
  • Созданный проект Go с немодульным менеджером зависимостей.
  • Созданный проект Go без какого-либо менеджера зависимостей.

Первый случай описан в посте модули Go; мы рассмотрим последние два в этом посте.

С менеджером зависимостей

Чтобы преобразовать проект, который уже использует инструмент управления зависимостями, выполните следующие команды:

$ git clone https://github.com/my/project
[...]
$ cd project
$ cat Godeps/Godeps.json
{
    "ImportPath": "github.com/my/project",
    "GoVersion": "go1.12",
    "GodepVersion": "v80",
    "Deps": [
        {
            "ImportPath": "rsc.io/binaryregexp",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        },
        {
            "ImportPath": "rsc.io/binaryregexp/syntax",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        }
    ]
}
$ go mod init github.com/my/project
go: creating new go.mod: module github.com/my/project
go: copying requirements from Godeps/Godeps.json
$ cat go.mod
module github.com/my/project

go 1.12

require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

go mod init создает новый файл go.mod и автоматически импортирует зависимости из Godeps.json, Gopkg.lock или ряда других поддерживаемых форматов. Аргументом для перехода mod init является путь модуля, где можно найти модуль.

Это хорошее время, чтобы сделать паузу и запустить go build ./... и go test ./... прежде чем продолжить. Последующие шаги могут изменить ваш файл go.mod, поэтому, если вы предпочитаете использовать итеративный подход, этот файл go.mod будет ближе всего к вашей спецификации зависимостей перед модулями.

$ go mod tidy
go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$ cat go.sum
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
$

go mod tidy находит все пакеты, транзитивно импортированные пакетами в вашем модуле. Он добавляет новые требования к модулям для пакетов, не предоставляемых ни одним из известных модулей, и устраняет требования к модулям, которые не предоставляют никаких импортированных пакетов. Если модуль предоставляет пакеты, которые импортируются только проектами, которые еще не мигрировали в модули, требование к модулю будет помечено // indirect комментарием. Хорошей практикой всегда является запуск go mod tidy перед передачей файла go.mod в систему управления версиями.

Закончим, убедившись, что сборка кода и тесты пройдены:

$ go build ./...
$ go test ./...
[...]
$

Обратите внимание, что другие менеджеры зависимостей могут указывать зависимости на уровне отдельных пакетов или целых репозиториев (не модулей) и, как правило, не распознают требования, указанные в файлах зависимостей go.mod. Следовательно, вы можете не получить точно такую же версию каждого пакета, как раньше, и существует некоторый риск обновления последних изменений. Поэтому важно следовать приведенным выше командам с проверкой полученных зависимостей. Для этого запустите

$ go list -m all
go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
github.com/my/project
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

и сравните полученные версии с вашим старым файлом управления зависимостями, чтобы убедиться, что выбранные версии соответствуют. Если вы нашли версию, которая была не той, которую вы хотели, вы можете узнать, почему с помощью go mod why -m и/или go mod graph, и обновите или понизьте зависмость до нужной версии с помощью go get. (Если запрашиваемая вами версия старше, чем ранее выбранная, go get понизит другие зависимости по мере необходимости для поддержания совместимости.) Например,

$ go mod why -m rsc.io/binaryregexp
[...]
$ go mod graph | grep rsc.io/binaryregexp
[...]
$ go get rsc.io/binaryregexp@v0.2.0
$

Без менеджера зависимостей

Для проекта Go без системы управления зависимостями начните с создания файла go.mod:

$ git clone https://go.googlesource.com/blog
[...]
$ cd blog
$ go mod init golang.org/x/blog
go: creating new go.mod: module golang.org/x/blog
$ cat go.mod
module golang.org/x/blog

go 1.12
$

Без файла конфигурации из предыдущего менеджера зависимостей go mod init создаст файл go.mod только с директивами module и go. В этом примере мы устанавливаем путь к модулю golang.org/x/blog, потому что это его собственный путь импорта. Пользователи могут импортировать пакеты с этим путем, и мы должны быть осторожны, чтобы не изменить его.

Директива module объявляет путь к модулю, а директива go объявляет ожидаемую версию языка Go, используемого для компиляции кода в модуле.

Затем запустите go mod tidy, чтобы добавить зависимости модуля:

$ go mod tidy
go: finding golang.org/x/website latest
go: finding gopkg.in/tomb.v2 latest
go: finding golang.org/x/net latest
go: finding golang.org/x/tools latest
go: downloading github.com/gorilla/context v1.1.1
go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: extracting github.com/gorilla/context v1.1.1
go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
$ cat go.mod
module golang.org/x/blog

go 1.12

require (
    github.com/gorilla/context v1.1.1
    golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
    golang.org/x/text v0.3.2
    golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
    golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
    gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
$ cat go.sum
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
[...]
$

go mod tidy добавил требования к модулю для всех пакетов, транзитивно импортируемых пакетами в вашем модуле, и создал go.sum с контрольными суммами для каждой библиотеки в конкретной версии. Закончим, убедившись, что код все еще собирается и тесты все еще проходят:

$ go build ./...
$ go test ./...
ok      golang.org/x/blog    0.335s
?       golang.org/x/blog/content/appengine    [no test files]
ok      golang.org/x/blog/content/cover    0.040s
?       golang.org/x/blog/content/h2push/server    [no test files]
?       golang.org/x/blog/content/survey2016    [no test files]
?       golang.org/x/blog/content/survey2017    [no test files]
?       golang.org/x/blog/support/racy    [no test files]
$

Обратите внимание, что когда go mod tidy добавляет требование, оно добавляет последнюю версию модуля. Если ваш GOPATH содержал более старую версию зависимости, которая впоследствии опубликовала критическое изменение, вы можете увидеть ошибки в go mod tidy, go build или go test. Если это произойдет, попробуйте перейти на более старую версию с помощью go get (например, go get github.com/broken/module@v1.1.0) или найдите время, чтобы сделать ваш модуль совместимым с последней версией каждой зависимости.

Тесты в режиме модуля

Некоторые тесты могут нуждаться в настройках после перехода на модули Go.

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

Если тест использует относительные пути (../package-in-another-module) для поиска и чтения файлов в другом пакете, он не будет выполнен, если пакет находится в другом модуле, который будет расположен в кэше версионного подкаталога модуля или по пути, указанном в директиве replace. В этом случае вам может потребоваться скопировать входные данные теста в ваш модуль или преобразовать входные данные теста из необработанных файлов в данные, встроенные в исходные файлы .go.

Если тест предполагает выполнение команд go в тесте в режиме GOPATH, он может завершиться неудачей. В этом случае вам может понадобиться добавить файл go.mod в исходное дерево для тестирования или явно отключить GO111MODULE = off.

Публикация релиза

Наконец, вы должны пометить (навесить tag) и опубликовать версию для вашего нового модуля. Это необязательно, если вы еще не выпустили ни одной версии, но без официального выпуска последующие пользователи будут зависеть от конкретных коммитов, использующих псевдо-версии, которые могут быть более сложными для поддержки.

$ git tag v1.2.0
$ git push origin v1.2.0

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

Импорт и канонические пути модулей

Каждый модуль объявляет свой путь к модулю в своем файле go.mod. Каждый оператор импорта, который ссылается на пакет в модуле, должен иметь путь к модулю в качестве префикса пути к пакету. Однако команда go может столкнуться с репозиторием, содержащим модуль, через множество различных путей удаленного импорта. Например, и golang.org/x/lint, и github.com/golang/lint разрешают использовать репозитории, содержащие код, размещенный на go.googlesource.com/lint. Файл go.mod, содержащийся в этом хранилище, объявляет его путь golang.org/x/lint, поэтому только этот путь соответствует допустимому модулю.

Go 1.4 предоставил механизм для объявления канонических путей импорта с использованием // import комментариев, но авторы пакетов не всегда предоставляли их. В результате код, написанный до модулей, мог использовать неканонический путь импорта для модуля, не обнаруживая ошибки для несоответствия. При использовании модулей путь импорта должен совпадать с каноническим путем к модулю, поэтому вам может потребоваться обновить утверждения import: например, вам может потребоваться изменить import "github.com/golang/lint" на import "golang.org/x/lint".

Другой сценарий, в котором канонический путь модуля может отличаться от пути к хранилищу, возникает для модулей Go в основной версии 2 или выше. Модуль Go с основной версией выше 1 должен включать суффикс основной версии в путь к модулю: например, версия v2.0.0 должна иметь суффикс /v2. Однако операторы импорта могли ссылаться на пакеты в модуле без этого суффикса. Например, немодульные пользователи github.com/russross/blackfriday/v2 версии 2.0 могут вместо этого импортировать его как github.com/russross/blackfriday, и им потребуется обновить путь импорта, включив суффикс /v2.

Заключение

Преобразование модулей в Go должно быть простым процессом для большинства пользователей. Случайные проблемы могут возникать из-за неканонических путей импорта или ломающих изменений в зависимостях.


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


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

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