ProtocolBuffersを使っているGoのプロジェクトをBazelでビルドする

# はじめに

この記事では[Protocol Buffers](https://developers.google.com/protocol-buffers/)を使った[Go](https://golang.org/)のプロジェクトを[Bazel](https://bazel.build/)でビルドする方法をハンズオン形式で紹介する．

*書かれていること：*

基本的に[bazelbuild/bazel-gazelle](https://github.com/bazelbuild/bazel-gazelle)に書かれていること以上の情報はないが，ステップ・バイ・ステップに進めていけるよう解説する．


*書かれていないこと：*

Protocol Buffers, Go, Bazelって何？

それぞれの解説は他のドキュメントに譲ります．


# 目標

gRPCの[Go Quick Start](https://grpc.io/docs/quickstart/go.html)で紹介されているexampleをBazelでbuildできるようにする．



# 前提

Go 1.6以上がインストールされていることをまず確認する．

```
$ go version
go version go1.11 darwin/amd64
```

Bazelを使うことで，依存ライブラリはもちろん，protocのようなツールまでも，設定ファイルで管理することができる．gRPCのインストールも，protocのインストールも不要．

また，ソースコードを `$GOPATH/src` の下に配置する必要もない．そこで，適当なディレクトリーにgRPC Goのコードをcloneする．

```
$ git clone https://github.com/grpc/grpc-go.git
```

今回，Bazel化をしていく題材にするのは `examples/helloworld` 以下のファイル．その他のファイルは不要です．

```
$ cd grpc-go/examples/helloworld
```

このプロジェクトでは，すでに，helloworld.protoファイルから生成されたhelloworld.pb.goファイルが含まれているが，これもbazelでbuildできることを確認するため削除する．

```
$ rm helloworld/helloworld.pb.go
```

# Bazel対応

## WORKSPACE

プロジェクトのトップディレクトリーにWORKSPACEというファイルを作り，以下のように記述する．

```python: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のGenerating build files](https://github.com/bazelbuild/rules_go#generating-build-files)を参考に最新のバージョンを指定するとよい．

`go_repository`によって依存ライブラリを記述する．`godep`における`vendor`の管理にあたる．ここでは`protobuf`を使うことを記述している．`go_repository`の詳しい解説は[こちら](https://github.com/bazelbuild/bazel-gazelle/blob/master/repository.rst#go_repository)に．


## Gazelle実行

`WORKSPACE`と同じくプロジェクトのトップディレクトリーに`BUILD.bazel`ファイルを以下のように作成する．


```python: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`を用いたターゲット指定されていることが分かる．

```python:helloworld/BUILD.bazel
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"],
)
```

サーバーのパッケージである`greeter_server`中の`BUILD.bazel`には，`go_library`と`go_binary`が作成される．

```python:greeter_server/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_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`が作成される．

```python: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"],
)
```

## Build

buildの実行はターゲットを指定するだけ．

```
$ bazel build //greeter_server
$ bazel build //greeter_client
```

## 実行

bazel-bin以下に実行ファイルができている．

serverの起動

```
$ bazel run //greeter_server
```

clientの実行

```
$ bazel run //greeter_client
2018/09/03 17:10:21 Greeting: Hello world
```



## Test

test mockに対する`BUILD.bazel`も同様に作成される．

```python:mock_helloworld/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",
    ],
)
```

testの実行は

```
$ bazel test
```


# 考察

あまりにマジカルですが，Goのビルドそのものが`go build`で実行できることを思うとむしろ手間がかかっているともいえます．メリットとしては`$GOPATH`に全く依存しないため，環境依存でビルドに失敗するというトラブルを避けることができます．godepでも依存ライブラリを記述できますが，Bazelでは[Goのバージョンですら指定することができます](https://github.com/bazelbuild/rules_go#how-do-i-test-a-beta-version-of-the-go-sdk)．

もちろん，Dockerの中でbuildすれば依存ライブラリやツールチェインのバージョン問題もないのですが，開発中の頻繁にビルドを繰り返す際にそれは効率が悪いでしょう．

さて，実際のところ注意すべき点はどこなのかを振り返ってみます．

## `BUILD.bazel`での`gazelle:prefix`

プロジェクトトップの`BUILD.bazel`で

```python
# gazelle:prefix google.golang.org/grpc/examples/helloworld
```

という指定がありました．コメントの形式ですが，これはGazelleに対する有効な[directive](https://github.com/bazelbuild/bazel-gazelle#directives)であり，このプロジェクト自身のパスを指定します．これによってGazelleが生成する`BUILD.bazel`中での`go_library`, `go_proto_library`の`importpath`のprefixが決まります．

```python:helloworld/BUILD.bazel
...
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"],
) ...
```

また，このprefixによって，プロジェクトに含まれるパッケージに対する依存関係は内部参照となります．

```python:greeter_server/BUILD.bazel
...
    deps = [
        "//helloworld:go_default_library",
...
```

このように`greeter_{server,client}`がローカルのhelloworldパッケージをリンクするという`BUILD.bazel`が生成されます．もしこれが `@org...`のような外部参照になっていた場合は正しくprefixが設定されていないということです．


## *.goでのimportの指定

上記のように`BUILD.bazel`に依存関係が設定されたら，実際のコードから`helloworld.proto`を利用できます．

`greeter_{server,client}/main.go`で

```
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
```

とimportしています．

`google.golang.org/grpc/examples/helloworld`というprefixを自分のプロジェクトパスに変更してみるとより理解が深まるかもしれません．


# まとめ

Bazelを用いて，Go + Protocol Buffersを利用して作られたgRPCのサーバーおよびクライアントをビルドし実行できました．

上記の手順の結果をGithubにあげておきます。
 [stn/grpc-helloworld-bazel](https://github.com/stn/grpc-helloworld-bazel/tree/v1.0.1)

開発者の環境に依存しないビルド環境を提供するBazelは「指定された手順通りやったけどビルドできない」「何もしていないのにいつの間にか壊れた」といったトラブル（例えば[こんなissues](https://www.google.co.jp/search?q=undefined%3A+proto.InternalMessageInfo)）を避けることができます．


**編集履歴**
2018/09/05 実行は `bazel run` でよかった．
2018/10/13 Githubに結果をアップロード