この記事はQiitaで公開されていました
問題
Goは、$GOPATH/src/ 以下にパッケージのソースコードを置くルール。自分で書いたコードも例外ではないので、以下の例では、自分で書くコードを $GOPATH/src/example.com/app に置いている。
$GOPATH/ └ ─ /src ├ ─ /example.com │ └ ─ /corp │ └ ─ /app │ └ ─ /rpc │ ├ ─ /user │ ├ ─ /blob │ └ ─ /log └ ─ /example.org └ ─ /app └ ─ /rpc └ ─ /main.go
Goでは、型や変数をpackagename.TypeName
のように書くので、UserMessage
という型を、パッケージを分けてuser.Message
とする場合がある。だけども上の通り、別パッケージにするにはディレクトリを分ける必要があるので、Protocol Buffersコンパイラ(protoc
)で生成するコードも例外なく、パッケージを分けるなら別の階層に置かなければならない。
このような、 Protocol Buffers(.proto)ファイルがサブディレクトリの.protoファイルを参照する 場合、出力先やオプションを適切に使わないと手間がかかったりビルドできなかったりする。
この記事で想定するProtocol Buffers定義の階層
$GOPATH/src/example.com/app/rpc 以下に .proto ファイルを全て置いた状態を想定する。
$GOPATH/src/example.com/ └ ─ /app └ ─ /rpc ├ ─ /user │ └ ─ /user.proto ├ ─ /blob │ └ ─ /blob.proto ├ ─ /log │ └ ─ /log.proto └ ─ /main.proto
最終的にどうすればいいか
protoファイルの書き方
参照されるだけの .proto ファイルは、option go_package
で正しいimport pathを設定しておく。以下は user.proto ファイルの例。
package user; option go_package = "example.com/app/rpc/user";
参照する側の .proto ファイルは、上記に加えて、import
の書き方をファイル階層に合わせる。
package rpc; option go_package = "example.com/app/rpc"; import "user/user.proto";
protoc-gen-goのオプション
Goのコードを生成する場合は以下のコマンドで行う。
$ protoc -Irpc --go_out=plugins=grpc:$GOPATH/src rpc/main.proto $ protoc -Irpc --go_out=plugins=grpc:$GOPATH/src rpc/user/user.proto
-I
オプションで.protoファイルのimport
で参照するルートディレクトリを指定する--go_out=
オプションで出力先を変更する
Ruby等は、そのまま実行すれば良さそう。
$ plugin="protoc-gen-grpc=$(which grpc_tools_ruby_protoc_plugin)" $ protoc -Irpc --ruby_out=. --grpc_out=. --plugin=$plugin rpc/main.proto $ protoc -Irpc --ruby_out=. --grpc_out=. --plugin=$plugin rpc/user/user.proto
この書き方で使い回しができる .proto になる。
色々な失敗案
参考のため、試行錯誤した結果と使えない理由をまとめた。
デフォルトの動作
別ディレクトリのファイルを参照する .proto ファイルは、特にオプションを入れない場合、生成された.pb.goファイルのimport
パスが $GOPATH/src/ からになっていない。例えば main.proto ファイルで
syntax = "proto3"; package rpc; import "user/user.proto";
と書くと、生成されたGoのコードは
package rpc import "user"
のようなimport
になってしまってパッケージを読み込めない。
go_packageオプション
この問題に対応するため、.proto ファイルのオプションでoption go_package
があるけど、これを書いてしまうと、Goソースコードの生成先に、さらにパッケージ階層が作られてしまう。
参照される側:
syntax = "proto3"; package user; option go_package = "example.com/app/rpc/user";
参照する側:
syntax = "proto3"; package rpc; option go_package = "example.com/app/rpc"; import "user/user.proto";
生成コマンド:
$ protoc -Irpc --go_out=plugins=grpc:. rpc/main.proto $ protoc -Irpc --go_out=plugins=grpc:. rpc/user/user.proto
結果:
$GOPATH/ └ ─ /src └ ─ /example.com └ ─ /app ├ ─ /example.com <- 余計な階層が作られる │ └ ─ /app │ └ ─ /rpc │ ├ ─ /user │ │ └ ─ /user.pb.go │ └ ─ /main.pb.go └ ─ /rpc ├ ─ /user │ └ ─ /user.proto ├ ─ /blob │ └ ─ /blob.proto ├ ─ /log │ └ ─ /log.proto └ ─ /main.proto
protoc-gen-goのオプションを試す
protoc
の--go_out=
オプション経由で、カンマ区切り文字列を指定すれば、protoc-gen-go
へオプションを与えることができるがどれも微妙。
import_prefix
このオプションは、指定した文字列が標準パッケージ以外全ての先頭に付く。protoc
で生成したパッケージだけに付くなら問題ないけれど、github.com/golang/protobuf/proto
の頭にもついてしまうのでよくない。
import_path
これは .proto ファイルにoption go_package
がない場合のみ、コマンドラインから与えることができるオプション。なので動作としてはoption go_package
と何も違いがない。
vendorに入れてみる
--go_out=
で出力先ディレクトリが決められるので、option go_package
で階層が掘られてしまうならvendor以下に入れてしまおう案。
基本的にはうまく動いたけれど、dep
で管理していないパッケージがvendor以下に入ってしまうため、dep init
やdep ensure
がエラーになってしまう。現状では無視することもできなさそうなので、使えない。