The Go Blog
Использование Go Modules
Введение
Эта запись является частью серии.
- Часть 1 — Использование Go Modules (данная запись)
- Часть 2 — Миграция на Go Modules
- Часть 3 — Публикация Go Modules
- Часть 4 — Go Modules: v2 и далее
- Часть 5 — Сохранение совместимости модулей
Примечание: Для документации по управлению зависимостями с использованием модулей, см. Управление зависимостями.
Go 1.11 и 1.12 включают предварительную поддержку модулей, новую систему управления зависимостями Go, которая делает информацию о версиях зависимостей явной и облегчает её управление. Эта статья представляет собой введение в базовые операции, необходимые для начала работы с модулями.
Модуль — это коллекция
Go пакетов,
хранящихся в файловой системе с файлом go.mod в корне.
Файл go.mod определяет путь модуля,
который также является путем импорта, используемым для корневого каталога,
и его требования к зависимостям,
то есть другие модули, необходимые для успешной сборки.
Каждое требование к зависимости записывается как путь модуля и конкретная
семантическая версия.
Начиная с Go 1.11, команда go включает использование модулей
при наличии файла go.mod в текущем каталоге или любом родительском каталоге,
при условии, что каталог находится вне $GOPATH/src.
(Внутри $GOPATH/src, для совместимости, команда go
всё ещё работает в старом режиме GOPATH, даже если файл go.mod найден.
См. подробности в
документации команды go.)
Начиная с Go 1.13, режим модулей станет стандартным для всего разработки.
В этой записи рассматриваются последовательность общих операций, которые возникают при разработке Go кода с использованием модулей:
- Создание нового модуля.
- Добавление зависимости.
- Обновление зависимостей.
- Добавление зависимости на новую основную версию.
- Обновление зависимости до новой основной версии.
- Удаление неиспользуемых зависимостей.
Создание нового модуля
Создадим новый модуль.
Создайте новый, пустой каталог где-либо вне $GOPATH/src,
перейдите в этот каталог с помощью cd, а затем создайте новый исходный файл, hello.go:
<code>package hello
func Hello() string {
return "Hello, world."
}
</code>
Также напишем тест в файле hello_test.go:
<code>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)
}
}
</code>
На данном этапе в директории содержится пакет, но не модуль,
поскольку отсутствует файл go.mod.
Если бы мы работали в директории /home/gopher/hello и выполнили команду go test,
то получили бы следующий вывод:
<code>$ go test PASS ok _/home/gopher/hello 0.020s $ </code>
Последняя строка представляет собой сводку по общему тестированию пакета.
Поскольку мы работаем вне $GOPATH
и также вне любого модуля,
команда go не знает импортного пути для текущей директории и создаёт фиктивный путь,
основанный на имени директории: _/home/gopher/hello.
Сделаем текущую директорию корневой для модуля,
используя команду go mod init, а затем снова попробуем выполнить go test:
<code>$ go mod init example.com/hello go: creating new go.mod: module example.com/hello $ go test PASS ok example.com/hello 0.020s $ </code>
Поздравляем! Вы написали и протестировали свой первый модуль.
Команда go mod init создала файл go.mod:
<code>$ cat go.mod module example.com/hello go 1.12 $ </code>
Файл go.mod появляется только в корне модуля.
Пакеты в поддиректориях имеют импортные пути, состоящие из пути модуля плюс путь к поддиректории.
Например, если мы создадим поддиректорию world,
то не нужно (и не стоит) запускать go mod init там.
Пакет будет автоматически распознан как часть модуля
example.com/hello с импортным путём
example.com/hello/world.
Добавление зависимости
Основной мотивацией для создания Go модулей была улучшенная поддержка использования (то есть добавления зависимости на) код, написанный другими разработчиками.
Обновим наш файл hello.go, чтобы импортировать rsc.io/quote
и использовать его для реализации функции Hello:
<code>package hello
import "rsc.io/quote"
func Hello() string {
return quote.Hello()
}
</code>
Теперь снова запустим тест:
<code>$ go test go: finding rsc.io/quote v1.5.2 go: downloading rsc.io/quote v1.5.2 go: extracting rsc.io/quote v1.5.2 go: finding rsc.io/sampler v1.3.0 go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: downloading rsc.io/sampler v1.3.0 go: extracting rsc.io/sampler v1.3.0 go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c PASS ok example.com/hello 0.023s $ </code>
Команда go разрешает импорты, используя конкретные
версии зависимостей, перечисленные в go.mod.
Когда она сталкивается с импортом пакета, который не предоставляется
никаким модулем в go.mod, команда go автоматически
ищет модуль, содержащий этот пакет, и добавляет его в
go.mod, используя последнюю версию.
(«Последняя» определяется как последняя тегированная стабильная (не-предрелизная) версия,
либо последняя тегированная предрелизная версия,
либо последняя нетегированная версия.)
В нашем примере go test разрешил новый импорт rsc.io/quote
к модулю rsc.io/quote v1.5.2.
Он также загрузил две зависимости, используемые rsc.io/quote,
а именно rsc.io/sampler и golang.org/x/text.
В файле go.mod записываются только прямые зависимости:
<code>$ cat go.mod module example.com/hello go 1.12 require rsc.io/quote v1.5.2 $ </code>
Вторая команда go test не будет повторять эту работу,
поскольку go.mod теперь актуален, а загруженные
модули кэшируются локально (в $GOPATH/pkg/mod):
<code>$ go test PASS ok example.com/hello 0.020s $ </code>
Обратите внимание, что хотя команда go делает добавление нового зависимости
быстрым и простым, это не бесплатно.
Ваш модуль теперь буквально зависит от новой зависимости
в критических аспектах, таких как корректность, безопасность и правильная лицензия,
чтобы назвать лишь несколько из них.
Для дополнительных соображений см. запись в блоге Расса Кокса,
“Our Software Dependency Problem.”
Как мы видели выше, добавление одной прямой зависимости часто
приводит к появлению других косвенных зависимостей.
Команда go list -m all выводит текущий модуль
и все его зависимости:
<code>$ go list -m all example.com/hello golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 $ </code>
В выводе команды go list, текущий модуль,
также известный как главный модуль,
всегда находится на первой строке,
за которым следуют зависимости, отсортированные по пути модуля.
Версия golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
является примером
псевдо-версии,
которая представляет собой синтаксис версий команды go
для конкретного непомеченного коммита.
Помимо go.mod, команда go
поддерживает файл с именем go.sum, содержащий
ожидаемые криптографические хеши содержимого конкретных версий модулей:
<code>$ cat go.sum golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO... golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq... rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3... rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX... rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q... rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9... $ </code>
Команда go использует файл go.sum для того, чтобы убедиться,
что будущие загрузки этих модулей получают те же данные,
что и при первой загрузке,
чтобы гарантировать, что модули, от которых зависит ваш проект,
не изменяются неожиданно,
независимо от причин: злонамеренных, случайных или других.
И go.mod, и go.sum должны быть добавлены в систему контроля версий.
Обновление зависимостей
С использованием модулей Go версии указываются с помощью тегов семантического версионирования.
Семантическая версия имеет три части: основная, второстепенная и патч.
Например, для v0.1.2 основная версия — 0, второстепенная — 1,
а патч — 2.
Рассмотрим несколько обновлений второстепенных версий.
В следующем разделе мы рассмотрим обновление основной версии.
Из вывода команды go list -m all мы можем увидеть, что используется непомеченная версия пакета golang.org/x/text.
Давайте обновим её до последней помеченной версии и проверим, что всё ещё работает:
<code>$ go get golang.org/x/text go: finding golang.org/x/text v0.3.0 go: downloading golang.org/x/text v0.3.0 go: extracting golang.org/x/text v0.3.0 $ go test PASS ok example.com/hello 0.013s $ </code>
Ура! Все тесты проходят.
Давайте ещё раз взглянем на вывод команды go list -m all и файл go.mod:
<code>$ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote v1.5.2 ) $ </code>
Пакет golang.org/x/text был обновлён до последней помеченной версии (v0.3.0).
Файл go.mod также был обновлён, чтобы указать версию v0.3.0.
Комментарий indirect показывает, что зависимость не используется напрямую
этим модулем, а только косвенно другими зависимостями модулей.
См. go help modules для получения подробной информации.
Теперь давайте попробуем обновить минорную версию rsc.io/sampler.
Начнём так же, как и раньше — запустив go get и выполнив тесты:
<code>$ go get rsc.io/sampler go: finding rsc.io/sampler v1.99.99 go: downloading rsc.io/sampler v1.99.99 go: extracting rsc.io/sampler v1.99.99 $ go test --- FAIL: TestHello (0.00s) hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world." FAIL exit status 1 FAIL example.com/hello 0.014s $ </code>
Ой! Ошибка теста показывает, что последняя версия rsc.io/sampler несовместима с нашим использованием.
Давайте выведем доступные помеченные версии этого модуля:
<code>$ go list -m -versions rsc.io/sampler rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99 $ </code>
Мы использовали версию v1.3.0; очевидно, что v1.99.99 нам не подходит. Может быть, стоит попробовать использовать v1.3.1:
<code>$ go get rsc.io/sampler@v1.3.1 go: finding rsc.io/sampler v1.3.1 go: downloading rsc.io/sampler v1.3.1 go: extracting rsc.io/sampler v1.3.1 $ go test PASS ok example.com/hello 0.022s $ </code>
Обратите внимание на явное указание @v1.3.1 в аргументе команды go get.
В общем случае каждый аргумент, передаваемый в go get, может содержать
явную версию; по умолчанию используется @latest,
которая разрешается в последнюю версию, определённую ранее.
Добавление зависимости на новую мажорную версию
Добавим новую функцию в наш пакет:
func Proverb возвращает пословицу о параллелизме на Go,
вызывая quote.Concurrency, которая предоставляется
модулем rsc.io/quote/v3.
Сначала мы обновим hello.go, чтобы добавить новую функцию:
<code>package hello
import (
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string {
return quote.Hello()
}
func Proverb() string {
return quoteV3.Concurrency()
}
</code>
Затем мы добавляем тест в файл hello_test.go:
<code>func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
</code>
Затем можно протестировать код:
<code>$ go test go: finding rsc.io/quote/v3 v3.1.0 go: downloading rsc.io/quote/v3 v3.1.0 go: extracting rsc.io/quote/v3 v3.1.0 PASS ok example.com/hello 0.024s $ </code>
Обратите внимание, что теперь наш модуль зависит от обоих пакетов: rsc.io/quote и rsc.io/quote/v3:
<code>$ go list -m rsc.io/q... rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.1.0 $ </code>
Каждая отдельная мажорная версия (например, v1, v2 и так далее) Go модуля
использует другой путь модуля: начиная с версии v2, путь должен заканчиваться на номер мажорной версии.
В примере, версия v3 пакета rsc.io/quote больше не называется просто rsc.io/quote: вместо этого она идентифицируется по пути модуля rsc.io/quote/v3.
Эта конвенция называется
семантическое версионирование импортов,
и она даёт несовместимым пакетам (те, у которых разные мажорные версии)
разные имена.
Напротив, версия v1.6.0 пакета rsc.io/quote должна быть обратно совместима
с версией v1.5.2, поэтому она переиспользует имя rsc.io/quote.
(В предыдущем разделе, rsc.io/sampler v1.99.99
должен был быть обратно совместим с rsc.io/sampler v1.3.0, но ошибки или неправильные предположения клиентов о поведении модуля могут возникнуть.)
Команда go позволяет включать в сборку не более одной версии
любого конкретного пути модуля, то есть не более одной мажорной версии:
один rsc.io/quote, один rsc.io/quote/v2, один rsc.io/quote/v3,
и так далее.
Это даёт авторам модулей чёткое правило относительно возможного дублирования пути одного и того же модуля: невозможно собрать программу, использующую одновременно
rsc.io/quote v1.5.2 и rsc.io/quote v1.6.0.
Одновременно разрешение использования разных мажорных версий модуля
(поскольку у них разные пути)
даёт потребителям модулей возможность постепенно переходить на новую мажорную версию.
В этом примере мы хотели использовать quote.Concurrency из rsc/quote/v3 v3.1.0
но ещё не готовы полностью перейти на использование rsc.io/quote v1.5.2.
Возможность постепенного перехода
особенно важна в крупных программах или кодовых базах.
Обновление зависимости до новой мажорной версии
Давайте завершим преобразование, чтобы использовать только rsc.io/quote/v3, вместо rsc.io/quote.
Из-за изменения мажорной версии, можно ожидать, что некоторые API могли быть удалены, переименованы или иным образом изменены несовместимым образом.
Просмотрев документацию, можно увидеть, что функция Hello была переименована в HelloV3:
<code>$ go doc rsc.io/quote/v3 package quote // import "rsc.io/quote/v3" Пакет quote собирает короткие высказывания. func Concurrency() string func GlassV3() string func GoV3() string func HelloV3() string func OptV3() string $ </code>
Можно обновить использование quote.Hello() в hello.go, чтобы использовать quoteV3.HelloV3():
<code>package hello
import quoteV3 "rsc.io/quote/v3"
func Hello() string {
return quoteV3.HelloV3()
}
func Proverb() string {
return quoteV3.Concurrency()
}
</code>
А затем в этот момент уже нет необходимости в переименованном импорте, поэтому можно отменить его:
<code>package hello
import "rsc.io/quote/v3"
func Hello() string {
return quote.HelloV3()
}
func Proverb() string {
return quote.Concurrency()
}
</code>
Давайте снова запустим тесты, чтобы убедиться, что всё работает:
<code>$ go test PASS ok example.com/hello 0.014s </code>
Удаление неиспользуемых зависимостей
Мы удалили все наши использования rsc.io/quote,
но оно всё ещё отображается в go list -m all и в файле go.mod:
<code>$ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.0.0 rsc.io/sampler v1.3.1 // indirect ) $ </code>
Почему? Потому что сборка отдельного пакета, как это делается с помощью go build или go test,
может легко определить, что чего-то не хватает и нужно добавить,
но не может определить, что можно безопасно удалить.
Удаление зависимости возможно только после проверки всех пакетов модуля,
а также всех возможных комбинаций build-тегов для этих пакетов.
Обычная команда сборки не загружает эту информацию,
и поэтому не может безопасно удалять зависимости.
Команда go mod tidy очищает неиспользуемые зависимости:
<code>$ go mod tidy $ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 // indirect ) $ go test PASS ok example.com/hello 0.020s $ </code>
Заключение
Модули Go — это будущее управления зависимостями в Go. Функциональность модулей теперь доступна во всех поддерживаемых версиях Go (то есть в Go 1.11 и Go 1.12).
В этой статье были представлены следующие рабочие процессы с использованием Go модулей:
go mod initсоздаёт новый модуль, инициализируя файлgo.mod, который описывает его.go build,go testи другие команды сборки пакетов добавляют новые зависимости вgo.modпо мере необходимости.go list -m allвыводит зависимости текущего модуля.go getизменяет требуемую версию зависимости (или добавляет новую зависимость).go mod tidyудаляет неиспользуемые зависимости.
Мы призываем вас начать использовать модули в локальной разработке и добавлять файлы go.mod и go.sum в ваши проекты. Чтобы предоставить обратную связь и помочь сформировать будущее управления зависимостями в Go, просим вас отправлять нам сообщения об ошибках или отчеты об опыте.
Благодарим всех за вашу обратную связь и помощь в улучшении модулей.
Следующая статья: Отладка того, что вы развертываете в Go 1.12
Предыдущая статья: Новая сеть разработчиков Go
Индекс блога