使用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 。

广告
将在 10 秒后关闭
bannerAds