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_library の chat.cc は chat、java_library の
DirectMessage.java は direct_message)。
パッケージの同名ターゲット(包含ディレクトリと同じ名前のターゲット)は、ディレクトリ名で説明されている機能を提供する必要があります。そのようなターゲットがない場合は、同名ターゲットを作成しないでください。
同名ターゲットを参照する場合は、短い名前を使用することをおすすめします(//x:x ではなく //x)。同じパッケージ内にある場合は、ローカル参照(//x ではなく :x)を使用することをおすすめします。
特別な意味を持つ「予約済み」のターゲット名は使用しないでください。これには、all、__pkg__、__subpackages__ が含まれます。これらの名前には特別なセマンティクスがあり、使用すると混乱や予期しない動作を引き起こす可能性があります。
チームの慣例がない場合は、Google で広く使用されている拘束力のない推奨事項をいくつかご紹介します。
- 一般に、"snake_case"
-
を使用します。
- 1 つの
srcを持つjava_libraryの場合、拡張子なしのファイル名と同じ名前は使用しません。 - Java の
*_binaryルールと*_testルールには、 「Upper CamelCase」を使用します。 これにより、ターゲット名がsrcのいずれかと一致します。java_testの場合、test_class属性をターゲット名から推測できます。
- 1 つの
- 特定のターゲットに複数のバリアントがある場合は、曖昧さを解消するために接尾辞を追加します(例:
:foo_dev、:foo_prod、:bar_x86、:bar_x64) - 接尾辞
_testのターゲットには、_test、_unittest、Test、Testsを付けます。 _libや_libraryなどの意味のない接尾辞は使用しないでください(_libraryターゲットとその対応する_binaryとの競合を回避する必要がある場合を除く)。- proto 関連のターゲットの場合:
proto_libraryターゲットの名前は_protoで終わる必要があります。- 言語固有の
*_proto_libraryルールは、基盤となる proto と一致する必要がありますが、_protoは言語固有の接尾辞に置き換えます。例:cc_proto_library:_cc_protojava_proto_library:_java_protojava_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 には連結(+)とフォーマット(%)の文字列演算子がありますが、注意して使用してください。共通の文字列部分をファクタリングして、式を簡潔にしたり、長い行を分割したりしたくなります。ただし、
分割された文字列値を一目で読み取るのは困難です。
buildozer やソースコード検索などの自動ツールでは、値が分割されている場合に値を検索して正しく更新することが困難です。
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)ファイルからエクスポートされるシンボル(ルール、マクロ、定数、関数)の数を最小限に抑えます。ファイルで複数のシンボルをエクスポートする場合は、それらを一緒に使用することが確実な場合にのみ行うことをおすすめします。それ以外の場合は、複数の .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 つの空白行を使用します。理由: ファイルの構造は、一般的な Python ファイルとは異なります。
BUILD最上位のステートメントのみが含まれます。1 つの空白行を使用すると、BUILDファイルが短くなります。