使用ProtocolBuffers的Go项目通过Bazel进行构建
首先
本文介绍了如何在Bazel中使用Protocol Buffers构建Go项目的实践方式。
写在书上的内容:
基本上,除了在bazelbuild/bazel-gazelle中所述的信息外,我没有更多的信息,但我会逐步解释如何进行。
未記載之事項。
Protocol Buffers, Go, Bazel都是什么?
每个解释都会在其他文件中详细说明。
的改变是我的优先事项。
我的重点是实现目标。
我最关注的是达到目标。
使Bazel来构建gRPC的Go Quick Start例子。
前提条件
首先确认已安装Go 1.6以上版本。
$ go version
go version go1.11 darwin/amd64
通过使用Bazel,可以在配置文件中管理依赖库以及像protoc这样的工具。不需要独立安装gRPC和protoc。
而且,您也不需要将源代码放置在 $GOPATH/src 目录下。因此,您可以克隆 gRPC Go代码到任意目录中。
$ git clone https://github.com/grpc/grpc-go.git
本次将以 examples/helloworld 目录下的文件作为 Bazel 化的主题。其他文件不需要。
$ cd grpc-go/examples/helloworld
在这个项目中,已经包含了从helloworld.proto文件生成的helloworld.pb.go文件,但为了确认它也可以在bazel中构建,需要将其删除。
$ rm helloworld/helloworld.pb.go
巴泽尔兼容性
工作空间
在项目的顶级目录下创建名为WORKSPACE的文件,并按照以下方式进行描述。
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# download go bazel tools
http_archive(
name = "io_bazel_rules_go",
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.15.1/rules_go-0.15.1.tar.gz"],
sha256 = "5f3b0304cdf0c505ec9e5b3c4fc4a87b5ca21b13d8ecc780c97df3d1809b9ce6",
)
# download the gazelle tool
http_archive(
name = "bazel_gazelle",
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.14.0/bazel-gazelle-0.14.0.tar.gz"],
sha256 = "c0a5739d12c6d05b6c1ad56f2200cb0b57c5a70e03ebd2f7b87ce88cabf09c7b",
)
# load go rules
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()
# load gazelle
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
gazelle_dependencies()
# external dependencies
load("@bazel_gazelle//:deps.bzl", "go_repository")
go_repository(
name = "com_github_golang_protobuf",
importpath = "github.com/golang/protobuf",
tag = "v1.14.0",
)
由于这篇文章提到了特定版本,请参考Bazel的“生成构建文件”章节来指定最新版本。
使用go_repository来描述依赖库。类似于godep中对vendor的管理。这里是描述使用protobuf。更详细的关于go_repository的解释在这里。
瞿他処旨
请按以下方式在项目的顶级目录下创建一个BUILD.bazel文件,与工作区相同。
load("@bazel_gazelle//:def.bzl", "gazelle")
# gazelle:prefix google.golang.org/grpc/examples/helloworld
gazelle(name = "gazelle")
然后,执行gazelle。
$ bazel run //:gazelle
在首次执行时,将下载必要的文件并在每个包目录下创建BUILD.bazel。由于需要下载和构建依赖库,所以可能需要花费一些时间。
当执行完成后,就会生成定义protobuf的helloworld包的BUILD.bazel文件,并可以看到已指定了使用proto_library、go_proto_library和go_library的目标。
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
proto_library(
name = "helloworld_proto",
srcs = ["helloworld.proto"],
visibility = ["//visibility:public"],
)
go_proto_library(
name = "helloworld_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "google.golang.org/grpc/examples/helloworld/helloworld",
proto = ":helloworld_proto",
visibility = ["//visibility:public"],
)
go_library(
name = "go_default_library",
embed = [":helloworld_go_proto"],
importpath = "google.golang.org/grpc/examples/helloworld/helloworld",
visibility = ["//visibility:public"],
)
BUILD.bazel文件在服务器软件包greeter_server中创建了go_library和go_binary。
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "google.golang.org/grpc/examples/helloworld/greeter_server",
visibility = ["//visibility:private"],
deps = [
"//helloworld:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_binary(
name = "greeter_server",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
同樣地,greeter_client也會建立一個BUILD.bazel。
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "google.golang.org/grpc/examples/helloworld/greeter_client",
visibility = ["//visibility:private"],
deps = [
"//helloworld:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_binary(
name = "greeter_client",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
建立
构建的执行只需要指定目标。
$ bazel build //greeter_server
$ bazel build //greeter_client
进行
在bazel-bin下面已经生成了可执行文件。
服务器启动
$ bazel run //greeter_server
客户端的运行
$ bazel run //greeter_client
2018/09/03 17:10:21 Greeting: Hello world
考试
对于测试模拟的BUILD.bazel也会以类似的方式创建。
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["hw_mock.go"],
importpath = "google.golang.org/grpc/examples/helloworld/mock_helloworld",
visibility = ["//visibility:public"],
deps = [
"//helloworld:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["hw_mock_test.go"],
embed = [":go_default_library"],
deps = [
"//helloworld:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
测试的执行
$ bazel test
考察 chá)
虽然这看起来非常神奇,但考虑到使用go build就可以执行Go的构建本身,实际上可能更加繁琐。作为优势,它完全不依赖于$GOPATH,这样可以避免由于环境依赖造成构建失败的麻烦。虽然godep也可以描述依赖库,但是在Bazel中甚至可以指定Go的版本。
当然,在Docker中构建可以解决依赖库和工具链版本的问题,但在频繁进行构建的开发过程中,这可能效率低下。
好吧,让我们回顾一下实际上需要注意的要点是哪些。
在BUILD.bazel中的gazelle:prefix
在项目的BUILD.bazel顶层文件中
# gazelle:prefix google.golang.org/grpc/examples/helloworld
有一个规定,关于评论的格式,这是针对瞪羚项目的一个有效指示,它指定了该项目自身的路径。这样一来,在Gazelle生成的BUILD.bazel文件中,go_library和go_proto_library的importpath前缀就确定了。
...
go_proto_library(
name = "helloworld_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "google.golang.org/grpc/examples/helloworld/helloworld",
proto = ":helloworld_proto",
visibility = ["//visibility:public"],
)
...
而且,通过这个前缀,项目中包含的包的依赖关系将变为内部引用。
...
deps = [
"//helloworld:go_default_library",
...
当greeter_{server,client}连接本地的helloworld包时,将生成BUILD.bazel。如果它是类似于@org…的外部引用,那么说明前缀设置不正确。
在Go中指定import
一旦在BUILD.bazel中设置了依赖关系,就可以从实际的代码中使用helloworld.proto。
在greeter_{server,client}/main.go文件中
pb "google.golang.org/grpc/examples/helloworld/helloworld"
我正在进行导入操作。
尝试将 google.golang.org/grpc/examples/helloworld 前缀更改为自己的项目路径,可能会加深理解。
总结
使用Bazel编译和运行基于Go和Protocol Buffers构建的gRPC服务器和客户端。
我会将上述步骤的结果上传至Github上的stn/grpc-helloworld-bazel。
Bazel 可以提供独立于开发环境的构建环境。使用 Bazel,您可以避免类似于“按照指定的步骤进行但无法构建”或“什么都没做但突然出错了”等问题(例如这些问题)。
编辑历史
2018/09/05 运行可以使用 bazel run 。
2018/10/13 将结果上传至 Github 。