MIXI DEVELOPERS NOTE
🥬

Bazel 6.0.0 にアップデートしたときの対応メモ

2023/02/07に公開

会社のモノレポで使ってる Bazel を5系から 6.0.0 にあげたときのメモです。

https://github.com/bazelbuild/bazel/releases/tag/6.0.0

使ってるルールは

このうち、いくつかは対応が必要だった(あるいは最新にアップデートする必要があった)ので、それについてメモしておきます。

rules_k8s

rules_k8s は kubectlbazel run で行えるようにするルールです。k8s_object を使うことで bazel runkubectl apply ができるようになります。

こういう感じのエラーメッセージが出ました:

$ bazelisk test //path/to/subproject/...
ERROR: /sandbox/external/bazel_tools/platforms/BUILD:19:6: in alias rule @bazel_tools//platforms:x86_64: Constraints from @bazel_tools//platforms have been removed. Please use constraints from @platforms repository embedded in Bazel, or preferably declare dependency on https://github.com/bazelbuild/platforms. See https://github.com/bazelbuild/bazel/issues/8622 for details.
ERROR: /sandbox/external/bazel_tools/platforms/BUILD:19:6: Analysis of target '@bazel_tools//platforms:x86_64' failed
ERROR: /path/to/project/oapi-codegen/cmd/oapi-codegen/BUILD.bazel:15:10: While resolving toolchains for target //oapi-codegen/cmd/oapi-codegen:oapi-codegen: invalid registered toolchain '@io_bazel_rules_k8s//toolchains/kubectl:kubectl_windows_toolchain': 
...

Bazel では基本的に、依存する外部ツールをホストの OS や CPU に合わせてインストールし直します(設定によってはホストにインストールされたものを使うことも可能です)。この時には、ツールチェインという仕組みを使うのが一般的です。ツールチェインを使うときに OS や CPU を判別するために @platforms//os:linux みたいのを使うのですが、昔は @bazel_tools/platforms ってのを使ってたみたいです(たぶん)。かなり長いこと移行期間が設けられており、その間は --incompatible_use_platforms_repo_for_constraints オプションを使うことで @bazel_tools/platforms が使えなくなります。Bazel 6.0.0 からはこのオプションがデフォルトでオンになりました

rules_k8s は、v0.6 まで @bazel_tools/platforms を使っていたため Bazel 6.0.0 からエラーが出るようになったわけです。--incompatible_use_platforms_repo_for_constraints=false オプションを追加しても良いですが、v0.7 で修正されているので今回はアップデートして対応しました:

https://github.com/bazelbuild/rules_k8s/pull/675

ただし注意点として、release にある通りに WORKSPACE を記述すると:

http_archive(
    name = "io_bazel_rules_k8s",
    sha256 = "ce5b9bc0926681e2e7f2147b49096f143e6cbc783e71bc1d4f36ca76b00e6f4a",
    strip_prefix = "rules_k8s-0.7",
    urls = ["https://github.com/bazelbuild/rules_k8s/archive/refs/tags/v0.7.tar.gz"],
)

load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories")

k8s_repositories()

load("@io_bazel_rules_k8s//k8s:k8s_go_deps.bzl", k8s_go_deps = "deps")

k8s_go_deps()

urls のが 500 ですというエラーが返ってきました(一時的なものかも?)。仕方ないので、git_repository を使うことにしました:

git_repository(
    name = "io_bazel_rules_k8s",
    remote = "https://github.com/bazelbuild/rules_k8s.git",
    commit = "fee80eb69e1921c076167ebebcf5eea3d2e9c707", # v0.7
    shallow_since = "1655492445 -0700",
)

また、rules_go を使っていると呼び出し順によっては次のようなエラーメッセージが出ます:

ERROR: Traceback (most recent call last):
	File "/path/to/project/WORKSPACE", line 270, column 12, in <toplevel>
		k8s_go_deps()
	File "/sandbox/external/io_bazel_rules_k8s/k8s/k8s_go_deps.bzl", line 36, column 31, in deps
		go_register_toolchains(go_version)
	File "/sandbox/external/io_bazel_rules_go/go/private/sdk.bzl", line 557, column 13, in go_register_toolchains
		fail("go_register_toolchains: version set after go sdk rule declared ({})".format(", ".join([r["name"] for r in sdk_rules])))
Error in fail: go_register_toolchains: version set after go sdk rule declared (go_sdk, go_sdk_old)
...

k8s_go_deps は v0.7 から、go_register_toolchains を使ってインストールする Go のバージョンを指定できるようになりました。指定しない場合も、何かしらのバージョンをデフォルトとして設定します。go_register_toolchains はバージョンを指定する場合、既にインストール済みの場合はエラーが返るようになってます。WORKSPACE ファイルで先に go_register_toolchains を実行していたためエラーになったというわけです。
なので実行順を変えるか、k8s_go_deps(go_version = "") とすることで go_register_toolchains の実行をスキップできるので、エラーを回避できます。

rules_pkg

rules_pkg は rules_docker でイメージを構築する時に配置する実行ファイルをパッケージ化したりするのに使っています:

go_binary(
    name = "cli",
    ...
)

pkg_tar(
    name = "bin",
    srcs = ["//path/to/subproject:cli"],
    mode = "0755",
    package_dir = "/usr/local/bin",
)

container_image(
    name = "image",
    base = "@com_google_distroless_base_debian11//image",
    entrypoint = ["/usr/local/bin/cli"],
    tars = [":bin"],
)

Bazel 6.0.0 にしたら次のようなエラーが出るようになりました:

$ bazelisk build //path/to/subproject:image
INFO: Build option --platforms has changed, discarding analysis cache.
INFO: Analyzed target //path/to/subproject:image (619 packages loaded, 14433 targets configured).
INFO: Found 1 target...
ERROR: /path/to/subproject/BUILD.bazel:43:8: PackageTar subproject/bin.tar failed: (Exit 127): build_tar failed: error executing command (from target //path/to/subproject:bin) bazel-out/darwin-py2-opt-exec-2B5CBBC6-ST-5f994f96b8a3/bin/external/rules_pkg/build_tar @bazel-out/darwin-fastbuild-ST-5f994f96b8a3/bin/path/to/subproject/bin.args

Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging
env: python2: No such file or directory
Target //path/to/subproject:image failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 124.863s, Critical Path: 65.73s
INFO: 447 processes: 2 internal, 445 darwin-sandbox.
FAILED: Build did NOT complete successfully

Python2 を使おうとするがないっぽくてエラーが出てそうです。

解決方法としては簡単で、rules_docker のインストールの前に rules_pkg のインストールを記述するだけです。なぜ Bazel を上げるだけでエラーが起きるようになったかというと、恐らくですが、Bazel 6.0.0 からビルトインであった pkg_tar が撤去された(rules_pkg を使いましょうとのこと)ようで、そのせいで暗黙で解決されていた依存関係が解決されなくなったのが原因っぽいです(参考)。つまり、元から rules_pkg を rules_docker より先にインストールすべきだが、ビルトインの pkg_tar のおかげでそうなっていたと。

インストール順を入れ替えたところ、代わりに次のようなエラーが出るようになりました:

ERROR: /sandbox/external/bazel_tools/platforms/BUILD:89:6: in alias rule @bazel_tools//platforms:windows: Constraints from @bazel_tools//platforms have been removed. Please use constraints from @platforms repository embedded in Bazel, or preferably declare dependency on https://github.com/bazelbuild/platforms. See https://github.com/bazelbuild/bazel/issues/8622 for details.
ERROR: /sandbox/external/bazel_tools/platforms/BUILD:89:6: Analysis of target '@bazel_tools//platforms:windows' failed
ERROR: /path/to/project/subproject/BUILD.bazel:53:16: While resolving toolchains for target //subproject:image: invalid registered toolchain '@bazel_skylib//toolchains/unittest:cmd_toolchain': 
...

rules_k8s と同じ @bazel_tools//platforms を使っているせいですね。@bazel_tools を使っているのは rules_pkg が依存している bazel_skylib の方で、そっちはかなり前に修正されてそうです:

https://github.com/bazelbuild/bazel-skylib/pull/214

rules_pkg 側がそのアップデートを取り込んだのが比較的最近っぽく:

https://github.com/bazelbuild/rules_pkg/pull/502

なので、最新にアップデートすれば修正されました。

おまけ:container_run_and_extractpkg_tar

CI/CD 用に、Ruby の入った Docker イメージを Bazel で次のような感じで作っていました:

download_pkgs(
    name = "build_ruby_package",
    image_tar = "@ubuntu_bionic//image",
    packages = [
        "git",
        "curl",
        "libssl-dev",
        "libreadline-dev",
        "build-essential",
        "zlib1g-dev",
        "ca-certificates",
    ],
)

install_pkgs(
    name = "image_build_ruby",
    image_tar = "@ubuntu_bionic//image",
    installables_tar = ":build_ruby_package.tar",
    installation_cleanup_commands = "rm -rf /var/lib/apt/lists/*",
    output_image_name = "image_build_ruby",
)

# RUBY_VERSION と BUNDLER_VERSION は適当に置き換えてください
container_run_and_extract(
    name = "build_ruby",
    commands = [
        "git clone --depth=1 https://github.com/rbenv/ruby-build.git",
        "CONFIGURE_OPTS='--disable-install-doc' ruby-build/bin/ruby-build RUBY_VERSION /root/ruby",
        "echo 'gem: --no-rdoc --no-ri' >> /.gemrc",
        "export PATH=/root/ruby/bin:$PATH",
        "gem install bundler -v BUNDLER_VERSION",
    ],
    extract_file = "/root/ruby",
    image = ":image_build_ruby.tar",
)

pkg_tar(
    name = "ruby",
    srcs = [":build_ruby/root/ruby"],
    mode = "0755",
    package_dir = "/root",
)

container_image(
    name = "image",
    base = ":image_base_package",
    tars = [
        ":ruby",
        ":reviewdog",
	# 他に一緒に入れたい実行ファイルなどなど
    ],
    env = {
        "PATH": "/root/ruby/bin:$$PATH"
    },
)

rules_docker の container_run_and_extract は Docker コンテナで実行して得た結果のファイルを Bazel の生成物として使える関数です。これを container_image と組み合わせることで、Dockerfile のマルチステージビルドのようなものを表現できます。Bazel 的にはあんまりお行儀の良い書き方ではないですが、Ruby 処理系はシングルバイナリで配布されてないですし、クロスコンパイルも厳しいので苦肉の策です。

rules_pkg のバージョンを上げたところ、上記の pkg_tar の部分で次の Issue と同じようなエラーメッセージが出るようになりました:

https://github.com/bazelbuild/rules_pkg/pull/647

pkg_tar がちゃんと TreeArtifact に対応したところ、ただのディレクトリは受け付けないようになったようです。container_run_and_extract を使っているとはいえ、そもそもディレクトリを取り回すのは Bazel ではアンチパターンっぽく、rules_docker の例やテストを見てもディレクトリを取り回してるものは見当たりませんでした。そこでとりあえず、container_run_and_extract の最後で tar をすることにしました:

container_run_and_extract(
    name = "build_ruby",
    commands = [
        "git clone --depth=1 https://github.com/rbenv/ruby-build.git",
        "CONFIGURE_OPTS='--disable-install-doc' ruby-build/bin/ruby-build RUBY_VERSION /root/ruby",
        "echo 'gem: --no-rdoc --no-ri' >> /.gemrc",
        "export PATH=/root/ruby/bin:$PATH",
        "gem install bundler -v BUNDLER_VERSION",
	"tar -cf /ruby.tar /root/ruby",
    ],
    extract_file = "/ruby.tar",
    image = ":image_build_ruby.tar",
)

container_image(
    name = "image",
    base = ":image_base_package",
    tars = [
        ":build_ruby/ruby.tar",
        ":reviewdog",
	# 他に一緒に入れたい実行ファイルなどなど
    ],
    env = {
        "PATH": "/root/ruby/bin:$$PATH"
    },
)

おしまい

大体は Bazel ルール間の依存関係の問題でした。Bazel の問題に、いわゆる依存パッケージをいい感じに管理するパッケージマネージャー的なのがないというのがあります。しかし実は、Bazel 6.0.0 からは Bzlmod というパッケージマネージャーっぽいのが導入されています(Bazel 5系から experimental として導入はされていた)。毎回痛い目見るので次はこれを試してみようと思います。

MIXI DEVELOPERS NOTE
MIXI DEVELOPERS NOTE

Discussion