建構樣式指南

回報問題 查看來源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

偏好使用 DAMP BUILD 檔案,而非 DRY

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();' > $@",
)

目標命名

目標名稱應具描述性,如果目標包含一個來源檔案,目標名稱通常應衍生自該來源 (例如,chat.cccc_library 可命名為 chat,或 DirectMessage.javajava_library 可命名為 direct_message)。

套件的同名目標 (與所含目錄同名的目標) 應提供目錄名稱所述的功能。如果沒有這類目標,請勿建立同名目標。

參照同名目標時,請盡量使用簡短名稱 (//x,而非 //x:x)。如果您位於相同套件中,請盡量使用本機參照 (:x,而非 //x)。

避免使用具有特殊意義的「保留」目標名稱。包括 all__pkg____subpackages__,這些名稱具有特殊語意,使用時可能會造成混淆和非預期行為。

如果沒有通用的團隊慣例,以下是 Google 廣泛使用的部分非約束性建議:

  • 一般來說,請使用「snake_case」
    • 如果 java_library 只有一個 src,表示使用的名稱與不含副檔名的檔案名稱不同
    • 如果是 Java *_binary*_test 規則,請使用「Upper CamelCase」。這樣一來,目標名稱就能與其中一個 src 相符。對於 java_test,這可讓 test_class 屬性從目標名稱推斷而來。
  • 如果特定目標有多個變體,請新增後置字元來消除歧義 (例如 :foo_dev:foo_prod:bar_x86:bar_x64)
  • _test_unittestTestTests 為目標的後置字串 _test
  • 避免使用無意義的後置字元,例如 _lib_library (除非為了避免 _library 目標與對應的 _binary 發生衝突,否則請勿使用)
  • 適用於 proto 相關目標:
    • proto_library 目標的名稱結尾應為 _proto
    • 語言專屬的 *_proto_library 規則應與基礎原型相符,但要將 _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 中的目標個別設定。這些可能是專為外部專案依附而設計的程式庫,或是可供外部專案建構程序使用的二進位檔。

依附元件

依附元件應僅限於直接依附元件 (規則中列出的來源所需依附元件)。請勿列出遞移依附元件。

套件本機的依附元件應列在最前面,並以與上方「參照目前套件中的目標」一節相容的方式參照 (而非使用絕對套件名稱)。

建議直接列出依附元件,做為單一清單。將多個目標的「通用」依附元件放入變數中,會降低可維護性,導致工具無法變更目標的依附元件,並可能導致未使用的依附元件。

Globs

使用 [] 表示「沒有目標」。請勿使用不相符的 glob,因為這比空白清單更容易出錯,也較不明顯。

遞迴

請勿使用遞迴 glob 來比對來源檔案 (例如 glob(["**/*.java"]))。

遞迴 glob 會略過含有 BUILD 檔案的子目錄,因此難以推斷 BUILD 檔案。

一般來說,遞迴 glob 的效率不如每個目錄都有 BUILD 檔案,且檔案之間定義了依附元件圖表,因為這樣可提升遠端快取和並行處理的效率。

建議您在每個目錄中撰寫 BUILD 檔案,並定義目錄之間的依附元件關係圖。

非遞迴

一般來說,非遞迴 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,
]]

建議使用較簡單的替代方案。舉例來說,定義一個巨集,產生一項測試,並針對每個頂層 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 檔案擴大為廣泛的符號「程式庫」,導致單一檔案的變更會強制 Bazel 重建許多目標。

其他慣例

  • 使用大寫和底線宣告常數 (例如 GLOBAL_CONSTANT),使用小寫和底線宣告變數 (例如 my_variable)。

  • 即使標籤長度超過 79 個字元,也不應分割。 標籤應盡可能為字串常值。理由:方便尋找及取代。同時提升可讀性。

  • 名稱屬性的值應為常數字串 (巨集除外)。理由:外部工具會使用名稱屬性參照規則。他們需要找出規則,而不必解讀程式碼。

  • 設定布林值類型的屬性時,請使用布林值,而非整數值。 基於舊版原因,規則仍會視需要將整數轉換為布林值,但我們不建議這麼做。理由flaky = 1 可能會誤解為「重新執行一次,即可解決這個目標的問題」。flaky = True 明確指出「這項測試不穩定」。

與 Python 樣式指南的差異

雖然目標是與 Python 樣式指南相容,但仍有幾項差異:

  • 沒有嚴格的行長度限制。長註解和長字串通常會分割成 79 欄,但這並非必要。不應在程式碼審查或預先提交指令碼中強制執行。原因:標籤可能會很長,超出這個限制。工具通常會產生或編輯 BUILD 檔案,這與行長度限制不相容。

  • 系統不支援隱含字串串連。使用 + 運算子。 理由BUILD 檔案包含許多字串清單。很容易忘記逗號,導致結果完全不同。這在過去造成許多錯誤。另請參閱這項討論。

  • 在規則中,關鍵字引數的 = 符號前後要加上空格。理由:具名引數比 Python 更常使用,且一律位於獨立一行。空格可提升可讀性。這項慣例已存在很長一段時間,不值得修改所有現有的 BUILD 檔案。

  • 根據預設,字串會使用雙引號。理由:Python 樣式指南未指定此項目,但建議保持一致。因此我們決定只使用雙引號字串。許多語言會使用雙引號標註字串常值。

  • 在兩個頂層定義之間使用單一空白行。理由BUILD 檔案的結構與一般 Python 檔案不同。當中只包含頂層陳述式。使用單一空白行可縮短 BUILD 檔案。