company_banner

Правда ли, что GOPATH и GOROOT больше не нужны?

    Так повелось, что разработчики, еще только начинающие знакомиться с Go, часто сталкиваются с проблемой выбора рабочей директории для Go-проектов. Вот и в чате конференции GolangConf тоже задавался этот вопрос. Новые гоферы часто пугают друг друга словами GOPATH и GOROOT. Однако, в руководствах по быстрому старту с текущей версией Go (1.13) упоминания эти двух «страшных» слов вообще нет.


    Давайте посмотрим, почему так. Для чистоты эксперимента я развернула свежую Ubuntu на виртуальной машине и установила Go по инструкции из Wiki:


    sudo add-apt-repository ppa:longsleep/golang-backports
    sudo apt-get update
    sudo apt-get install golang-go

    Go 1.13 установлен и готов к использованию:


    $ go version
    go version go1.13 linux/amd64
    $ which go
    /usr/bin/go
    $ whereis go
    go: /usr/bin/go /usr/lib/go /usr/share/go /usr/share/man/man1/go.1.gz

    GOROOT


    Про GOROOT уже было прекрасно написано в статье 2015-года, и эта информация до сих пор актуальна.


    Забавно, что среди списка директорий, выданных последней командой (whereis go), GOROOT на самом деле нет:


    $ go env GOROOT
    /usr/lib/go-1.13

    Итак, например, если для IDE мне понадобится указать путь к файлам стандартной библиотеки Go, я укажу /usr/lib/go-1.13. Пожалуй, на этом сценарии использования GOROOT в повседневной жизни заканчиваются.


    GOPATH и модули


    Казалось бы, в этом месте надо кинуться устанавливать GOPATH, но я не буду этого делать. На самом деле GOPATH уже и так задан:


    $ go env GOPATH
    /home/elena/go

    Меня устраивает вариант с GOPATH в ~/go, а значит, менять его я не буду.


    Я сразу создам директорию для своего первого проекта на Go. Это можно сделать в любом месте, например, прямо в домашнем каталоге. Также я сразу начну работать с инструментом Go Modules:


    $ mkdir ~/hello
    $ go mod init github.com/rumyantseva/hello
    go: creating new go.mod: module github.com/rumyantseva/hello

    Для команды go mod init я указала уникальный путь модуля моего проекта. По этому пути прокси или другой инструмент в случае необходимости сможет найти файлы моего проекта.


    После вызова команды go mod init в моем домашнем каталоге появилась директория go:


    $ tree ~/go
    /home/elena/go
    └── pkg
        └── mod
            └── cache
                └── lock
    
    3 directories, 1 file

    При этом lock-файл (в самом низу дерева), пока пуст.


    В каталоге ~/hello появился файл go.mod со следующим содержанием:


    module github.com/rumyantseva/hello
    
    go 1.13

    Именно в go.mod впоследствии будет храниться вся информация о зависимостях моего модуля.


    Давайте теперь напишем приложение, использующее внешнюю зависимость. В директории ~/hello я создаю файл main.go и пишу в него такой код:


    package main
    
    import (
        "github.com/sirupsen/logrus"
    )
    
    func main() {
        logrus.Info("Hello, world!")
    }

    Конечно, в реальной жизни для написания "Hello, world!" можно обойтись и без logrus, но в этом примере эта библиотека поможет нам узнать, где хранятся файлы внешних зависимостей.


    Запускаю приложение самым простым способом:


    $ go run main.go
    go: finding github.com/sirupsen/logrus v1.4.2
    go: downloading github.com/sirupsen/logrus v1.4.2
    go: extracting github.com/sirupsen/logrus v1.4.2
    go: downloading golang.org/x/sys v0.0.0-20190422165155-953cdadca894
    go: extracting golang.org/x/sys v0.0.0-20190422165155-953cdadca894
    go: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894
    INFO[0000] Hello, world!  

    Перед тем, как приложение было собрано и запущено, сработал инструмент go mod. Он определил мою внешнюю зависимость github.com/sirupsen/logrus, взял ее последнюю на данный момент версию v1.4.2 и отправился за транзитивными зависимостями.


    В файл go.mod добавилась строка с описанием зависимости от logrus:


    module github.com/rumyantseva/hello
    
    go 1.13
    
    require github.com/sirupsen/logrus v1.4.2 // indirect

    Также появился файл go.sum, в котором помимо хэша зависимости logrus хранится информация о хэшах транзитивных зависимостей:


    github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
    github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
    github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
    github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
    github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
    github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
    golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
    golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

    Где же сам код зависимостей? Его можно найти в ~/go/pkg/mod. Также, в ~/go/pkg будут храниться контрольные суммы и другая служебная информация для работы с зависимостями.


    Если вы уже сталкивались с инструментом go get, знаете, что он при вытягивании зависимостей фактически клонирует репозитории (например, в случае git с помощью git clone). Но go mod работает не так. Для go mod основная единица кода — модуль. Модули представляют собой архивы. Во время работы с зависимостями go mod явно (если вы вызывали команду go mod download) или неявно (если вы запустили компиляцию приложения) скачивает и распаковывает архивы через GOPROXY. Давайте посмотрим, как прокси задан в Go 1.13 по умолчанию:


    $ go env GOPROXY
    https://proxy.golang.org,direct

    Итак, в качестве прокси при сборке моего "Hello, World!" использовался proxy.golang.org. Конечно, эту переменную можно изменить, выбрав другое хранилище модулей. Например, можно развернуть свой собственный внутренний прокси компании, где будут храниться, в том числе, внутренние библиотеки, код которых не публиковался в open source.


    В общем, если я начинаю новый проект и не против использования Go Modules, мне можно ничего не знать о GOPATH. Go самостоятельно создаст директорию ~/go тогда, когда это будет нужно.


    Когда нужен GOPATH?


    Если вы принципиально не используете Go Modules (например, в легаси-проекте), уйти от более явной работы с GOPATH может быть не так просто.


    Чтобы посмотреть, что будет с моим проектом, если я решила не использовать go mod, удаляю файлы ~/hello/go.mod и ~/hello/go.sum. Также я удалю и ~/go, чтобы вернуться к тому состоянию системы, которое у меня было в самом начале:


    rm -rf ~/go ~/hello/go.mod ~/hello/go.sum

    В директории ~/hello остается только файл main.go. Что теперь произойдет, если я попробую запустить его с помощью go run?


    $ go run main.go
    main.go:4:2: cannot find package "github.com/sirupsen/logrus" in any of:
        /usr/lib/go-1.13/src/github.com/sirupsen/logrus (from $GOROOT)
        /home/elena/go/src/github.com/sirupsen/logrus (from $GOPATH)

    Вот они, эти страшные GOROOT и GOPATH :)


    Для того, чтобы скомпилировать приложение, мне надо подянуть зависимость в GOPATH. Делаю это с помощью старого доброго go get:


    $ go get -v github.com/sirupsen/logrus
    github.com/sirupsen/logrus (download)
    created GOPATH=/home/elena/go; see 'go help gopath'
    get "golang.org/x/sys/unix": found meta tag get.metaImport{Prefix:"golang.org/x/sys", VCS:"git", RepoRoot:"https://go.googlesource.com/sys"} at //golang.org/x/sys/unix?go-get=1
    get "golang.org/x/sys/unix": verifying non-authoritative meta tag
    golang.org/x/sys (download)
    golang.org/x/sys/unix
    github.com/sirupsen/logrus

    Что произошло? Первым делом, go get создал директорию ~/go (ту, что указана в качестве GOPATH). Затем начался процесс клонирования репозиториев с зависимостями. Забавно, что клонирование репозиториев выглядит заметно медленнее, чем вариант, когда мы использовали go mod для скачивания и распаковки модулей. Тем не менее, код зависимостей теперь можно найти внутри ~/go/src/.


    Кстати, на моей чистой установке Ubuntu до сих пор не было клиента git и, для того чтобы go get сработал, пришлось его установить.


    Запускаю приложение:


    $ go run main.go 
    INFO[0000] Hello, world!

    Работает!


    Вот только на уровне приложения я теперь не отслеживаю версии внешних зависимостей. Что если из-за уязвимости в какой-то момент в репозитории github.com/sirupsen/logrus окажется не ожидаемый мной логгер, а какой-нибудь вредоносный код? Рано или поздно, мне всё-таки понадобится инструмент для работы с зависимостями, и если Go Modules по какой-то причине не подходит, придётся искать что-то другое...


    Заключение


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


    Если вы начинаете новый проект, попробуйте Go Modules! Возвращаться к старому подходу к работе с зависимостями имеет смысл, только если что-то пошло не так. Кстати, если вы предпочитаете хранить все зависимости внутри проекта, Go Modules поддерживает режим vendor.


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


    Кстати, 7-го октября на конференции GolangConf в качестве одной из специальных активностей, мы планируем экспертную зону, где любой желающий сможет задать любые вопросы по Go членам программного комитета конференции и энтузиастам российского Go-сообщества. Установить Go? Разобраться с зависимостями? Написать микросервис? Это к нам!

    Конференции Олега Бунина (Онтико)
    869.96
    Конференции Олега Бунина
    Share post

    Comments 17

      0
      module github.com/rumyantseva/hello
      go 1.13
      require github.com/sirupsen/logrus v1.4.2 // indirect


      Ну штош, это уже гораздо больше похоже на рубевый Gemfile (и Gemfile.lock), значит верной дорогой идете! Еще лет пять эволюции…

      А вообще шутки шутками, но почему файлы go.mod,go.sum не были названы в стандартном ключе типа Modfile и Modfile.lock? Опять какой то go-велосипед
      +1
      Как явно можно указывать версию модуля?
        +1

        В файле go.mod. Если брать пример из статьи, то в строке


        require github.com/sirupsen/logrus v1.4.2

        вместо v1.4.2 можно подставить любую другую желаемую версию. После этого можно вызвать вручную go mod tidy и go mod download, чтобы обновить транзитивные зависимости и загрузить модули согласно этим обновлениям. Также можно просто перекомпилировать приложение (команды go mod при этом будут вызваны автоматически).


        Соотственно, если вы разрабатываете бибилиотеку и выкладываете ее, например, на Github, имеет смысл использовать семантическое версионирование с тегами вида v1.2.3.


        Если семантического версионирования нет, то версии будут выглядеть а ля v0.0.0-20190422165155-953cdadca894 (что-то вроде v0.0.0-дата-хэш).


        Или вот например, с тем же logrus'ом: там сейчас есть коммиты в мастер, но тега v1.4.3 пока нет. И если посмотреть версии этой библиотеки на gocenter, помимо v1.4.2 там будут варианты а ля v1.4.3-0.20190807103436-de736cf91b92.

          +1
          Спасибо!
          Есть ещё вопрос.
          В примере автоматически подставилась последняя версия. Получается что привязка будет именно к ней.
          Есть ли какая-то возможность указать dev версию или master ветку?
          Есть ли возможность указывать последнюю минорную или мажорную версию?
            +1

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


            Тогда ваш процесс сборки приложения будет всегда из двух шагов состоять:


            1. Вызывать go get github.com/sirupsen/logrus@master — при этом файл go.mod обновится автоматически
            2. Вызвать собственно компиляцию.

            С мажорными версиями тоже отдельная история: мажорная версия должна быть указана в пути модуля.

              0
              Спасибо за развернутые ответы. К сожалению карма не позволяет поблагодарить вас с помощью плюсика)
        0
        Благодарю. Написано кратко и по делу.
          0

          Вроде без GOPATH нормально не работает расширение VS Code для работы с Go. Более того, проект должен быть внутри папки, куда указывает переменная, чтобы правильно подсвечивался синтаксис.

            0

            Тоже сталкивалась с проблемами с VS Code, да. Обещают, что если включить gopls, то более или менее должно заработать. Но как-то gopls у меня не прижился пока.

              0

              Странно, я когда гуглил проблему, как раз попадались запросы привести лог gopls, и в целом у меня в логе ее вывод был, значит работало в каком-то виде, но эффекта не было.

            +1

            Классно написано, спасибо!


            Если вы принципиально не используете Go Modules (например, в легаси-проекте)

            Кстати, я стараюсь не использовать модули для open-source проектов – это стимулирует всегда держать ухо востро, чтобы они работали с последними версиями (через CI или пулл-реквесты "не билдится", гг). И вообще GOPATH это ️

              0

              Спасибо, Иван! Полезный кейс про open-source, да!

              0
              А потом внезапно оказывается что самый известный проект на Go (Docker) не смог в Go Modules :(
                +1
                Если бы это было единственной проблемой Докера… :)
                0
                Кто нибудь может подсказать — а как следует поступить, если тебе нужно срочно внести изменения во внешний (vendor) код, и ты не чаешь дождаться, пока основные разработчики этого кода пофиксят ошибку и делаешь это сам?

                При этом не хотелось бы создавать свой отдельный форк на github.

                Примером такой ситуации может быть:

                Когда нам нужно подменить этот внешний код не для своего собственного непосредственного использования, а для «обмана» другой внешней зависимости.

                Вроде в документации на Go Modules написано как при сохранении того же пути к внешнему коду в import фактически подставлять свой исправленный код. Но до меня все никак не
                дойдет как именно это сделать.
                  0

                  Не пробовала так делать, но первое, что приходит в голову — инструкция replace. Из документации мне кажется, что если в vendor-mode работать, то результат должен быть как раз такой, как вы описываете.

                Only users with full accounts can post comments. Log in, please.