はじめに
Go 言語、Bazel および、Protocol Buffers
を組み合わせたプロジェクト作成でハマった内容を記載する。
この記事を書いたのは、protobuf 定義で、外部定義(Well-Known Types)をインポートして使用する場合に、依存関係の解決ができず、悶絶したためとなる。
作成するプロジェクトでは、gRPC で protobuf を利用する設定を行う。
この手順が正解か不明だが、参考になれば幸いである。
環境
bazel を Windows 上で利用する場合、rule によっては、shell
が必須となる。(bazel は、徐々に shell に依存しない方向に向かっている模様)
通常は、MSYS2 を利用することで実行できるが、rule
によっては、対応していない場合もあるようだ。
当方の Windows 環境では、gazelle 関連の rule
が動作しないため(原因は未調査だが、Windows は未サポートなのかも)、bat
ファイルで実行する。
shell が必須となることも想定して、WSL 上でも動作確認はしておく。
プロジェクト自体は、Windows 側のディレクトリに作成する。
WSL 上の開発環境は、この記事を参考に構築する。
ここでは、Go 言語をインストールした開発環境を前提とする。
WSL 上の開発環境を、以降「開発環境」と表記する。
- Windows 10
- WSL2(Ubuntu-20.04)
利用ツール
Bazelisk は、bazel
のラッパーコマンドであり、プロジェクト毎に設定したバージョンの bazel
を自動的にダウンロードして実行してくれる優れもの。
bazel のみインストールすれば、Go
をインストールせずとも、以下のようなコマンドで、コマンドが実行可能だが、Bazelisk
を簡単にインストールするために、Go をインストールしている。
bazelisk run @go_sdk//:bin/go -- version
IntelliJ は、必須ではない。
- Go 1.19.3
- Bazelisk
- IntelliJ
環境構築
Windows および、開発環境の構築を行う。
Windows には、Go および、IntellilJ がインストール済みとする。
インストール方法は、割愛する。当方では、パッケージ管理ツールの
scoop
を利用している。
Bazelisk は、Windows および WSL 環境に、以下のコマンドでインストールする。
go install github.com/bazelbuild/bazelisk@latest
bazel は、WSL と Windows
上の両方で動作させてみたが、今のところ動作しているように見える。
ただし、bazel のビルド作業用ディレクトリは、Windows と WSL
では、別の場所にシンボリックリンクが設定されるため、bazel
で実行する時に、場合によってはシンボリックリンクが、別の環境用のままとなり、エラーとなる。その場合は、bazelisk
build //server
などのような別のコマンドを実行して、シンボリックリンクが再作成されるようにするとよい。
プロジェクトの作成
以降の説明は、サンプルプロジェクトを確認しながら読み進めるとよい。
任意の方法で、プロジェクトを作成する。ここでは、IntelliJ で Go
プロジェクトを作成した。
IntelliJ では、go.mod
ファイルを自動で作成してくれる。無い場合は、手動で作成する。
protobuf の定義ファイルを作成
proto ディレクトリに、profobuf の定義ファイルを格納する。
user.proto
ファイルでは、外部定義(google/protobuf/any.proto)の参照を行っている。
bazel の設定ファイルを作成
bazel の設定ファイルは、プロジェクトルートディレクトリに、WORKSPACE.bazel
および、BUILD.bazel ファイルを作成する。
bazel の設定ファイルの拡張子は、無しでも認識されるが、WORKSPACE.bazel
の設定を若干変更する必要があるので注意。
go の依存パッケージを追加
gRPC を go で利用するために、パッケージを手動で追加しておく。
これにより、protobuf の定義でインポートしている外部定義も解決できる。
依存関係をすべて手作業で bazel
定義ファイルに追加する方法もあるが、やりたくない。
プロジェクトルートで以下のコマンドを実行すると、go.mod ファイルが更新される。
go get -u google.golang.org/grpc
依存パッケージを事前に追加しない場合、以下のようなエラーで protobuf
の定義がビルドできない。
compilepkg: missing strict dependencies:
/home/ubuntu/.cache/bazel/_bazel_ubuntu/60c27803c2e555e5df9e00daff60b40e/sandbox/linux-sandbox/103/execroot/__main__/external/org_golang_google_grpc/internal/syscall/syscall_linux.go: import of "golang.org/x/sys/unix"
No dependencies were provided.
Check that imports in Go sources match importpath attributes in deps.
以下のコマンドで、go.mod の依存関係を WORKSPACE.bazel
ファイルに反映する。gazelle-update-repos
コマンドは、プロジェクトルートの BUILD.bazel ファイルに定義している。
Windows の場合
build-tools\run_gazelle_update_repos.bat
WSL の場合
bazelisk run //:gazelle-update-repos
コマンドを実行すると、deps.bzl ファイルが新規作成され、WORKSPACE.bazel
ファイルに以下の行が追加される。
load("//:deps.bzl", "go_dependencies")
# gazelle:repository_macro deps.bzl%go_dependencies
go_dependencies()
次に、プロジェクトルート以外の BUILD.bazel を自動生成する。
プロジェクトルートで以下のコマンドを実行する。
Windows の場合
build-tools\run_gazelle.bat
WSL の場合
bazelisk run //:gazelle
proto/BUILD.bazel ファイルが作成される。
次に、protobuf の定義をビルドする。
bazelisk build //proto
生成されたソースは、bazel
のサンドボックス内(bazel-out/k8-fastbuild/bin/proto/proto_go_proto_/github.com/i-chi-li/example-go-bazel-protobuf/proto/address.pb.go
など)に格納されている。
bazel
のデフォルト動作では、ホームディレクトリにビルドしたファイルなどが格納される。
これらのファイルは、Windows 側から直接参照できないため、IntelliJ
などから、protobuf で生成したソースをコード補完などで利用できない。
bazel のビルドファイルの出力先は、オプション(--output_base
、--output_user_root)で変更できるが、現時点ではエラー(external/go_sdk/pkg/tool/linux_amd64/link:
mapping output file failed: input/output error)となる。(原因は未調査)
そのため、生成したソースをサンドボックスからプロジェクト側にコピーする。
手動で行っても良いが、以下を参考に bazel でコマンド化することにする。
コマンドの実装は、build-tools/copy_proto.bzl
に記載してあり、プロジェクトルートの BUILD.bazel
ファイル内から呼び出している(copy_proto)。
以下のコマンドを実行すると、protobuf で生成したファイルを generated/proto
ディレクトリにコピーされる。
bazelisk run //:copy-proto
サンプルの実行
サンプルサーバとクライアントは、以下のように実行可能。
Windows 側で実行することもできる。
bazel で実行する場合。
bazelisk run //server
bazelisk run //client
go コマンド(環境にインストールした go )で実行する場合。
go run server/main.go
go run client/main.go
Windows 上で go コマンド(bazel で定義した go)で実行する場合。(.exe 拡張子をつけないと動作しなかった)
bazelisk run @go_sdk//:bin/go.exe -- run server/main.go
bazelisk run @go_sdk//:bin/go.exe -- run client/main.go
WSL 上で go コマンド(bazel で定義した go)で実行する場合。
bazelisk run @go_sdk//:bin/go -- run server/main.go
bazelisk run @go_sdk//:bin/go -- run client/main.go
最後に
見よう見真似でやってみたが、bazel の入り口にも到達できていない気がする。
特に Windows 上では、一筋縄ではいかない。
自動生成したファイルを、ワークスペースにコピーするような機能は、bazel
のコミュニティでも話題になっているようで、なにかしらの機能として取り込まれつつあるようだが、詳細はわからなかった。
今回作成した、protobuf で生成したファイルをコピーするルールは、bazel
の理念からは逸脱しているような気がする・・・
bazel
の言語を含むツール類を、ビルド時にダウンロードして、環境の違いによって、問題が発生し難くする思想は、たいへん惹きつけられるのだが、如何せん学習コストが高い気がする。導入部分をやってみて、どうにもすっきりしなかった。まだまだ自己研鑽が必要だと思った。
bazel 自体も、Windows 上で遜色なく動作するように、shell に依存しない機能が追加されていくようなので、今後に期待したい。