生成 Go 代码
Protoc
使用 protoc
是从 proto
文件手动生成代码的方法。如果你只处理少量的 proto
文件,并且文件之间没有很多依赖(如导入),这种方法可能还可以接受。否则,随着项目的复杂性增加,手动使用 protoc
会变得非常麻烦,正如我们将看到的那样。
不过,我认为我们应该先了解一下 protoc
命令,这样我们可以对它的功能有一个基本的了解。而且,许多高级工具都是基于 protoc
构建的,因此这将帮助我们理解它的不同特性。
使用前面提到的 dummy.proto
文件,我们可以在根目录(chapter4
文件夹)运行如下命令:
$ protoc --go_out=. \
--go_opt=module=github.com/PacktPublishing/gRPC-Go-for-Professionals \
--go-grpc_out=. \
--go-grpc_opt=module=github.com/PacktPublishing/gRPC-Go-for-Professionals \
proto/dummy/v1/dummy.proto
这看起来可能有点复杂,实际上这不是你可以编写的最简洁的命令。我将在介绍 Buf
时向你展示一个更简洁的命令。但首先,让我们将上述命令分解成几个部分来理解。
在讨论 --go_out
和 --go-grpc_out
之前,我们先来看一下 --go_opt=module
和 --go-grpc_opt=module
这两个选项。这些选项用于告诉 protoc
需要根据在 Proto 文件中传递的 go_package
选项来剥离公共模块。假设我们有如下内容:
option go_package = "github.com/PacktPublishing/gRPC-Go-for-Professionals/proto/dummy/v1";
然后,--go_opt=module=github.com/PacktPublishing/gRPC-Go-for-Professionals
会从 go_package
中剥离出 module=
后面的部分,这样最终得到的就是 /proto/dummy/v1
。
现在我们理解了这一点,我们可以讨论 --go_out
和 --go-grpc_out
这两个选项。这两个选项告诉 protoc
生成 Go 代码的位置。在我们的例子中,看起来我们是在告诉 protoc
在根目录生成代码,但实际上,因为它与之前提到的两个选项结合使用,它会把代码生成到 Proto 文件所在的目录旁边。这是由于去除包路径的操作,导致 protoc
会在 /proto/dummy/v1
包路径下生成代码。
现在,您可以看到每次编写这样的命令可能会有多么痛苦。大多数人不会这么做。他们通常会编写脚本来自动执行此操作,或者使用其他工具,如 Buf
。
Buf
对于 Buf
,我们需要进行一些设置来生成代码。在项目的根目录(chapter4
)下,我们将创建一个 Buf
模块。为此,我们只需要运行以下命令:
$ buf mod init
这将创建一个名为 buf.yaml
的文件。这个文件是用来设置项目级别的选项的,比如代码检查或跟踪破坏性更改。虽然这些内容超出了本书的范围,但如果你对这个工具感兴趣,可以查看官方文档: Buf Getting Started。
创建完 buf.yaml
后,我们需要写一个代码生成的配置。在一个名为 buf.gen.yaml
的文件中,我们将配置如下内容:
version: v1
plugins:
- plugin: go
out: proto
opt: paths=source_relative
- plugin: go-grpc
out: proto
opt: paths=source_relative
在这里,我们定义了使用 Go 插件来生成 Protobuf
和 gRPC 代码。对于每个插件,我们都指定了代码生成的输出目录为 proto
,并且使用了一个 --go_opt
和 --go-grpc_opt
的配置项 paths=source_relative
。当设置为 paths=source_relative
时,生成的代码会放在与输入文件(如 dummy.proto
)相同的目录下。最终,Buf
执行的命令类似于我们在 protoc
部分执行的命令:
$ protoc --go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
proto/dummy/v1/dummy.proto
为了使用 Buf
运行代码生成,我们只需要在 chapter4
目录下运行以下命令:
$ buf generate proto
对于中型或大型项目,使用 Buf
是很常见的做法。它帮助自动化代码生成,并且很容易上手。不过,你可能已经注意到,我们需要先生成代码,然后再构建 Go 应用程序。接下来,Bazel
将帮助我们将这一切合并到一步中。
Bazel
在本节中,我将使用名为 |
Bazel
的设置稍微复杂一些,但它值得付出努力。一旦你的构建系统搭建完成,你将能够通过一个命令完成整个应用程序的构建(包括代码生成和构建)或运行。
在 Bazel
中,我们从定义一个名为 WORKSPACE.bazel
的文件开始,文件位于项目的根目录。在这个文件中,我们定义项目的所有依赖项。在我们的例子中,我们有 Protobuf
和 Go 的依赖。此外,我们还将添加一个对 Gazelle
的依赖,Gazelle
将帮助我们创建生成代码所需的 BUILD.bazel
文件。
所以,在 WORKSPACE.bazel
文件中,在其他内容之前,我们将定义我们的工作区名称,导入我们的版本变量,并导入一些实用工具来克隆 Git 仓库和下载归档文件:
workspace(name = "github_com_packtpublishing_grpc_go_for_professionals")
load("//:versions.bzl",
"GO_VERSION",
"RULES_GO_VERSION",
"RULES_GO_SHA256",
"GAZELLE_VERSION",
"GAZELLE_SHA256",
"PROTO_VERSION"
)
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
接着,我们添加 Gazelle
依赖项:
http_archive(
name = "bazel_gazelle",
sha256 = GAZELLE_SHA256,
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/%s/bazel-gazelle-%s.tar.gz" % (GAZELLE_VERSION, GAZELLE_VERSION),
"https://github.com/bazelbuild/bazel-gazelle/releases/download/%s/bazel-gazelle-%s.tar.gz" % (GAZELLE_VERSION, GAZELLE_VERSION),
],
)
然后,我们添加 Go 构建工具的依赖项:
http_archive(
name = "io_bazel_rules_go",
sha256 = RULES_GO_SHA256,
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/%s/rules_go-%s.zip" % (RULES_GO_VERSION, RULES_GO_VERSION),
"https://github.com/bazelbuild/rules_go/releases/download/%s/rules_go-%s.zip" % (RULES_GO_VERSION, RULES_GO_VERSION),
],
)
现在我们有了这些,我们可以拉取 rules_go
的依赖项,设置构建 Go 项目的工具链,并告诉 Gazelle 在哪里找到我们的 WORKSPACE.bazel
文件:
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
go_rules_dependencies()
go_register_toolchains(version = GO_VERSION)
gazelle_dependencies(go_repository_default_config = "//:WORKSPACE.bazel")
然后,我们拉取 Protobuf
的依赖并加载它的依赖项:
git_repository(
name = "com_google_protobuf",
tag = PROTO_VERSION,
remote = "https://github.com/protocolbuffers/protobuf"
)
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
protobuf_deps()
我们的 WORKSPACE.bazel
文件就完成了。
接下来,我们来配置根目录下的 BUILD.bazel
文件。在这个文件中,我们将定义运行 Gazelle
的命令,并告诉 Gazelle
我们的 Go 模块名称,同时要求它忽略 proto
目录中的 Go 文件。如果不这样做,Gazelle
会认为 proto
目录中的 Go 文件也应该有自己的 Bazel
目标文件,这可能会带来一些问题:
load("@bazel_gazelle//:def.bzl", "gazelle")
# gazelle:exclude proto/**/*.go
# gazelle:prefix github.com/PacktPublishing/gRPC-Go-for-Professionals
gazelle(name = "gazelle")
有了这些,我们现在可以运行以下命令:
如果你通过 可以在 |
在依赖项被拉取并编译后,你应该能够看到在 proto/dummy/v1
目录下生成一个 BUILD.bazel
文件。这个文件中最重要的部分是以下的 go_library
:
go_library(
name = "dummy",
embed = [":v1_go_proto"],
importpath = "github.com/PacktPublishing/gRPC-Go-for-Professionals/proto/dummy/v1",
visibility = ["//visibility:public"],
)
稍后,我们将使用这个库并将其链接到我们的二进制文件中。它包含了我们开始使用所需的所有生成代码。