GOPATH 汚染問題
(初出: そろそろ真面目に Golang 開発環境について考える — GOPATH 汚染問題 - Qiita,そろそろ真面目に Golang 開発環境について考える — Internal Packages と Vendoring - Qiita)
go get
コマンドはとても強力な機能で,私のように Windows と UNIX 系環境の間を渡り歩いてる身としては, make などの tool chain に大きく依存することなく, go get
コマンドだけで repository の fetch からビルド・インストールまで出来てしまうのは非常にありがたい1。
しかし, go get
コマンドは外部パッケージの revision 等をコントロールできず,常に repository の最新コードを取ってこようとする。
ひとつの環境でひとつのプロジェクトを管理していくのならこれでも何とかならないこともないが, GOPATH
内に複数のプロジェクトが同居している場合は同じ外部パッケージでもプロジェクトごとに異なるリビジョンを要求する可能性があり,管理が煩雑になってしまう。
しかも困ったことに GOPATH
環境変数は複数のプロジェクト管理を想定していないため,全てのパッケージをひとつのフォルダに入れようとする2 3。
【対策1】 プロジェクトごとに GOPATH を設定し直す
この問題に対する一番安直な答えは「プロジェクトごとに GOPATH
を設定し直す」である。例えば前回紹介した gb をビルドする場合は以下のようにする。
C:>mkdir C:\workspace\gb
C:>SET GOPATH=C:\workspace\gb
C:>go get -v github.com/constabulary/gb/...
github.com/constabulary/gb (download)
github.com/constabulary/gb/log
github.com/constabulary/gb
github.com/constabulary/gb/vendor
github.com/constabulary/gb/cmd
github.com/constabulary/gb/cmd/gb
github.com/constabulary/gb/cmd/gb-vendor
あとは GOPATH
直下の bin
フォルダにパスを通すか,パスの通ってるフォルダに実行ファイルをコピーすればよい。
実行履歴はバッチファイル(UNIX 系なら shell スクリプト)に保存しておけばいつでも復元できる。
毎回環境をセットアップしないといけないのは面倒だが,プロジェクト管理のためのツールも必要なく, Go コンパイラの標準機能のみで管理できる。 標準機能のみで管理できるというのは結構重要で,たとえば CI ツールを使っている場合は,設定を単純にできるので管理しやすいといえる。
UNIX 系の環境であれば direnv を使う手もある4。
direnv は cd
をフックし,ディレクトリごとに環境変数を書き換えることができる。
この機能を使ってプロジェクト・フォルダごとに GOPATH
を設定できる。
【対策2】 プロジェクト・ベースの管理ツールを使う
もうひとつは gb のようなプロジェクト・ベースでコード管理のできるツールを使う方法である。 gb については前回紹介したので,そちらを参照のこと。
gb で作った開発環境はフォルダ構成を丸ごと開発メンバに配布・同期することが可能になるため,複数人で環境を合わせることが容易になる。
【対策3】 Go 1.5 の Vendoring 機能を使う
Go 言語のバージョン 1.5 から Vendoring 機能が使えるようになった。
Vendoring 機能を使うと,外部パッケージを GOPATH
とは独立に管理できるようになる。
この機能を使うには環境変数 GO15VENDOREXPERIMENT
に 1 をセットする。
(追記 当初の予告通り Vendoring 機能は 1.6 から既定の機能になった。環境変数 GO15VENDOREXPERIMENT
をセットしなくても有効になる)
Vendoring 機能が有効な状態では vendor
フォルダが特別な意味を持つ。
たとえば mypackage
パッケージに対して mypackage/vendor/vpackage
と配置した場合, import "vpackage"
と記述すれば mypackage/vendor
フォルダ以下の vpackage
も探してくれる。
では,前回作ったコードを流用して確かめてみる。
C:\workspace\vdemo>SET GOPATH=C:\workspace\vdemo
C:\workspace\vdemo>SET GO15VENDOREXPERIMENT=1
C:\workspace\vdemo>tree /f .
C:\WORKSPACE\VDEMO
└─src
└─julian-day
julian-day.go
C:\workspace\vdemo>go build ./...
src\julian-day\julian-day.go:10:2: cannot find package "github.com/spiegel-im-spiegel/astrocalc/modjulian" in any of:
C:\Go\src\github.com\spiegel-im-spiegel\astrocalc\modjulian (from $GOROOT)
C:\workspace\vdemo\src\github.com\spiegel-im-spiegel\astrocalc\modjulian (from $GOPATH)
C:\workspace\vdemo>mkdir src\julian-day\vendor
C:\workspace\vdemo>tree /f .
C:\WORKSPACE\VDEMO
└─src
└─julian-day
│ julian-day.go
│
└─vendor
C:\workspace\vdemo>go build ./...
src\julian-day\julian-day.go:10:2: cannot find package "github.com/spiegel-im-spiegel/astrocalc/modjulian" in any of:
C:\workspace\vdemo\src\julian-day\vendor\github.com\spiegel-im-spiegel\astrocalc\modjulian (vendor tree)
C:\Go\src\github.com\spiegel-im-spiegel\astrocalc\modjulian (from $GOROOT)
C:\workspace\vdemo\src\github.com\spiegel-im-spiegel\astrocalc\modjulian (from $GOPATH)
vendor
フォルダを追加したことで Go コンパイラの挙動が変わったことがお分かりだろうか。
目的のパッケージを vendor tree → GOROOT
→ GOPATH
の順で捜索している。
では vendor
フォルダに外部パッケージを導入してビルドしてみよう。
C:\workspace\vdemo>pushd src\julian-day\vendor
C:\workspace\vdemo\src\julian-day\vendor>git clone https://github.com/spiegel-im-spiegel/astrocalc.git github.com/spiegel-im-spiegel/astrocalc
Cloning into 'github.com/spiegel-im-spiegel/astrocalc'...
remote: Counting objects: 43, done.
remote: Total 43 (delta 0), reused 0 (delta 0), pack-reused 43
Unpacking objects: 100% (43/43), done.
Checking connectivity... done.
C:\workspace\vdemo\src\julian-day\vendor>popd
C:\workspace\vdemo>tree /f .
C:\WORKSPACE\VDEMO
└─src
└─julian-day
│ julian-day.go
│
└─vendor
└─github.com
└─spiegel-im-spiegel
└─astrocalc
│ .editorconfig
│ .gitignore
│ .travis.yml
│ LICENSE
│ README.md
│
└─modjulian
example_test.go
LICENSE
modjulian.go
modjulian_test.go
C:\workspace\vdemo>go install -v ./...
julian-day/vendor/github.com/spiegel-im-spiegel/astrocalc/modjulian
julian-day
C:\workspace\vdemo>tree /f .
C:\WORKSPACE\VDEMO
├─bin
│ julian-day.exe
│
├─pkg
│ └─windows_amd64
│ └─julian-day
│ └─vendor
│ └─github.com
│ └─spiegel-im-spiegel
│ └─astrocalc
│ modjulian.a
│
└─src
└─julian-day
│ julian-day.go
│
└─vendor
└─github.com
└─spiegel-im-spiegel
└─astrocalc
│ .editorconfig
│ .gitignore
│ .travis.yml
│ LICENSE
│ README.md
│
└─modjulian
example_test.go
LICENSE
modjulian.go
modjulian_test.go
C:\workspace\vdemo>bin\julian-day.exe 2015 1 1
2015-01-01 00:00:00 +0000 UTC
MJD = 57023日
vendor
フォルダ以下にパッケージがフルパスで入ってしまうため階層が深くなりがちなのが「玉に瑕」だが,それ以外は特に問題はない。
あるいは vendor
フォルダ以下のパッケージは go get
の制約から外れているので,呼び出し側を
import (
"flag"
"fmt"
"os"
"strconv"
"time"
"astrocalc/modjulian"
)
として以下のフォルダ構成にする手もある5。
C:\workspace\vdemo>tree /f .
C:\WORKSPACE\VDEMO
└─src
└─julian-day
│ julian-day.go
│
└─vendor
└─astrocalc
│ .editorconfig
│ .gitignore
│ .travis.yml
│ LICENSE
│ README.md
│
└─modjulian
example_test.go
LICENSE
modjulian.go
modjulian_test.go
C:\workspace\vdemo>go install -v ./...
julian-day/vendor/astrocalc/modjulian
julian-day
C:\workspace\vdemo>bin\julian-day.exe 2015 1 1
2015-01-01 00:00:00 +0000 UTC
MJD = 57023日
注意が必要なのは, go get
は git の submodule を上手く扱えないため, vendor
フォルダ以下のパッケージを submodule として配置している場合はビルドに失敗することだ。
この場合は -d
オプションで go get
がビルドまで行わないようにし,手動で submodule の init
と update
を行う必要がある。
C:>go get -d project/...
C:>git submodule init
C:>git submodule update
C:>go install ./...
(「Glide で Vendoring」に続く)
ブックマーク
-
それでも git などのコード管理ツールへの依存はどうしても残るのだけれど。 ↩︎
-
具体的には
GOPATH
で列挙されるパスのリストのうち先頭のパスにインストールされる。 ↩︎ -
Go 言語の開発・管理主体は Google だが,こんな構成で Google は困らないのかと思ったのだが,実は Google は全てのコードを単一の repository で管理しているらしい。(参考: 20億行のコードを保存し、毎日4万5000回のコミットを発行しているGoogleが、単一のリポジトリで全社のソースコードを管理している理由) ↩︎
-
パッケージのパスが変わるとテストが通らなくなる場合があるので注意。 ↩︎