HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В Go используется нестандартная система публикации модулей. В отличие от большинства других популярных языков, в Go не нужно самостоятельно загружать код в централизованные репозитории. Каждый Go-модуль публикуется только в репозиториях исходного кода, например, на GitHub или GitLab.</p>
1 <p>В Go используется нестандартная система публикации модулей. В отличие от большинства других популярных языков, в Go не нужно самостоятельно загружать код в централизованные репозитории. Каждый Go-модуль публикуется только в репозиториях исходного кода, например, на GitHub или GitLab.</p>
2 <p>В этом уроке мы рассмотрим, как работает публикация модулей в Go.</p>
2 <p>В этом уроке мы рассмотрим, как работает публикация модулей в Go.</p>
3 <h2>Как работает загрузка через прокси-сервер</h2>
3 <h2>Как работает загрузка через прокси-сервер</h2>
4 <p>Команда go не загружает пакеты напрямую из хранилищ исходного кода, а опрашивает<a>прокси-сервер</a>.</p>
4 <p>Команда go не загружает пакеты напрямую из хранилищ исходного кода, а опрашивает<a>прокси-сервер</a>.</p>
5 <p>Этот сервер хранит все публичные Go-модули во всех версиях. Если модуль или запрошенная версия отсутствуют на сервере, то прокси попытается сохранить их у себя, прежде чем отдать пользователю. Такая система защищает пользователей от потери доступа к пакету, если автор изменит нужный тег, перезапишет его или удалит пакет целиком.</p>
5 <p>Этот сервер хранит все публичные Go-модули во всех версиях. Если модуль или запрошенная версия отсутствуют на сервере, то прокси попытается сохранить их у себя, прежде чем отдать пользователю. Такая система защищает пользователей от потери доступа к пакету, если автор изменит нужный тег, перезапишет его или удалит пакет целиком.</p>
6 <p>Еще пользователя оберегает<a>база данных контрольных сумм</a>, которая хранит записи из файлов<em>go.sum</em>. Представьте, что вы загружаете модуль командой go. В этот момент команда считывает хэш загружаемого модуля и сверяет его с данными из базы контрольных сумм. Если они не совпадают, то установка завершается с ошибкой.</p>
6 <p>Еще пользователя оберегает<a>база данных контрольных сумм</a>, которая хранит записи из файлов<em>go.sum</em>. Представьте, что вы загружаете модуль командой go. В этот момент команда считывает хэш загружаемого модуля и сверяет его с данными из базы контрольных сумм. Если они не совпадают, то установка завершается с ошибкой.</p>
7 <h2>Как найти запрашиваемый пакет в прокси</h2>
7 <h2>Как найти запрашиваемый пакет в прокси</h2>
8 <p>Проследим установку пакета на примере популярной библиотеки<a>lo</a>. Возьмем базовый пример:</p>
8 <p>Проследим установку пакета на примере популярной библиотеки<a>lo</a>. Возьмем базовый пример:</p>
9 <p>Создадим модуль со структурой:</p>
9 <p>Создадим модуль со структурой:</p>
10 <p>И содержимым<em>hxlt.go</em></p>
10 <p>И содержимым<em>hxlt.go</em></p>
11 <p>Вызов команды go get github.com/samber/lo@latest проверит переменную окружения GOPROXY. Допустим, она содержит список прокси-серверов:</p>
11 <p>Вызов команды go get github.com/samber/lo@latest проверит переменную окружения GOPROXY. Допустим, она содержит список прокси-серверов:</p>
12 <p>Тогда команда go выполнит следующие шаги:</p>
12 <p>Тогда команда go выполнит следующие шаги:</p>
13 <ul><li>Запросит последнюю версию github.com/samber/lo от сервера https://corp.example.com/</li>
13 <ul><li>Запросит последнюю версию github.com/samber/lo от сервера https://corp.example.com/</li>
14 <li>Запросит последнюю версию github.com/samber от сервера https://corp.example.com/</li>
14 <li>Запросит последнюю версию github.com/samber от сервера https://corp.example.com/</li>
15 <li>Запросит последнюю версию github.com от сервера https://corp.example.com/</li>
15 <li>Запросит последнюю версию github.com от сервера https://corp.example.com/</li>
16 </ul><p>Представим, что все запросы к<a>https://corp.example.com/</a>выдали ошибку 404 или 410. Тогда команда перейдет к следующей записи:</p>
16 </ul><p>Представим, что все запросы к<a>https://corp.example.com/</a>выдали ошибку 404 или 410. Тогда команда перейдет к следующей записи:</p>
17 <ul><li>Запросит последнюю версию github.com/samber/lo от сервера https://proxy.golang.org/</li>
17 <ul><li>Запросит последнюю версию github.com/samber/lo от сервера https://proxy.golang.org/</li>
18 <li>Запросит последнюю версию github.com/samber от сервера https://proxy.golang.org/</li>
18 <li>Запросит последнюю версию github.com/samber от сервера https://proxy.golang.org/</li>
19 <li>Запросит последнюю версию github.com от сервера https://proxy.golang.org/</li>
19 <li>Запросит последнюю версию github.com от сервера https://proxy.golang.org/</li>
20 </ul><p>Если в результате найдется один модуль с именем github.com/samber/lo и пакетом lo, то команда go обновит файлы<em>go.mod и go.sum</em>и скачает сам пакет.</p>
20 </ul><p>Если в результате найдется один модуль с именем github.com/samber/lo и пакетом lo, то команда go обновит файлы<em>go.mod и go.sum</em>и скачает сам пакет.</p>
21 <h2>Как найти последнюю версию пакета</h2>
21 <h2>Как найти последнюю версию пакета</h2>
22 <p>Продолжим пример с запросом go get github.com/samber/lo@latest. Здесь можно использовать атрибут @latest, и тогда go определит последнюю версию запрашиваемого пакета.</p>
22 <p>Продолжим пример с запросом go get github.com/samber/lo@latest. Здесь можно использовать атрибут @latest, и тогда go определит последнюю версию запрашиваемого пакета.</p>
23 <p>Рассмотрим, как это работает. Допустим в системе контроля версий проекта есть несколько веток и тегов:</p>
23 <p>Рассмотрим, как это работает. Допустим в системе контроля версий проекта есть несколько веток и тегов:</p>
24 <ul><li>Ветка main</li>
24 <ul><li>Ветка main</li>
25 <li>Ветка v2</li>
25 <li>Ветка v2</li>
26 <li>Тег v1.0.0</li>
26 <li>Тег v1.0.0</li>
27 <li>Тег 1101-1663105068</li>
27 <li>Тег 1101-1663105068</li>
28 </ul><p>В этом случае последней версией будет считаться<em>тег v1.0.0</em>, потому что Go отдает приоритет тегам перед ветками. Обратите внимание, что используются только те теги, которые начинаются с буквы v и следуют<a>семантическому версионированию</a>.</p>
28 </ul><p>В этом случае последней версией будет считаться<em>тег v1.0.0</em>, потому что Go отдает приоритет тегам перед ветками. Обратите внимание, что используются только те теги, которые начинаются с буквы v и следуют<a>семантическому версионированию</a>.</p>
29 <p>Если в проекте нет тегов, команда создает<a>псевдо-версию</a>, используя последний коммит.</p>
29 <p>Если в проекте нет тегов, команда создает<a>псевдо-версию</a>, используя последний коммит.</p>
30 <h2>Как публиковать модуль</h2>
30 <h2>Как публиковать модуль</h2>
31 <p>Попробуем опубликовать собственный модуль. Этот процесс состоит из нескольких шагов.</p>
31 <p>Попробуем опубликовать собственный модуль. Этот процесс состоит из нескольких шагов.</p>
32 <p>Создаем go-модуль:</p>
32 <p>Создаем go-модуль:</p>
33 <p>Добавляем код в модуль, созданный выше:</p>
33 <p>Добавляем код в модуль, созданный выше:</p>
34 <p>Запускаем go mod tidy, чтобы загрузить недостающие пакеты и удалить лишние:</p>
34 <p>Запускаем go mod tidy, чтобы загрузить недостающие пакеты и удалить лишние:</p>
35 <p>Дальше нужно отформатировать код с помощью встроенного инструмента go fmt. Не забывайте запускать форматирование перед публикацией пакета - так вы убедитесь, что вывод команды не содержит ошибок.</p>
35 <p>Дальше нужно отформатировать код с помощью встроенного инструмента go fmt. Не забывайте запускать форматирование перед публикацией пакета - так вы убедитесь, что вывод команды не содержит ошибок.</p>
36 <p>После этого проверяем код линтером. Для Go написано множество линтеров, поэтому появились утилиты-аггрегаторы, запускающие несколько линтеров разом. Мы рекомендуем пользоваться<a>golangci-lint</a>.</p>
36 <p>После этого проверяем код линтером. Для Go написано множество линтеров, поэтому появились утилиты-аггрегаторы, запускающие несколько линтеров разом. Мы рекомендуем пользоваться<a>golangci-lint</a>.</p>
37 <p>Остался последний шаг - добавляем новый тег в репозиторий:</p>
37 <p>Остался последний шаг - добавляем новый тег в репозиторий:</p>
38 <p>Модуль готов к скачиванию! Чтобы пользователи смогли найти его до загрузки, можно запустить команду go list - она добавит модуль в<a>индекс go.pkg</a>.</p>
38 <p>Модуль готов к скачиванию! Чтобы пользователи смогли найти его до загрузки, можно запустить команду go list - она добавит модуль в<a>индекс go.pkg</a>.</p>
39 <h2>Как добавлять обратно несовместимые изменения</h2>
39 <h2>Как добавлять обратно несовместимые изменения</h2>
40 <p>В работе над проектом разработчики могут переименовывать функции, изменять ее сигнатуры, удалять устаревший код. Подобные действия могут вносить ломающие изменения в API. Это значит, что пользователям придется вносить изменения в собственную кодовую базу.</p>
40 <p>В работе над проектом разработчики могут переименовывать функции, изменять ее сигнатуры, удалять устаревший код. Подобные действия могут вносить ломающие изменения в API. Это значит, что пользователям придется вносить изменения в собственную кодовую базу.</p>
41 <p>Go-разработчики серьезно относятся к подобным изменениям в API. Если вам нужно внести ломающие изменения, не получится ограничиться добавлением тега с увеличенной мажорной версией. Рассмотрим подробнее, как устроено внесение таких изменений.</p>
41 <p>Go-разработчики серьезно относятся к подобным изменениям в API. Если вам нужно внести ломающие изменения, не получится ограничиться добавлением тега с увеличенной мажорной версией. Рассмотрим подробнее, как устроено внесение таких изменений.</p>
42 <p>Возьмем модуль из примера выше. Его структура выглядит так:</p>
42 <p>Возьмем модуль из примера выше. Его структура выглядит так:</p>
43 <p>При этом файл<em>go.mod</em>выглядит так:</p>
43 <p>При этом файл<em>go.mod</em>выглядит так:</p>
44 <p>Чтобы создать версию 2.0.0 этого модуля, нужно обновить<em>go.mod</em>, чтобы он содержал в названии новую мажорную версию пакета. Сохраним текущий код в ветке v1:</p>
44 <p>Чтобы создать версию 2.0.0 этого модуля, нужно обновить<em>go.mod</em>, чтобы он содержал в названии новую мажорную версию пакета. Сохраним текущий код в ветке v1:</p>
45 <p>Обновим<em>go.mod</em>файл, добавив ему в название v2:</p>
45 <p>Обновим<em>go.mod</em>файл, добавив ему в название v2:</p>
46 <p>Если бы в проекте было несколько пакетов, то для папки с v2 пришлось бы обновить все импорты, чтобы они включали суффикс v2. Добавим тег и опубликуем:</p>
46 <p>Если бы в проекте было несколько пакетов, то для папки с v2 пришлось бы обновить все импорты, чтобы они включали суффикс v2. Добавим тег и опубликуем:</p>
47 <h2>Выводы</h2>
47 <h2>Выводы</h2>
48 <ul><li>Чтобы опубликовать Go-модуль, нужно внести его в хранилище исходного кода: то есть добавить сам код, файлы<em>go.mod</em>и<em>go.sum</em>в систему контроля версий</li>
48 <ul><li>Чтобы опубликовать Go-модуль, нужно внести его в хранилище исходного кода: то есть добавить сам код, файлы<em>go.mod</em>и<em>go.sum</em>в систему контроля версий</li>
49 <li>Имя модуля при этом должно содержать путь до модуля в этом хранилище</li>
49 <li>Имя модуля при этом должно содержать путь до модуля в этом хранилище</li>
50 <li>Модули по умолчанию загружаются не из хранилищ кода, а из центрального репозитория пакетов</li>
50 <li>Модули по умолчанию загружаются не из хранилищ кода, а из центрального репозитория пакетов</li>
51 <li>Чтобы точнее указать требования к версии пакета, можно использовать<a>version query</a>. Отсутствующий version query имеет дефолтное значение latest</li>
51 <li>Чтобы точнее указать требования к версии пакета, можно использовать<a>version query</a>. Отсутствующий version query имеет дефолтное значение latest</li>
52 <li>Перед публикацией не забывайте форматировать код и проверять его линтером</li>
52 <li>Перед публикацией не забывайте форматировать код и проверять его линтером</li>
53 <li>В работе над публичными проектами на Go добавляйте новые версии, а не изменяйте уже существующие</li>
53 <li>В работе над публичными проектами на Go добавляйте новые версии, а не изменяйте уже существующие</li>
54 </ul>
54 </ul>