sgykfjsm.github.com

ベンダリングのためのgomvpkgとgbについて考えてみる

GO言語には多言語にあるようなパッケージマネジメントの仕組みが(公式には)サポートされていない。GOv1.5では実験的に導入されているが、例えばJavaで言うmaven、Pythonで言うpipなどのような共通として用いられているツールなどは無く、基本的には以下の3つのやり方が主流だと思われる。

  • 依存するパッケージを自分のリポジトリに取り込むVendoring
  • 3rd partyツールによるRevison指定などといった擬似的なパッケージマネジメント
  • そもそもパッケージマネジメントをしない

自分の開発では、業務も含めて、あまり外部パッケージに依存するようなことはあまり無いが、もしやるなら1つ目のVendoringになると思う。で、今回はVendoringを行なう際に便利だと思われる2つのツール、gomvpkggbを試してみる。

gomvpkgの概要

gomvpkgは公式に提供されているツールであり、その名称から想像できるように、パッケージを移動させるツールだ。gomvpkgの実体はgolang.org/x/tools/refactor/renameによるリファクタリングである。

使い方はシンプルで以下の様に実行すれば良い。詳細はgomvpkg -helpで確認されたし。

1
gomvpkg -from ${original_package_name} -to ${post_moved_package_name}

このコマンドを実行する場合、注意することは以下の通り。

  • -from-toの引数は物理的なディレクトリパスではなく、ソースコードファイルに記述するimportにつづくパッケージ名であること
  • 移動後のパッケージが配置されるディレクトリは事前に存在してはいけない
  • 移動後のパッケージが配置されるディレクトリの親ディレクトリが存在していること

普通のmvコマンドと同じように使うとエラーが頻発して困るので注意されたし。

gomvpkgの難点

gomvpkg$GOPATH以下をスキャンし、移動対象のパッケージの依存関係を解決し、指定の位置にパッケージを移動する(コピーではない)。また、移動対象のパッケージをimportしているファイルがあれば、import pathを書き換えてくれる。

これだけを述べるとなんか便利そうだが、実際は意外とクセモノだったりする。まず、”依存性を解決する”という点について。実際にgomvpkgを実行すると、以下のようなエラーが出る場合がある。

1
2
3
4
5
/Users/sgyk/local/script/golang/src/github.com/Sirupsen/logrus/examples/hook/hook.go:5:2: could not import gopkg.in/gemnasium/logrus-airbrake-hook.v2 (cannot find package "gopkg.in/gemnasium/logrus-airbrake-hook.v2" in any of:
        /usr/local/opt/go/libexec/src/gopkg.in/gemnasium/logrus-airbrake-hook.v2 (from $GOROOT)
        /Users/sgyk/local/script/golang/src/gopkg.in/gemnasium/logrus-airbrake-hook.v2 (from $GOPATH))
/Users/sgyk/local/script/golang/src/github.com/Sirupsen/logrus/examples/hook/hook.go:12:16: undeclared name: airbrake
gomvpkg: couldn't load packages due to errors: github.com/Sirupsen/logrus/examples/hook.

上記は簡単に言うと、移動対象のパッケージにおいてgopkg.in/gemnasium/logrus-airbrake-hook.v2が参照できないからgomvpkgに失敗したということ。なので、これを解決するにはgo getで対象のパッケージを取得してこなければならない。

次に”$GOPATH以下をスキャン”という点と”import pathを書き換える”という点について。おそらく、gomvpkgを実行する場合、対象プロジェクト(言い換えると移動先のプロジェクト)内のファイルのimportだけを書き換えることを期待しているんじゃないかと思う。少なくとも自分はそうだった。しかし、実際には$GOPATH内すべてをスキャンするため、対象となるimport pathはすべて書き換えられてしまう。実際にgomvpkgを実行すると、以下の様な出力をみるはず。

1
2
3
4
Renamed 6 occurrences in 1 file in 1 package.
Renamed 11 occurrences in 1 file in 1 package.
Renamed 16 occurrences in 1 file in 1 package.
...

これは実際のところ、かなり怖い。なぜならどのプロジェクトのファイルが書き換えられてしまったのか、詳細が把握できないからだ。そのため、意図しないファイル改変を行ってしまう可能性が高い。ちなみに運が悪い(?)と、上記の出力がなされず、別の出力がなされてしまう場合がある。

まぁ冷静に考えていると、どのプロジェクトのファイルだけを改変するかという指定はできないので、この挙動は仕方ないのかもしれないが、危ういことには変わりない。

gomvpkgはGOの公式ドキュメントでベンダリングにおける有用なツールとして紹介されているが、実際はかなり危険なので使う際は注意が必要だと思う。

gbについて

gomvpkgがリファクタリングのためのツールであるのに対し、gbはベンダリングのためのツールであり、公式には”A project based build tool for the Go programming language.“と紹介されている。

gbについて簡単にいうと、既存のGOプロジェクトとは隔離された世界でベンダリングを行なうためのツールと言える。言い換えると、gbはプロジェクトベースで依存パッケージをベンダリングする。また、gbは既存のgo toolsを置き換えるものではない。

特徴的な点としては、$GOPATH配下にプロジェクトを作ってはいけないというルールがあり、関連パッケージはgo getではなくgit cloneで取得しなければならない点だ。この特徴については捉えようによっては良い点がある。たとえば、GO初心者が参画する場合、$GOPATHにハマることもないし、go getについて知る必要がないため、学習コストを一定度下げることができる(といっても大したコストではないが)。しかし、逆に言えば、gbのお作法を学ぶ必要がある。

gbのお作法

gbはプロジェクトベースツールなので、プロジェクトごとにディレクトリを設けなければならない。また、srcディレクトリやbinディレクトリはプロジェクトのルートディレクトリの直下に設けなければならない。依存するパッケージはプロジェクトのルートディレクトリ直下にvender/src以下に格納しなければならない。

基本的には上記のディレクトリ配置のお作法を守っていれば、通常のGO開発と同じようにすすめることが出来るはず。

gbの難点

gb$GOPATHとは違う世界で開発しなければならないが、そのためにエディタの支援を受けられない場合がある。

例えば、vimではソースコード中でimportされているパッケージからコマンド補完をすることがプラグインによって可能だが、プロジェクトルートに格納されているが$GOPATH以下にはないパッケージの場合、エディタがエラーとする場合がある。その場合は問題となっているパッケージをgo getすれば解決するが、gbでのgit clonego getでの二度手間が発生してしまう。


以上、ベンダリングのためのツールとしてgomvpkggbの両方を見たが、どちらもそれなりに難点があり、どちらも他人に進めることはできないなーと個人的には思う。とはいえ、ある程度の大規模な開発を行なうのであれば、gbが良いかな―とは思う。ただ、なんとなくGの世界では外部パッケージに頼らないようにすべきみたいな印象を持っているので、GO開発者はパッケージ管理にあんまり興味ないのかなーと思ったり。一応、v1.5からは実験的にgo toolでのベンダリングサポートが導入されているようだけど、パッケージ管理という面から考えると(例えばバージョンのアップデートとダウングレードの容易さなど)、まだまだ貧弱だな―とおもうので、なるべく外部パッケージに頼らないほうが良いのかなと思う。