文章目录

1. 简介2.protoc命令3.文件输出位置4. Packages

1. 简介

前面说过,grpc使用Protocol Buffer(简称protobuf)作为接口描述语言,protobuf的重点之一即是protoc工具的使用,这篇文章将介绍protoc工具的使用,特别是和go相关插件的组合使用。

2.protoc命令

我们首先构建一个项目,项目结构如下:

proto-project

├── cient.go

├── genproto

├── proto

│ ├── messagepb

│ └── simplepb

│ └── simple.proto

└── server.go

其中,simple.proto的内容如下:

syntax = "proto3";

package simplepb;

option go_package = "github.com/IguoChan/proto-project/genproto/simplepb";

message SimpleRequest{

string data = 1;

}

message SimpleResponse{

int32 code = 1;

string value = 2;

}

// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)

service Simple{

rpc Route (SimpleRequest) returns (SimpleResponse){};

}

我们希望最后生成的go文件出现在genproto文件夹中,我们在项目根目录下执行以下指令:

protoc -I./proto --go_out=./genproto --go_opt paths=source_relative ./proto/*/*.proto

从上面可以看出,protoc命令有三个重要的参数,分别是搜索路径参数、语言插件参数和文件位置参数,以下将分别介绍一下这三种参数。 2.1 搜索路径参数

$ protoc --help

Usage: protoc [OPTION] PROTO_FILES

Parse PROTO_FILES and generate output based on the options given:

-IPATH, --proto_path=PATH Specify the directory in which to search for

imports. May be specified multiple times;

directories will be searched in order. If not

given, the current working directory is used.

If not found in any of the these directories,

the --descriptor_set_in descriptors will be

checked for required proto file.

我们通过这个参数来设置需要导入的proto文件位置,如果没设置,那就以当前工作目录为准去寻找,通常写作-IPATH或者--proto_path=PATH。

2.2 文件位置参数 最后就是文件位置参数,文件位置参数的描述如下:

@ Read options and filenames from file. If a

relative file path is specified, the file

will be searched in the working directory.

The --proto_path option will not affect how

this argument file is searched. Content of

the file will be expanded in the position of

@ as in the argument list. Note

that shell expansion is not applied to the

content of the file (i.e., you cannot use

quotes, wildcards, escapes, commands, etc.).

Each line corresponds to a single argument,

even if it contains spaces.

按照其中的描述,proto_path,也就是搜索路径参数对其并无影响,但是实际上并不是的,比如我们在messagepb文件夹新增一个message.proto文件,其引用了simple.proto,如下:

syntax = "proto3";

package messagepb;

option go_package = "github.com/IguoChan/proto-project/genproto/messagepb";

import "simplepb/simple.proto";

message MessageRequest {

simplepb.SimpleRequest req = 1;

}

在项目的根目录下,执行以下指令,可以生成正确的go代码:

$ pwd

/Users/xxx/workspace/proto-project

$ protoc -I./proto --go_out=./genproto --go_opt paths=source_relative ./proto/messagepb/message.proto

此时整个项目如下,说明message.pb.go文件生成的是正确的.

proto-project

├── client.go

├── genproto

│ └── messagepb

│ └── message.pb.go

├── go.mod

├── go.sum

├── proto

│ ├── messagepb

│ │ └── message.proto

│ └── simplepb

│ └── simple.proto

└── server.go

但是如果我们到以下目录执行如下指令时,就会发现会报错:

$ pwd

/Users/xxx/workspace/proto-project/proto/messagepb

$ protoc -I../ --go_out=../../genproto --go_opt paths=source_relative ./message.proto

./message.proto: File does not reside within any path specified using --proto_path (or -I). You must specify a --proto_path which encompasses this file. Note that the proto_path must be an exact prefix of the .proto file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it's harder than you think).

最开始时,我并不理解这两种方式有什么区别,通过万能的互联网也没有找到特别让人信服的说法,比如以下两种:

protobuf编译出错…给出的结论是如果指定了proto_path(-I)参数,protoc不在当前目录寻找proto文件了。根据这个思路,我们将指令改为以下方式,发现会报错,即找不到目标文件的位置。说明以上说法也不是完全准确。

$ protoc -I../ --go_out=../../genproto --go_opt paths=source_relative ./messagepb/message.proto

Could not make proto path relative: ./messagepb/message.proto: No such file or directory

File does not reside…中,有人认为,-I参数中不应该存在…/的形式,我认为描述也不准确。 经过我的不断实验,发现在此目录下,有以下几种方式都是能够正确产生go文件的:

$ pwd

/Users/xxx/workspace/proto-project/proto/messagepb

# 1.文件位置参数中必须包含-I中的参数

$ protoc -I../ --go_out=../../genproto --go_opt paths=source_relative ../messagepb/message.proto

# 2.绝对路径时也必须满足“文件位置参数中必须包含-I中的参数”

protoc -I/Users/xxx/workspace/proto-project/genproto/../proto/messagepb/../ --go_out=/Users/xxx/workspace/proto-project/proto/messagepb/../../genproto --go_opt paths=source_relative /Users/xxx/workspace/proto-project/genproto/../proto/messagepb/../messagepb/message.proto

# 3.以下这种形式也可以

protoc -I../ --go_out=../../genproto --go_opt paths=source_relative messagepb/message.proto

实验总结有以下规律,但是没有从其他资料得到验证(也没有时间去扒拉源码啦),如果有问题还请大家指正!

如果文件位置参数以…/、./或者/开头:1)首先判断文件位置参数是否包含-I中的位置参数,比如以上第一种情况里…/messagepb/message.proto中包含-I…/中的…/,第二种情况里/Users/xxx/workspace/proto-project/genproto/…/proto/messagepb/…/messagepb/message.proto中包含-I参数中的/Users/xxx/workspace/proto-project/genproto/…/proto/messagepb/…/;2)判断这个路径是否真实存在所需proto文件;当1)和2)两个条件都为是,才能找到正确的文件位置参数。如果文件参数以正常文件或者文件夹开头(也就是字母等):将-I参数添加到文件前,比如以上第三种情况中,组合的path为…/messagepb/message.proto;将其作为新的文件位置参数输入到1中,再判断条件2)。

多个-I参数时,有满足以上条件的即可。至于protoc --help中给出的-I参数对文件位置参数不影响,我觉得描述有误。 2.3 语言插件参数 protoc自带了一些语言插件,如下:

--cpp_out=OUT_DIR Generate C++ header and source.

--csharp_out=OUT_DIR Generate C# source file.

--java_out=OUT_DIR Generate Java source file.

--js_out=OUT_DIR Generate JavaScript source.

--kotlin_out=OUT_DIR Generate Kotlin file.

--objc_out=OUT_DIR Generate Objective-C header and source.

--php_out=OUT_DIR Generate PHP source file.

--python_out=OUT_DIR Generate Python source file.

--ruby_out=OUT_DIR Generate Ruby source file.

但是其并不包括go的语言插件,所以我们需要自己安装,Go Generated Code。其实安装教程我们在前一章已经描述过了,下面重点讲一下文件的输出位置。

3.文件输出位置

我们执行以下指令,可以在genproto文件夹下生成了我们希望的文件输出位置。

$ pwd

/Users/xxx/workspace/proto-project

$ protoc -I./proto --go_out=./genproto --go_opt paths=source_relative ./proto/*/*.proto

$ tree genproto

genproto

├── messagepb

│ └── message.pb.go

└── simplepb

└── simple.pb.go

那么如何控制生成go文件的输出位置呢?官方文档中有描述,protoc通过–go_out参数和–go_opt参数共同决定:

If the paths=import flag is specified, the output file is placed in a directory named after the Go package’s import path. For example, an input file protos/buzz.proto with a Go import path of example.com/project/protos/fizz results in an output file at example.com/project/protos/fizz/buzz.pb.go. This is the default output mode if a paths flag is not specified. If the paths=source_relative flag is specified, the output file is placed in the same relative directory as the input file. For example, an input file protos/buzz.proto results in an output file at protos/buzz.pb.go.

paths默认为import,在这种模式下,其会在–go_out所指定的目录下完全按照go package(下章介绍)的路径生成文件,比如执行以下命令:

$ pwd

/Users/xxx/workspace/proto-project

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

$ tree genproto

genproto

└── github.com

└── IguoChan

└── proto-project

└── genproto

├── messagepb

│ └── message.pb.go

└── simplepb

└── simple.pb.go

可以发现,其在genproto文件夹下生成了完全按照.proto文件中option go_package所示的文件路径的格式。说实话,这种方式对于工程应用很不友好,比如正常工程的go package地址常带着git仓库的头,而git仓库并不是工程名的一部分,导致很不好管理,所以我们一般使用relative的生成方式。 当使用–go_opt paths=source_relative指定生成的文件位置时,其生成规则是.proto文件相对于-I参数的相对位置,即是生成的.pb.go文件相对–go_out参数的相对位置,所以以下指令才能生成如下的工程文件。

$ pwd

/Users/xxx/workspace/proto-project

$ protoc -I./proto --go_out=./genproto --go_opt paths=source_relative ./proto/*/*.proto

$ tree ../proto-project

../proto-project

├── client.go

├── genproto

│ ├── messagepb

│ │ └── message.pb.go

│ └── simplepb

│ └── simple.pb.go

├── go.mod

├── go.sum

├── proto

│ ├── messagepb

│ │ └── message.proto

│ └── simplepb

│ └── simple.proto

└── server.go

4. Packages

Packages,为了生成go代码,go包是必须要指定的,在protoc中,有两种方式指定go的包:

在.proto文件中指定在protoc下发指令时指定

官方建议在.proto文件中声明它,以便文件的Go包可以用.proto文件本身集中标识,并简化调用时传递的标志集protoc。 需要明确的是,.proto文件中的package和指定go包的go_package没有任何关系。 package参数针对的是protobuf,是proto文件的命名空间,它的作用是为了避免我们定义的接口,或者message出现冲突。 go_package参数主要声明Go代码的存放位置,也可以说它解决的是包名问题。

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。