Carpe Diem

備忘録

WARNING: Missing 'go_package' option が出る時の対処法

背景

いつの間にかprotocでprotoc-gen-goプラグインを使うと以下のようなエラーが出るようになりました。

2021/01/05 06:19:01 WARNING: Missing 'go_package' option in "xxx/xxx.proto",
please specify it with the full Go package path as
a future release of protoc-gen-go will require this be specified.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

現時点ではwarningですが、メッセージにあるように将来的にrequiredになるようなので早めに対応する方が良さそうです。

ドキュメントでも以下のようにgo_packageにフルパスのimportを指定するように書かれています。

The .proto file should contain a go_package option specifying the full import path of the Go package that contains the generated code.

ref: Go Generated Code  |  Protocol Buffers  |  Google Developers

環境

  • protoc v3.14.0
  • protoc-gen-go v1.25.0
  • golang v1.15.6
  • protoc-gen-go-grpc v1.0.1

go_package optionはなぜ必要?

まずgo_packageが必要なケースを説明します。

go_packageがないと

背景としては以下のようにあるprotoが別ディレクトリにあるprotoファイルを参照したい時に

.
├── person
│   └── person.proto
└── team
    └── team.proto

単純に以下のようにしてimportすると、

person.proto
syntax = "proto3";

package person;

message Person {
  string Name = 1;
  uint32 Age  = 2;
}
team.proto
syntax = "proto3";

package team;

import "person/person.proto";  // ココ

message Team {
  repeated person.Person people = 1;
}

protocでのgoファイル生成はできるものの、生成されたgoファイルのimport pathがpackage名のみで上手く参照できていないためです。

team.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.23.0
//     protoc        v3.14.0
// source: team/team.proto

package team

import (
    proto "github.com/golang/protobuf/proto"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
    protoimpl "google.golang.org/protobuf/runtime/protoimpl"
    person "person"  // ココ
    reflect "reflect"
    sync "sync"
)

const (
    // Verify that this generated code is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)

なのでgo自体のビルドはコケてしまいます。

go_packageがあると

ここで以下のようにgo_packageを指定すると、

person.proto
syntax = "proto3";

option go_package = "github.com/jun06t/grpc-sample/go-package-option/after/person";  // ココ

package person;

message Person {
  string Name = 1;
  uint32 Age  = 2;
}
team.proto
syntax = "proto3";

option go_package = "github.com/jun06t/grpc-sample/go-package-option/after/team";  // ココ

package team;

import "person/person.proto";

message Team {
  repeated person.Person people = 1;
}

生成されたgoファイルでもきちんとimport pathが設定されます。

team.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.23.0
//     protoc        v3.14.0
// source: team/team.proto

package team

import (
    proto "github.com/golang/protobuf/proto"
    person "github.com/jun06t/grpc-sample/go-package-option/after/person"  // ココ
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
    protoimpl "google.golang.org/protobuf/runtime/protoimpl"
    reflect "reflect"
    sync "sync"
)

const (
    // Verify that this generated code is sufficiently up-to-date.
    _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)

単純にgo_packageを使用すると起きる問題

ここで1つ問題が起きます。

go_packageを指定するとgo_outで指定したディレクトリをルートにサブディレクトリが作成されてそこにファイルが置かれてしまうのです。

after/
├── github.com
│   └── jun06t
│       └── grpc-sample
│           └── go-package-option
│               └── after
│                   ├── person
│                   │   └── person.pb.go
│                   └── team
│                       └── team.pb.go
├── person
│   └── person.proto
└── team
    └── team.proto

これは結構前から問題視されていて、以下のIssueで議論されていました。

github.com

対処法

では上記の対応方法について説明します。

GOPATHを使っている場合

初期から挙がっていたwork aroundです。

go_outで指定する出力先を$GOPATH/srcにするやり方です。

protoc \
-I=./after \
--go_out=:${GOPATH}/src \
./after/team/*.proto \
./after/person/*.proto

こうすればgoファイルが参照できる位置にファイルが生成されるようになります。

しかし現在goはGo modulesの導入によりGOPATHは要らない世界を推進していますので、やり方としてはよろしくないでしょう。

GOPATHを使っていない場合(Go modulesの場合)

ドキュメントにもありますが、--go_opt=module=$PREFIXというフラグが追加されているのでこれを使います。

ref: Go Generated Code  |  Protocol Buffers  |  Google Developers

$PREFIXに書いた部分のディレクトリが省略されます。

protoc \
-I=./after \
--go_out=:./after \
--go_opt=module=github.com/jun06t/grpc-sample/go-package-option/after \
./after/team/*.proto \
./after/person/*.proto

こうすることで以下のように期待する位置にファイルが生成されます。

after/
├── person
│   ├── person.pb.go
│   └── person.proto
└── team
    ├── team.pb.go
    └── team.proto

その他

ディレクトリ名とpackage名が異なる場合

例えばprotoというディレクトリで管理しているけれど、goのpackage名として別で管理したい場合、protoファイルでpackage helloworldとしても

syntax = "proto3";

option go_package = "github.com/my-user/my-repo/proto";

package helloworld;

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

生成されたファイルのpackageはgo_packageで上書きされてしまいます。

// Code generated by protoc-gen-go-grpc. DO NOT EDIT.

package proto  // ココ

import (
        context "context"

これはgo_packageの最後のディレクトリ名をpackage名にする仕様のためです。

対応方法

対処方法として;でリネームできます。

syntax = "proto3";

option go_package = "github.com/my-user/my-repo/proto;helloworld";

package helloworld;

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

コンパイルします。

// Code generated by protoc-gen-go. DO NOT EDIT.

package helloworld  // ココ

import (
        context "context"

サンプルコード

検証したコードはこちら

github.com

まとめ

go_packageoptionの必要性と、--go_opt=module=$PREFIXを用いた利用方法を紹介しました。

参考