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 つのソースファイルが含まれている場合、ターゲットには通常、そのソースから派生した名前を付ける必要があります(たとえば、chat.cc
の cc_library
は chat
、DirectMessage.java
の java_library
は direct_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
、_unittest
、Test
、またはTests
で_test
ターゲットを接尾辞で指定する_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"])
)。
再帰的グロブは、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 は、連結された文字列がラベルであると検出すると、それらを自動的に融合します。
したがって、特に name
や deps
などのラベル型の属性では、連結された文字列やフォーマットされた文字列よりも、明示的なリテラル文字列を使用することをおすすめします。たとえば、次の 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)ファイルによってエクスポートされるシンボル(ルール、マクロ、定数、関数)の数を最小限に抑えます。複数のシンボルを一緒に使用することが確実な場合にのみ、1 つのファイルで複数のシンボルをエクスポートすることをおすすめします。それ以外の場合は、複数の .bzl
ファイルに分割し、それぞれに独自の bzl_library を含めます。
シンボルが多すぎると、.bzl
ファイルがシンボルの広範な「ライブラリ」に拡大し、単一のファイルへの変更によって 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
ファイルが短くなります。