Скоро выходит версия Go 1.18, и в массовом сознании она, скорее всего, будет ассоциироваться с Generic-ами. Но помимо них туда попадает еще несколько вкусных фичей. Например, Go Workspaces.

Что даёт Go Workspaces?

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

Ранее при работе с несколькими модулями одновременно их нужно было «связать» через директиву replace в go.mod-файле.

Подход с replace-ами в go.mod имеет ряд существенных недостатков:

  • каждый модуль живёт своей независимой жизнью и некоторый инструментарий разработки умеет одновременно работать только с одним модулем.

  • replace-ы не транзитивны. То есть если вы добавляете в go.mod новый replace, то его надо добавить во все использующие его модули. Даже при небольшом количестве модулей это доставляет заметные неудобства.

  • запустить утилиту через go run можно только из каталогов тех модулей, которые либо содержат эту утилиту, либо «видят» её через replace (при этом она еще должна быть объявлена как require).

Go Workspaces добавляет немного комфорта:

  • gopls предоставляет информацию сразу по всем используемым модулям;

  • общие replace-ы можно описать в одном месте, и они видны для всех объявленных модулей;

  • локальные правки с директивой replace не нужно прописывать внутри go.modфайлов, которые лежат в репозитории;

  • сборка и запуск утилит для всех объявленных модулей может быть из каталога, в котором лежит go.work и его дочерних каталогов;

  • добавляется команда go work sync, которая позволяет обновить зависимости нескольких модулей до наиболее свежей общей версии.

Как оно работает?

При использовании go.work-файла Go собирает общий список зависимостей из всех перечисленных в go.mod-файлов, с учетом replace-директив. На базе всех перечисленных зависимостей формируется единый граф зависимостей, который и применяется ко всем используемым модулям. Если для одного модуля указано в разных go.mod несколько конфликтующих replace-директив — будет ошибка.

К примеру, если один модуль требует зависимость на github.com/davecgh/go-spew версии v1.1.1, а другой модуль требует зависимость на github.com/davecgh/go-spew версии v1.1.0, то оба модуля будут исполняться с более старшей версией v1.1.1.

Это работает для всех модулей одного Go Workspace, даже если между модулями нет зависимости.

Что не даёт Go Workspace?

Если вы хотите использовать несколько go.mod-файлов в рамках одного (моно)репозитория, то go.work вам никак в этом не поможет: в рамках одного Go Workspace используются общие зависимости, собранные на основе всех используемых go.mod-файлов. То есть такой подход ничем принципиально не будет отличаться от использования одного go.mod-файла, кроме увеличения сложности за счет большего количества используемых файлов.

Собственно ссылка на соседние модули через тэги или идентификатор коммита также остаются за кадром Go Workspace.

Как начать использовать Go Workspaces?

Для использования Go Workspaces добавилась группа команд go work.

Для начала нужно вызвать команду go work init в корне рабочего пространства.

Допустим у нас есть рабочее пространство вида:

Тогда для использования Go Workspaces надо:

  1. Создать go.workфайл:

    go work init
  2. Добавить туда модули:

    go work use shared
    go work use tools

В итоге создастся файл go.work с содержимым вида:

go 1.18
use (
	shared
	tools
)

И далее, к примеру, можно вызвать сборку из любого подкаталога внутри рабочего пространства:

go run github.com/bozaro/go/go-work-play/tools/hello

Если в go.work добавить replace-ы, то они так же будут иметь эффект на все модули рабочего пространства.

Какие еще есть проблемы?

Важно понимать, что использование Go Workspaces меняет поведение некоторых команд, например:

  • go run перестаёт работать для модулей, которые не объявлены в go.work;

  • go list не может менять go.modфайлы:

go list -mod=mod -m -json
go: -mod may only be set to readonly when in workspace mode, but it is set to "mod"
    Remove the -mod flag to use the default readonly value,
    or set -workfile=off to disable workspace mode.

К этому изменению некоторые утилиты могут быть не готовы.

Подведение итогов

Я для себя сделал следующие выводы.

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

  • Можно не добавлять паразитные replace директивы в go.mod файлы для того, чтобы править одновременно несколько связанных проектов.

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

  • Проблема с несколькими go.mod в рамках одного репозитория с помощью Go Workspaces не решается никак.

Комментарии (9)


  1. shortcircuit
    17.02.2022 19:19

    Я правильно понимаю, что при запуске через go run ... отдельной утилиты с микроскопическим go.mod, лежащей в монорепе с гигантскими go.mod, всё равно скачаются все-все-все зависимости всех go.mod, которые есть в воркспейсе?


    1. Bozaro Автор
      17.02.2022 19:21

      Да. Именно так. Микроскопическая утилита запустится с обобщенным go.mod.

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


      1. shortcircuit
        17.02.2022 19:23

        А есть какой-то способ сказать, что "вот этот конкретный go.mod не относится к воркспейсу"?


        1. Bozaro Автор
          17.02.2022 19:31

          В go.work явно перечисляются используемые go.mod-ы.
          Если директория с go.mod там не указана, то GoLang будет её игнорировать и запустить из неё без ключа -workfile=off не получится. Будет ошибка вида:

          go: no modules were found in the current workspace; see 'go help work'


  1. gohrytt
    18.02.2022 00:23
    +7

    Звучит как какая-то бесполезная х***я. Простите.


    1. THQSql
      18.02.2022 10:16

      Я думаю это не для прода, а для удобства разработки. Иногда это пригождается для каких-то внутренних зависимостей, которые ещё не обновлены, например на новую спецификацию и ты им подсовываешь свежак.


  1. newprim
    18.02.2022 12:56

    Я конечно джун на Go, но за 5 месяцев работы ни разу вообще не пришлось даже зайти в go.mod, не то что что-то самому там писать. IDE и go mod tidy вроде справлялись сами неплохо.

    В общем, лично мне что-то не хватило понимания, что поменялось и зачем это вообще надо :)


  1. tendium
    19.02.2022 22:50
    +1

    Чую, это будет одна из самых непонятых фич go. На go пишу лет 5 уже, и — признаюсь — так и не осилил, для чего нужен этот go.work. До этого читал в оригинале, теперь тут. Хотя, может проблема во мне, а не в go.work.


    1. Bozaro Автор
      20.02.2022 03:05

      Я изначально то же ждал от go.work совершенно другого поведения: он меня интересовал в контексте работы с несколькими go.mod в рамках монорепозитория.

      Но он сделан для другого:

      • решение проблемы с IDE, которые используют gopls для интеграции, например VS Code (в этом случае при работе с несколькими go.mod надо открывать по одному экземпляру IDE на каждый go.mod);

      • решение проблемы работы с несколькими связанным go.mod в разных репозиториях.

      Если для Вас эти сценарии не актуальны, то эта фича действительно выглядит предельно странно.