Build スタイルガイド

DRY よりも DAMP BUILD ファイルを優先する

DRY(Don't Repeat Yourself)の原則は、コードの冗長性を回避するために変数や関数などの抽象化を導入することで、一意性を促進します。

一方、DAMP(Descriptive and Meaningful Phrases)の原則は、ファイルが理解しやすく保守しやすいように、一意性よりも読みやすさを重視します。

BUILD ファイルはコードではなく、構成です。コードのようにテストされませんが、人やツールによるメンテナンスが必要です。そのため、DRY よりも DAMP の方が適しています。

BUILD.bazel ファイルのフォーマット

BUILD ファイルのフォーマットは Go と同じアプローチで、標準化されたツールがほとんどのフォーマットの問題を処理します。 Buildifier は、ソースコードを解析して 標準スタイルで出力するツールです。したがって、すべての BUILD ファイルは同じ方法で自動的にフォーマットされるため、コードレビュー中にフォーマットの問題が発生することはありません。また、ツールで BUILD ファイルを簡単に理解、編集、生成できます。

BUILD ファイルのフォーマットは、buildifier の出力と一致する必要があります。

フォーマットの例

# Test code implementing the Foo controller.
package(default_testonly = True)

py_test(
    name = "foo_test",
    srcs = glob(["*.py"]),
    data = [
        "//data/production/foo:startfoo",
        "//foo",
        "//third_party/java/jdk:jdk-k8",
    ],
    flaky = True,
    deps = [
        ":check_bar_lib",
        ":foo_data_check",
        ":pick_foo_port",
        "//pyglib",
        "//testing/pybase",
    ],
)

ファイル構造

推奨事項: 次の順序で記述します(すべての要素は省略可能です)。

  • パッケージの説明(コメント)

  • すべての load() ステートメント

  • package() 関数。

  • ルールとマクロの呼び出し

Buildifier は、スタンドアロン コメントと要素に添付されたコメントを区別します。コメントが特定の要素に添付されていない場合は、その後に空行を使用します。この区別は、自動変更を行う場合(ルールを削除するときにコメントを保持するか削除するかなど)に重要です。

# Standalone comment (such as to make a section in a file)

# Comment for the cc_library below
cc_library(name = "cc")

現在のパッケージ内のターゲットへの参照

ファイルは、パッケージ ディレクトリからの相対パスで参照する必要があります(.. などのアップリファレンスは使用しないでください)。生成されたファイルには、ソースではないことを示す接頭辞 ":" を付ける必要があります。ソースファイルには接頭辞 : を付けないでください。ルールには接頭辞 : を付ける必要があります。たとえば、x.cc がソースファイルの場合、次のようになります。

cc_library(
    name = "lib",
    srcs = ["x.cc"],
    hdrs = [":gen_header"],
)

genrule(
    name = "gen_header",
    srcs = [],
    outs = ["x.h"],
    cmd = "echo 'int x();' > $@",
)

ターゲットの名前付け

ターゲット名には説明的な名前を付ける必要があります。ターゲットにソースファイルが 1 つ含まれている場合、 通常、ターゲットにはそのソースから派生した名前を付けます(たとえば、 cc_librarychat.ccchatjava_libraryDirectMessage.javadirect_message)。

パッケージの同名ターゲット(包含ディレクトリと同じ名前のターゲット)は、ディレクトリ名で説明されている機能を提供する必要があります。そのようなターゲットがない場合は、同名ターゲットを作成しないでください。

同名ターゲットを参照する場合は、短い名前を使用することをおすすめします(//x ではなく //x:x)。同じパッケージ内では、ローカル 参照(:x ではなく //x)を使用することをおすすめします。

特別な意味を持つ「予約済み」のターゲット名は使用しないでください。これには、all__pkg____subpackages__ が含まれます。これらの名前には特別なセマンティクスがあり、使用すると混乱や予期しない動作を引き起こす可能性があります。

一般的なチームの慣例がない場合は、Google で広く使用されている拘束力のない推奨事項をいくつかご紹介します。

  • 一般に、"snake_case"
      を使用します。
    • src が 1 つの java_library の場合、拡張子なしのファイル名と同じ名前は使用しません。
    • Java の *_binary ルールと *_test ルールには、 「Upper CamelCase」を使用します。 これにより、ターゲット名を src のいずれかに一致させることができます。java_test の場合、test_class 属性をターゲットの名前から推測できます。
  • 特定のターゲットに複数のバリアントがある場合は、区別するための接尾辞を追加します(例: :foo_dev:foo_prod:bar_x86:bar_x64)
  • 接尾辞 _test のターゲットには、_test_unittestTestTests を付けます。
  • _lib_library などの意味のない接尾辞は使用しないでください( _library ターゲットとその対応する _binary との競合を回避する必要がある場合を除く)。
  • proto 関連のターゲットの場合:
    • proto_library ターゲットの名前は _proto で終わる必要があります。
    • 言語固有の *_proto_library ルールは、基盤となる proto と一致する必要がありますが、_proto は言語固有の接尾辞に置き換えます。例:
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

公開設定

公開設定は、テストと逆依存関係によるアクセスを許可しながら、できる限りスコープを絞る必要があります。__pkg____subpackages__ は適宜使用してください。

パッケージの default_visibility//visibility:public に設定しないでください。 //visibility:public は、プロジェクトの公開 API のターゲットに対してのみ個別に設定する必要があります。これらは、外部プロジェクトが依存するように設計されたライブラリや、外部プロジェクトのビルドプロセスで使用できるバイナリです。

依存関係

依存関係は、直接的な依存関係(ルールにリストされているソースに必要な依存関係)に限定する必要があります。推移的な依存関係はリストしないでください。

パッケージ ローカルの依存関係は最初にリストし、 上記の現在のパッケージ内のターゲットへの参照 セクションと互換性のある方法で参照する必要があります(絶対パッケージ名ではなく)。

依存関係は、単一のリストとして直接リストすることをおすすめします。複数のターゲットの「共通」の依存関係を変数に配置すると、保守性が低下し、ツールでターゲットの依存関係を変更できなくなり、未使用の依存関係が発生する可能性があります。

glob

[] で「ターゲットなし」を示します。何も一致しない glob は使用しないでください。空のリストよりもエラーが発生しやすく、わかりにくいです。

Recursive

再帰的な glob を使用してソースファイルを照合しないでください(例: glob(["**/*.java"]))。

再帰的な glob は、BUILD ファイルを含むサブディレクトリをスキップするため、BUILD ファイルの理由付けが難しくなります。

通常、再帰的な glob は、ディレクトリごとに BUILD ファイルがあり、それらの間に依存関係グラフが定義されている場合よりも効率が低くなります。これにより、リモート キャッシュと並列処理が向上します。

各ディレクトリに BUILD ファイルを作成し、それらの間に依存関係グラフを定義することをおすすめします。

Non-recursive

通常、非再帰的な glob は許容されます。

リスト内包表記を避ける

BUILD.bazel ファイルの最上位レベルでリスト内包表記を使用しないでください。個別の最上位ルールまたはマクロ呼び出しで名前付きターゲットを作成して、繰り返し呼び出しを自動化します。明確にするために記すと、それぞれに短い name パラメータを指定します。

リスト内包表記は、次の点を削減します。

  • 保守性。人間による保守担当者や大規模な自動変更では、リスト内包表記を正しく更新することが困難または不可能になります。
  • 検出可能性。パターンに name パラメータがないため、名前でルールを見つけるのが困難です。

リスト内包表記パターンの一般的な用途は、テストの生成です。次に例を示します。

[[java_test(
    name = "test_%s_%s" % (backend, count),
    srcs = [ ... ],
    deps = [ ... ],
    ...
) for backend in [
    "fake",
    "mock",
]] for count in [
    1,
    10,
]]

よりシンプルな代替方法を使用することをおすすめします。たとえば、1 つのテストを生成するマクロを定義し、最上位の name ごとに呼び出します。

my_java_test(name = "test_fake_1",
    ...)
my_java_test(name = "test_fake_10",
    ...)
...

deps 変数を使用しない

リスト変数を使用して共通の依存関係をカプセル化しないでください。

COMMON_DEPS = [
  "//d:e",
  "//x/y:z",
]

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = COMMON_DEPS + [ ... ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = COMMON_DEPS + [ ... ],
)

同様に、 exports を使用して依存関係をグループ化しないでください。

代わりに、ターゲットごとに依存関係を個別にリストします。

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

Gazelle などのツールで 管理します。繰り返しになりますが、依存関係の管理方法について考える必要はありません。

リテラル文字列を優先する

Starlark には連結(+)とフォーマット(%)の文字列演算子がありますが、注意して使用してください。共通の文字列部分をファクタリングして、式を簡潔にしたり、長い行を分割したりしたくなります。ただし、次の点にご注意ください。

したがって、連結された文字列やフォーマットされた文字列よりも、明示的なリテラル文字列を優先してください。特に、namedeps などのラベル型の属性では、明示的なリテラル文字列を優先してください。たとえば、次の BUILD フラグメントは

NAME = "foo"
PACKAGE = "//a/b"

proto_library(
  name = "%s_proto" % NAME,
  deps = [PACKAGE + ":other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:" +
            "extravagantly_long_target_name",
)

次のように書き換えることをおすすめします。

proto_library(
  name = "foo_proto",
  deps = ["//a/b:other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)

.bzl ファイルでエクスポートされるシンボルを制限する

公開されている各 .bzl(Starlark)ファイルからエクスポートされるシンボル(ルール、マクロ、定数、関数)の数を最小限に抑えます。ファイルで複数のシンボルをエクスポートするのは、それらが必ず一緒に使用される場合に限ることをおすすめします。それ以外の場合は、複数の .bzl ファイルに分割し、それぞれに独自の bzl_library を含めます。

シンボルが多すぎると、.bzl ファイルがシンボルの広範な「ライブラリ」に拡大し、単一のファイルを変更すると、Bazel が多くのターゲットを再ビルドすることになります。

その他の慣例

  • 定数(GLOBAL_CONSTANT など)を宣言するには大文字とアンダースコアを使用し、変数(my_variable など)を宣言するには小文字とアンダースコアを使用します。

  • ラベルは、79 文字を超えても分割しないでください。 ラベルは、可能な限り文字列リテラルにする必要があります。理由 __: 検索と置換が簡単になります。また、読みやすさも向上します。

  • name 属性の値は、リテラル定数文字列にする必要があります(マクロを除く)。理由 __: 外部ツールは name 属性を使用して ルールを参照します。コードを解釈せずにルールを見つける必要があります。

  • ブール型の属性を設定する場合は、整数値ではなくブール値を使用します。 以前の理由により、ルールは必要に応じて整数をブール値に変換しますが、これはおすすめしません。理由: flaky = 1 は、「このターゲットを 1 回再実行してデフレークする」と誤解される可能性があります。flaky = True は、「このテストは不安定である」と明確に示しています。

Python スタイルガイドとの違い

Python スタイルガイドとの互換性は目標ですが、いくつかの違いがあります。

  • 厳密な行の長さの制限はありません。長いコメントや長い文字列は 79 列に分割されることが多いですが、必須ではありません。コードレビューや presubmit スクリプトで強制しないでください。理由 __: ラベルが長くなり、この 制限を超える可能性があります。BUILD ファイルはツールによって生成または編集されることが多く、行の長さの制限には適していません。

  • 暗黙的な文字列連結はサポートされていません。+ 演算子を使用します。理由 __: BUILD ファイルには多くの文字列リストが含まれています。カンマを忘れると、まったく異なる結果になります。過去に多くのバグが発生しています。こちらのディスカッションもご覧ください。

  • ルールのキーワード引数には = 記号の前後にスペースを使用します。理由: 名前付き引数は Python よりもはるかに頻繁に使用され、常に別の行に記述されます。スペースを使用すると読みやすくなります。この慣例は長年使用されており、既存のすべての BUILD ファイルを変更する価値はありません。

  • デフォルトでは、文字列には二重引用符を使用します。理由 __: これは Python スタイルガイドで 指定されていませんが、一貫性を保つことをおすすめします。そのため、二重引用符で囲まれた文字列のみを使用することにしました。多くの言語では、文字列リテラルに二重引用符を使用します。

  • 2 つの最上位定義の間には 1 行の空白行を使用します。理由 __: ファイルの構造は、一般的な Python ファイルとは異なります。BUILD最上位のステートメントのみが含まれます。1 行の空白行を使用すると、BUILD ファイルが短くなります。