Carpe Diem

備忘録

protocの使い方

概要

christina04.hatenablog.com

で紹介したprotoeasyがリンクごと消えて使えなくなったので、protocの使い方を整理するために書きます。

環境

  • libprotoc 3.9.1

使い方

Goを例に基本的な使い方を説明します。

SRC_DIRディレクトリに.protoファイルがある場合、基本的には以下のようにコンパイルを実行します。

$ protoc -I./SRC_DIR --go_out=./OUT_DIR ./SRC_DIR/*.proto

各オプションをそれぞれ説明していきます。

-Iオプション

-Iオプションは.protoファイルでimportするファイルのPATHです。 --proto_pathの短縮形です。

機能

-Iオプションを指定すると、

  • .protoファイルでのimportを使える
  • import pathを省略できる

と言ったことが可能になります。

.protoファイルでのimportを使える

例えば

path/
├── device.proto
└── person.proto

という配置で、以下のように片方が別のprotoに依存している場合

syntax = "proto3";
package path;

import 'device.proto';  // 依存

message Person {
  string name   = 1;
  int32  id     = 2;
  string email  = 3;
  Device device = 4;
}

-IでPATHを通さないとエラーが発生します。

$ protoc --go_out=./path ./path/*.proto
device.proto: File not found.
path/person.proto:4:1: Import "device.proto" was not found or had errors.
path/person.proto:10:3: "path.Device" seems to be defined in "path/device.proto", which is not imported by "path/person.proto".  To use it here, please add the necessary import.

PATHを通すとコンパイルできます。

$ protoc -I./path --go_out=./path ./path/*.proto

import pathを省略できる

外部ライブラリのprotoを使う際、

-I${GOPATH}/src

だけならimportしたいxxx.protoでは

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

と冗長に書く必要がありますが、

-I${GOPATH}/src \
-I${GOPATH}/src/github.com/gogo/protobuf

のようにPATHに追加してあげれば

import "gogoproto/gogo.proto";

とだけ書けば良くなります。

書き方

様々な指定方法ができるので参考にいくつか例を示します。

説明
-I. カレントディレクトリを指定
-I./proto ./protoというディレクトリを指定
-I=./proto =をつけてもOK
-I${GOPATH}/src GOPATHでGoのライブラリを使用
-I.:./proto:${GOPATH}/src :で複数指定も可能

xxx_outオプション

--xxx_outプラグイン&出力先の指定です。proto-gen-xxxというプラグイン名だとxxx_outという書き方になります。
具体的な例だと以下です。

アーキテクチャ

protocol buffersは多言語をサポートしており、その仕組みはpluginによる拡張機構です。
以下がそのアーキテクチャ図です。pluginバイナリを用意して以下のように実行されます。

f:id:quoll00:20190902215630p:plain

ref: protocプラグインの書き方 - Qiita

書き方

Go(=protoc-gen-go)であればこれまで通り

$ protoc -I./SRC_DIR --go_out=OUT_DIR ./SRC_DIR/*.proto

ですし、Goで有名なgogoプラグイン(=protoc-gen-gogo)であれば

$ protoc -I./SRC_DIR --gogo_out=OUT_DIR ./SRC_DIR/*.proto

と書きます。

プラグインに渡す引数

プラグインによっては様々な機能を提供しており、それを使うために引数を渡す設定も可能です。

一般的な書き方は

$ protoc \
  --foo_out=arg1=aaaaaa,arg2,arg3=cccccc:OUT_DIR \
  input.proto

で、

  • 引数の指定はカンマ区切りで繋げる
  • ブール値はそのオプション名だけ書く
  • それ以外のオプションはname=value形式で指定する

というルールがあります。

具体例1: gRPC

例えば以下のようにgRPCのprotoがあるとします。

syntax = "proto3";
package helloworld;

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

protoc-gen-goはgRPCをサポートしているので、以下のように引数を渡すことができます。

$ protoc \
  -I./plugin \
  --go_out=plugins=grpc:./plugin \
  ./plugin/*.proto

具体例2: grpc-gateway

protoc-gen-grpc-gatewayプラグインだと、

$ protoc-gen-grpc-gateway --help
Usage of protoc-gen-grpc-gateway:
  -allow_colon_final_segments
        determines whether colons are permitted in the final segment of a path
  -allow_delete_body
        unless set, HTTP DELETE methods may not have a body
  -allow_patch_feature
        determines whether to use PATCH feature involving update masks (using google.protobuf.FieldMask). (default true)
  -allow_repeated_fields_in_body body
        allows to use repeated field in body and `response_body` field of `google.api.http` annotation option
  -alsologtostderr
        log to standard error as well as files
  -grpc_api_configuration string
        path to gRPC API Configuration in YAML format
  -import_path string
        used as the package if no input files declare go_package. If it contains slashes, everything up to the rightmost slash is ignored.
  -import_prefix string
        prefix to be added to go package paths for imported proto files
  -log_backtrace_at value
        when logging hits line file:N, emit a stack trace
  -log_dir string
        If non-empty, write log files in this directory
  -logtostderr
        log to standard error instead of files
  -paths string
        specifies how the paths of generated files are structured

このように多くのオプションを用意しているので

$ protoc \
  -I./proto \
  --grpc-gateway_out=logtostderr=true:./proto \
  ./proto/*.proto

こんなふうに指定します。

複数使う

protoc-gen-goプラグインはGoのprotoメッセージファイルとgrpcのファイルを生成し、protoc-gen-grpc-gatewayはgrpc-gateway用のファイルを生成するのでお互いが上書きするようなことがありません。
そういったプラグイン同士であれば

$ protoc \
-I=./proto \
-I=${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:./proto \
--grpc-gateway_out=logtostderr=true:./proto \
./proto/*.proto

このように複数まとめて指定して生成することも可能です。

サンプルコード

今回使ったサンプルコードはこちら

github.com

まとめ

一見複雑そうなprotocコンパイル方法ですが、一定のルールを覚えると柔軟に使えるようになると思います。

ソース