Goのパッケージ管理ツールgodepの紹介と、僕がハマった罠
Goのプロジェクトの依存パッケージをどうするかという問題があります。
package myapp import ( "gopkg.in/hoge/fuga" "myserver.com/foo/bar" "buz" ) func main() { // ... }
などのアプリケーションをサーバにデプロイするとき、fugaパッケージやbar、buzパッケージをどこから調達するかです。
たとえばNodeJSならpackage.jsonとnpm installが、RubyならGemfileとbundle installがこのような依存パッケージを調達してくれます。
godep
Goにおける依存パッケージの解決方法のひとつに、godepというものがあります。
Goのパッケージマネージャの中ではいちばんメジャーなもののようです。(mattnさんのgomやrosylillyさんのgondlerもわたし気になります)
使い方
1. 依存関係を保存する
デプロイしたいアプリケーションのルートディレクトリでgodep saveを実行します*1。
This will save a list of dependencies to the file Godeps/Godeps.json, and copy their source code into Godeps/_workspace.
これは、アプリケーションが参照するすべてのパッケージのソースコードを、ローカルマシンの$GOPATH/srcから探してきて、アプリケーションのルート直下のGodeps/_workspaceにコピーします*2。これと同時に、その依存パッケージのバージョンを含めたリスト情報をGodeps/Godeps.jsonに記録します。
2. 依存関係をリポジトリからインストールする
依存パッケージをgo getしていないマシンにおいて、godep resotoreを実行します。
The godep restore command is the opposite of godep save. It will install the package versions specified in Godeps/Godeps.json to your GOPATH.
Godeps.jsonの記述にしたがって、リモートのリポジトリから依存パッケージをインストール(go get)しようとします。しかしながらこれは、依存パッケージのリポジトリがgopkg.inやgithub.comなどのようにパブリックに(あるいはインターナルに到達可能に)ホスティングされている場合にのみ効果を発揮します。
3. そうではなく、saveしたソースコードから実行する
Goのバイナリはgo build``go install、あるいはgo runと同時に、生成され、それが実行されますが、普通は依存パッケージをローカルの$GOAPTHから解決しようとします. これらのコマンドの前にgodep go runなどとgodepをつけることで、これをGodeps/_workspaceから解決しようとします。
ためしに、(godep saveしてある前提で)
% cd myapp % go run main.go This is main. % rm -rf $GOPATH/src/buz #依存パッケージを削除しちゃう % go run main.go main.go:3:8: cannot find package "buz" in any of: /Users/otiai10/.go/src/buz (from $GOROOT) /Users/otiai10/proj/go/src/buz (from $GOPATH) # 叱られるが % godep go run main.go This is buz.Buz (method of imported package)
このように、godepプレフィックスをつけてgo * コマンドを実行すると、依存パッケージをGodeps/_workspaceから解決してビルドをつくる。
デプロイにあたって
したがって、サーバへデプロイする場合に、ローカルでアプリケーションの依存をgodep saveでアプリケーションのGodepsディレクトリに保存してしまい、デプロイスクリプトでこのアプリケーションを起動するときにgodep go run main.go(あるいはgodep go install myapp; myapp)とすればアプリケーションの依存は解決される(というか、一緒にデプロイしてしまうかんじ)ことになります。
パッケージの更新も保存しておく
とのことから、最も単純な開発フローにおいては依存パッケージ側で変更があればアプリケーションでそれをsaveする(godep update foo)をする必要があります。
ためしに、buzパッケージの一部分を編集して以下のようにしてコミットしてみました。
func Buz() {
- fmt.Println("This is buz.Buz (method of imported package)")
+ fmt.Println("This is buz.Buz version 2!!")
}
そしてアプリケーションでgodep update buzをするとGodeps/_workspaceのソースコードも更新されます。そしてgodep go run main.goとすると、
This is buz.Buz version 2!!
となるはずです。
僕がハマった罠
さらに、buzパッケージをv3に... する前に、godep go install ./ をしてみます。
すると実は、Godep/_workspace/pkg以下にbuzパッケージのv2のビルドが生成されます。実はこれがくせ者でした。
あとは普通にbuzパッケージをv3にします。
func Buz() {
- fmt.Println("This is buz.Buz version 2!!")
+ fmt.Println("v3だよ! This is buz.Buz")
}
で、godep update buzすると、Godeps/_workspace/src以下のbuzパッケージのソースもv3になったので、意気揚々とgodep go run main.goすると、、
This is buz.Buz version 2!!
!?
丁寧に書くのも飽きたので結論
godep saveやgodep updateは、godepが依存パッケージのソースコードをGodeps/_workspace/srcに保存するgodep go installすると、godepが依存パッケージのビルドをGodeps/_workspace/pkgに保存する- 以降、
godep go runやgodep go installなどのビルドを伴う処理においてGodeps/_workspace/pkg以下にビルド済みのものがあればそちらを使って依存を解決する godep updateではGodeps/_workspace/pkg以下のビルドは更新されないので、src以下が新しくなってるのにパッケージが古いままのような挙動を示す
godepって、なんか挙動にモットーのようなものを感じる*3ので、なんらかの意図があってこうなってるんだと思いますが、まだそういった記述にたどり着けてないです。
現場からは以上です
DRY
追記 2015/02/12
なんかknown issueだった模様。
*1:この際、"directory "xxx" is not using a known version control system"などと怒られるのは、このアプリケーションがgitなどによって管理されていない(.gitが無い)場合です
*2:この際、"godep: cannot find package "foo" in any of"などと怒られるのは、このマシンの$GOPATHにfooパッケージが無いからです
*3:たとえば、godep restoreしたときは必ずリモートレポジトリをgo getしに行くのであって、ローカルのGodeps/_workspace/から$GOPATHへrestoreするようなことはない、とか