建構樣式指南

回報問題 查看原始碼 Nightly · 8.1 · 8.0 · 7.5 · 7.4

請使用 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 可以命名為 chatDirectMessage.javajava_library 可以命名為 direct_message)。

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

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

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

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

  • 一般來說,請使用 "snake_case"
    • 對於具有一個 srcjava_library,這表示使用名稱與不含副檔名的檔案名稱不同
    • 針對 Java *_binary*_test 規則,請使用「大寫駝峰式命名法」。這樣一來,目標名稱就能與其中一個 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 相符,但將 _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 個字元,也絕對不應分割。標籤應盡可能使用字串常值。理由:這樣就能輕鬆找出要取代的字詞。並且可提升可讀性。

  • name 屬性的值應為文字常數字串 (宏除外)。原因:外部工具會使用 name 屬性參照規則。他們需要在不解讀程式碼的情況下找出規則。

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

與 Python 樣式指南的差異

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

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

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

  • 在規則中使用關鍵字引數時,請在 = 符號前後加上空格。原因:命名引數比 Python 中的引數更常見,而且一律位於不同行。空格可改善可讀性。這個慣例已存在很長一段時間,因此不建議修改所有現有的 BUILD 檔案。

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

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