BazelでGoプロジェクトのビルド。Gazelleのgo_repositoryで外部ライブラリの依存とBazelのgo_testでテスト。


BazelをつかったGoプロジェクトのビルドをまとめている。前回のエントリではバイナリのビルドとDockerイメージのビルドをまとめた。

GoとKotlinのマルチプロジェクトをBazelでビルドする - 平日インプット週末アウトプットぶろぐ

今回は外部ライブラリをGoプロジェクトに依存させる方法とテストの方法をまとめていく。

Gazelleのgo_repositoryで外部のライブラリを依存させる

外部ライブラリの依存はGazelleのgo_repositoryをつかう。

bazel-gazelle/repository.rst at master · bazelbuild/bazel-gazelle · GitHub

依存させるライブラリはDIツールのwireを選んだ。

WORKSPACE

WORKSPACEにgo_repositoryを有効にしてruleを追加する。

1
2
3
4
5
6
7
8
9
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")

gazelle_dependencies()

go_repository(
    name = "com_github_google_cloud",
    commit = "2152f209f3c907645f7ebdcacdb2c18cd89e6fa8",
    importpath = "github.com/google/go-cloud",
)

nameはWORKSPACE内の固有をつける。commitはgoogle/go-cloud 0.5.0 バージョンのハッシュ値を設定する。import_pathはGoコードでwireを利用するときにimport文のパスをセットする。

wireをつかってDIするコードを用意する。

簡易的なstruceのインスタンスをDIするコードを用意する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# pkg/public_go/usecase/greet_usecase.go

package usecase

import (
        "github.com/google/go-cloud/wire"
)

var GreetUsecaseSet = wire.NewSet(ProvideUseCase)

type GreetUsecase struct {
    Msg string
}

func ProvideUseCase(msg string) GreetUsecase {
    return GreetUsecase{
        Msg: msg,
    }
}

上記のコードを追加してgazelleコマンドを実行する。

1
(bazel-multiprojects) $ bazel run gazelle

コマンドを実行するとGazelleはWORKSPACEに追加したgo_repositoryの依存とgreet_usecase.goのwireの利用を理解して次のようなBUILD.bazelファイルを生成してくれる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pkg/public_go/usecase/BUILD.bazel


load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
    name = "go_default_library",
    srcs = ["greet_usecase.go"],
    importpath = "github.com/soushin/bazel-multiprojects/pkg/public_go/usecase",
    visibility = ["//visibility:public"],
    deps = ["@com_github_google_cloud//wire:go_default_library"],
)

greet_usecase.goをソースにしたライブラリのruleが定義されている。depsにはWORKSPACEで定義したcom_github_google_cloudのレポジトリが参照されている。

Gazelleの魅力

Bazel + Gazelleの魅力はこのようにWORKSPACEで定義した依存とコードの中間の役割のBUILDファイルを自動で生成してくれるところだ。

最終的にはmainパッケージからusecaseパッケージを参照することになる。wireで生成したwire_gen.goコードは次のようになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# pkg/public_go/wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
    "context"
    "github.com/soushin/bazel-multiprojects/pkg/public_go/usecase"
)

// Injectors from injector.go:

func initializeGreetUsecase(ctx context.Context, greet string) (usecase.GreetUsecase, error) {
    greetUsecase := usecase.ProvideUseCase(greet)
    return greetUsecase, nil
}

mainパッケージにusecaseが依存した状態で再度、gazelleコマンドを実行するとmainパッケージのBUILD.bazelは次のような差分になる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/pkg/public_go/BUILD.bazel b/pkg/public_go/BUILD.bazel
index 06f7a04..b50739a 100644
--- a/pkg/public_go/BUILD.bazel
+++ b/pkg/public_go/BUILD.bazel
@@ -2,10 +2,16 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

 go_library(
     name = "go_default_library",
-    srcs = ["main.go"],
+    srcs = [
+        "main.go",
+        "wire_gen.go",
+    ],
     importpath = "github.com/soushin/bazel-multiprojects/pkg/public_go",
     visibility = ["//visibility:private"],
-    deps = ["//pkg/common_go/util:go_default_library"],
+    deps = [
+        "//pkg/common_go/util:go_default_library",
+        "//pkg/public_go/usecase:go_default_library",
+    ],
 )

 go_binary(

depsに//pkg/public_go/usecase:go_default_libraryが追加されている。usecaseパッケージに定義しているgo_libraryを参照している。

Gazelleは各パッケージの依存とWORKSPACEの外部依存を理解してBUILDファイルを生成するだけでなくパッケージ間の依存も理解をしてくれる。

Bazelのgo_testでテスト

テストのビルドもBazelで行う。

次のようなstringのテストコードを用意する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# pkg/common_go/util/string_test.go

package util

import "testing"

func TestAdd(t *testing.T) {
    actual := Add("test")
    expected := "test - built by Bazel!"
    if actual != expected {
        t.Errorf("invalid text: got %s want %s", actual, expected)
    }
}

このテストコードを配置した状態でgazelleコマンドを実行するとutilパッケージのBUILD.bazelは次のような差分になる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/pkg/common_go/util/BUILD.bazel b/pkg/common_go/util/BUILD.bazel
index ce90025..d48f3ca 100644
--- a/pkg/common_go/util/BUILD.bazel
+++ b/pkg/common_go/util/BUILD.bazel
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

 go_library(
     name = "go_default_library",
@@ -6,3 +6,9 @@ go_library(
     importpath = "github.com/soushin/bazel-multiprojects/pkg/common_go/util",
     visibility = ["//visibility:public"],
 )
+
+go_test(
+    name = "go_default_test",
+    srcs = ["string_test.go"],
+    embed = [":go_default_library"],
+)

Gazelleがパッケージ内のテストコードを解釈してgo_testのruleを追加してくれる。このgo_testは次のように実行する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(bazel-multiprojects) $ bazel run //pkg/common_go/util:go_default_test 
INFO: Analysed target //pkg/common_go/util:go_default_test (4 packages loaded).
INFO: Found 1 target...
Target //pkg/common_go/util:go_default_test up-to-date:
  bazel-bin/pkg/common_go/util/darwin_amd64_stripped/go_default_test
INFO: Elapsed time: 0.244s, Critical Path: 0.01s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
exec ${PAGER:-/usr/bin/less} "$0" || exit 1
Executing tests from //pkg/common_go/util:go_default_test
-----------------------------------------------------------------------------
PASS

まとめ

コード