概要
Bazel解説第4弾です。
Bazelを使ってみる その1(Goのビルド) - Carpe Diem
Bazelを使ってみる その2(protobufのビルド) - Carpe Diem
Bazelを使ってみる その3(docker imageのビルド) - Carpe Diem
今回はgRPCをビルドしてみます。
gRPCは2通りの設定方法があります。
- gazelleのやり方
- grpcのruleをメンテしてるコミュニティのやり方
今回は簡単なgazelleのやり方で説明します。
環境
- Bazel v4.2.2
準備
gRPCコード準備
まずgRPCのリポジトリを用意します。
ディレクトリ構造
以下のディレクトリ構造で、
├── client │ └── main.go ├── go.mod ├── go.sum ├── proto │ └── helloworld.proto └── server └── main.go
proto/helloworld.proto
Unary callなgRPCを用意します。
syntax = "proto3"; package helloworld; option go_package = "github.com/jun06t/bazel-sample/grpc/proto;helloworld"; service Greeter { rpc SayHello(HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
server/main.go
protoに依存させてますがこの段階では.pb.go
がないので依存関係でWarning等出ると思います。
go build
してもコケるでしょう。
package main import ( "context" "log" "net" pb "github.com/jun06t/bazel-sample/grpc/proto" "google.golang.org/grpc" ) const ( port = ":8080" ) type server struct{} func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatal(err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) err = s.Serve(lis) if err != nil { log.Fatal(err) } }
client
こちらも同様にClientの.pb.go
がないのでWarningが出るでしょうしビルドもエラーになります。
package main import ( "context" "log" pb "github.com/jun06t/bazel-sample/grpc/proto" "google.golang.org/grpc" ) const ( address = "localhost:8080" ) func main() { conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatal(err) } defer conn.Close() c := pb.NewGreeterClient(conn) req := &pb.HelloRequest{ Name: "alice", } resp, err := c.SayHello(context.Background(), req) if err != nil { log.Fatal(err) } log.Println("Reply: ", resp.Message) }
Bazel準備
WORKSPACE
こちらはいつもの設定にprotobufのときと同じ設定を追記します。
http_archive( name = "com_google_protobuf", sha256 = "d0f5f605d0d656007ce6c8b5a82df3037e1d8fe8b121ed42e536f569dec16113", strip_prefix = "protobuf-3.14.0", urls = [ "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v3.14.0.tar.gz", "https://github.com/protocolbuffers/protobuf/archive/v3.14.0.tar.gz", ], ) load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps()
rootのBUILD.bazel
gRPCは依存が多いので別ファイルで生成できるように-to_macro=deps.bzl%go_dependencies
オプションを付けます。
また依存ライブラリのprotoを再ビルドしないように-build_file_proto_mode=disable_global
オプションもデフォルトで付けるようにします。
load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/jun06t/bazel-sample/grpc gazelle(name = "gazelle") gazelle( name = "gazelle-update-repos", args = [ "-from_file=go.mod", "-to_macro=deps.bzl%go_dependencies", "-prune", "-build_file_proto_mode=disable_global", ], command = "update-repos", )
この状態で一度
$ bazel run //:gazelle
すると、以下のように./proto/BUILD.bazel
が配置されます。
./proto/BUILD.bazel
load("@rules_proto//proto:defs.bzl", "proto_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 = "github.com/jun06t/bazel-sample/grpc/proto", proto = ":helloworld_proto", visibility = ["//visibility:public"], ) go_library( name = "proto", embed = [":helloworld_go_proto"], importpath = "github.com/jun06t/bazel-sample/grpc/proto", visibility = ["//visibility:public"], )
protobufの時と違って
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
がありますね。
./server/BUILD.bazel
サーバは以下のように生成されました。
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") go_library( name = "server_lib", srcs = ["main.go"], importpath = "github.com/jun06t/bazel-sample/grpc/server", visibility = ["//visibility:private"], deps = [ "//proto", "@org_golang_google_grpc//:go_default_library", ], ) go_binary( name = "server", embed = [":server_lib"], pure = "on", visibility = ["//visibility:public"], )
deps
で//proto
に依存することで、先程解決できなかった依存関係をビルド時に解決させます。
クライアントも同様です。
ビルド
ではサーバ、クライアントをそれぞれ実行してみます。
サーバ
$ bazel run //server Starting local Bazel server and connecting to it... INFO: Analyzed target //server:server (140 packages loaded, 9306 targets configured). INFO: Found 1 target... Target //server:server up-to-date: bazel-bin/server/server_/server INFO: Elapsed time: 116.644s, Critical Path: 24.24s INFO: 341 processes: 13 internal, 328 darwin-sandbox. INFO: Build completed successfully, 341 total actions INFO: Build completed successfully, 341 total actions
クライアント
$ bazel run //client INFO: Analyzed target //client:client (1 packages loaded, 3 targets configured). INFO: Found 1 target... Target //client:client up-to-date: bazel-bin/client/client_/client INFO: Elapsed time: 1.476s, Critical Path: 0.88s INFO: 5 processes: 3 internal, 2 darwin-sandbox. INFO: Build completed successfully, 5 total actions INFO: Build completed successfully, 5 total actions 2022/01/06 19:35:02 Reply: Hello alice
ちゃんとReplyが返ってきました。
ただし.pb.go
はサンドボックスにしかないです。
サンプルコード
今回の成果物はこちら
まとめ
Bazelを使ったgRPCのビルドを行いました。
- gazelleのおかげでほぼいじる必要がない
- protoの依存関係も解決される
- ただし
.pb.go
はサンドボックスに生成されるため、エディタなどでは依存関係が解決されないので別途対応が必要
- ただし
といった点が分かりました。