はじめに

最初に断っておくと、本記事は具体的な解決策やコードを提示する内容になっていないため、それを求めている方はごめんなさい。

概要

Bazelの特徴として、Hermeticityがあります。日本語で言うと密封性らしい。要はbuildに必要なtoolchain等をBazelのSandbox環境内で揃えることで、環境に依存しない形でbuildができます。これの嬉しい点は、異なる環境(OSやCPU)でも常に同じbuild成果物が出来上がることや、環境をコードとして定義できる点などがあります。

上記のように謳っているにも関わらず、実はBazelのC/C++のbuild-inルールはhermeticになっておらず、buildを実行するマシンのsystem gccに依存しています。(以下Aspect社のBlogに言及あり)

ということで、systemのgccに依存しないようにするためには、何かしら追加の実装をする必要があります。

各社の取り組み

Aspect

Aspect社では、これを解決するために以下のリポジトリでgcc-toolchainのルールを提供しています。

どうやらsystem(実行環境)のライブラリに依存するのを回避するために、gccコンパイラのsysrootオプションを利用してシステムのデフォルトで参照されるライブラリではなく、Bazelで指定したライブラリを利用できるように工夫されている模様です。

sysrootはクロスコンパイル環境でビルドされるプログラムが、ターゲットシステムのヘッダーファイルやライブラリなどのリソースを参照するためにルートディレクトリを設定するオプションです。ユースケースとしては以下が考えられます。

  • ビルド環境がx86_64で、実行環境がaarch64の場合、ビルド時にシステム環境のファイルを参照すると実行環境で正しく動作しない。よって、ビルド環境のシステムファイルを参照せず、sysrootに指定したヘッダーファイルを参照するようにする
  • システムに依存していないファイルを使うと言うことはhermeticになっている

Uber

Uberでは以下のリポジトリでhermeticにする取り組みがされているようです。これは zig ccを使って、hermeticにしてるようです。(zig ccgcc/clangコマンドのほぼ代替コマンド)

考察

Docker Containerでhost環境が整えられればそれで良くないか?

そもそもhostの環境が整えられるのであれば、そんなに神経質にならずにコード管理はContainerfileで管理してしまって、Container上でBazelを動かすことに決めてしまうとシンプルになりそう。ビルドキャッシュ等その他の面でもBazelを使う利点はあるので、Dockerと組み合わせてhermeticにするのはコードの量も少なくなりそうな気がします。(クロスコンパイル等は注意が必要かもしれません。)

Bazelで外部Workspaceに頼らずhermeticにするには?

UberやAspect社のコードがどれだけメンテナンスが続くのかもわからないので、この基本的なtoolchainは自社で作っておきたいところです。いずれBazelの開発が追いついて標準でhermeticなtoolchainが利用できるようになった時にすぐ外せるようにしたいのもあります。

必要なこととしては、以下のようなものがあります。

  1. http_archiveを使って、archiveパッケージをダウンロード
  2. toolchain_configを指定して、downloadしたbinaryのpath定義や、細かいlinker、compilerオプションを定義する
  3. 2. で定義したtoolchain_configを使って、cc_toolchainならびにtoolchainを定義する
  4. register_toolchainでtoolchainを登録する

Aspect社の実装を確認すると同じようなことが実装されてありました。

まずgcc_toolchain_dependencies()で依存パッケージをダウンロードして、cc_toolchain_config.bzlの中で、compileオプションの定義やsanitizerのコマンド等を定義しています。最終的には、以下のようにcc_toolchain_configを作成しています。

...
return [
        cc_common.create_cc_toolchain_config_info(
            abi_libc_version = "local",
            abi_version = "local",
            action_configs = action_configs,
            artifact_name_patterns = [],
            builtin_sysroot = builtin_sysroot,
            cc_target_os = None,
            compiler = "gcc",
            ctx = ctx,
            cxx_builtin_include_directories = cxx_builtin_include_directories,
            features = features,
            host_system_name = "local",
            make_variables = [],
            target_cpu = "local",
            target_libc = "local",
            target_system_name = "local",
            tool_paths = [
                tool_path(name = name, path = path)
                for name, path in tool_paths.items()
            ],
            toolchain_identifier = "local_linux",
        ),
    ]

ですので、こちらのcc_toolchain_configを自分たちが必要な粒度にカスタマイズして利用する形が良いと思います。Aspect社のリポジトリには、Fortranに必要な設定も含まれているようなので、その辺りは除いて実装すると必要なもののみになるのかな。ちなみにtool_pathの設定はdefs.bzl_render_tool_paths()にありました。最後にgcc_register_toolchainを経由して、nativeのregister_toolchainsをcallして環境ごとのtoolchainを登録しています。

このAspect社の実装とBazelの公式Tutorialを参考にhermeticなgccを手にいれることができそうです。とはいえ、ある程度のコードは書かないといけないのと、求めるbuild成果物になっているかの確認が大変そうではあります。