Build スタイルガイド

問題を報告 ソースを表示 ナイトリー · 8.1 · 8.0 · 7.5 · 7.4

DRY ではなく DAMP BUILD ファイルを優先する

DRY 原則(「繰り返しを避けること」)では、変数や関数などの抽象化を導入してコードの冗長性を回避することで、一意性を促進します。

対照的に、DAMP 原則(「記述的で意味のあるフレーズ」)では、一意性よりも読みやすさを重視し、ファイルを理解しやすく、メンテナンスしやすいようにします。

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

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 つのソースファイルが含まれている場合、通常、ターゲットにはそのソースから派生した名前を付けます(たとえば、chat.cccc_librarychat という名前にできます。また、DirectMessage.javajava_librarydirect_message という名前にできます)。

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

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

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

チームの慣例がない場合は、Google で広く使用されている拘束力のない推奨事項を以下に示します。

  • 通常は 「snake_case」 を使用します。
    • src が 1 つの java_library の場合、拡張子のないファイル名とは異なる名前を使用します。
    • Java の *_binary ルールと *_test ルールの場合は、「大文字のケイマンケース」を使用します。これにより、ターゲット名を src のいずれかに一致させることができます。java_test の場合、ターゲットの名前から test_class 属性を推測できます。
  • 特定のターゲットに複数のバリエーションがある場合は、末尾に接尾辞を追加して区別します(例: :foo_dev:foo_prod:bar_x86:bar_x64
  • _test ターゲットの接尾辞を _test_unittestTest、または Tests に変更
  • _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

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

Recursive

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

再帰的なグロブを使用すると、BUILD ファイルを含むサブディレクトリがスキップされるため、BUILD ファイルの推論が難しくなります。

一般に、再帰的なグロブは、ディレクトリごとに BUILD ファイルがあり、それらの間に依存関係グラフが定義されている場合よりも効率が低くなります。これは、リモート キャッシュと並列処理を改善できるためです。

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

非再帰

再帰的でないグロブは通常使用できます。

リスト内包を避ける

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 には、連結(+)とフォーマット(%)用の文字列演算子が用意されていますが、使用には注意が必要です。式を簡潔にしたり、長い行を分割したりするために、共通の文字列部分を分離したくなります。ただし、

  • 分割された文字列値は一目で読み取れません。

  • buildozer や Code Search などの自動ツールでは、値が分割されている場合、値を見つけて正しく更新することが困難です。

  • BUILD ファイルでは、繰り返しを回避するよりも読みやすさが重要です(DAMP と DRY をご覧ください)。

  • このスタイルガイドでは、ラベル値の文字列を分割しないよう警告し、長い行を明示的に許可しています。

  • Buildifier は、連結された文字列がラベルであることを検出すると、その文字列を自動的に統合します。

したがって、特に 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 ファイルがシンボルの広範な「ライブラリ」に成長し、1 つのファイルの変更によって Bazel が多くのターゲットを再ビルドする可能性があります。

その他の規則

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

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

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

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

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

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

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

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

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

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

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