Lebih memilih file DAMP BUILD daripada DRY
Prinsip DRY — "Jangan Ulangi Diri Anda" — mendorong keunikan dengan memperkenalkan abstraksi seperti variabel dan fungsi untuk menghindari redundansi dalam kode.
Sebaliknya, prinsip DAMP — "Descriptive and Meaningful Phrases" (Frasa Deskriptif dan Bermakna) — mendorong keterbacaan daripada keunikan untuk membuat file lebih mudah dipahami dan dikelola.
File BUILD bukan kode, melainkan konfigurasi. Tidak diuji seperti kode, tetapi perlu dikelola oleh orang dan alat. Hal ini membuat DAMP lebih baik
bagi mereka daripada DRY.
Pemformatan file BUILD.bazel
Pemformatan file BUILD mengikuti pendekatan yang sama dengan Go, di mana alat
standar menangani sebagian besar masalah pemformatan.
Buildifier adalah alat yang mem-parsing dan
memancarkan kode sumber dalam gaya standar. Oleh karena itu, setiap file BUILD diformat dengan cara otomatis yang sama, sehingga pemformatan tidak menjadi masalah selama peninjauan kode. Hal ini juga mempermudah alat untuk memahami, mengedit, dan membuat file BUILD.
Pemformatan file BUILD harus sesuai dengan output buildifier.
Contoh pemformatan
# 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",
],
)
Struktur file
Rekomendasi: Gunakan urutan berikut (setiap elemen bersifat opsional):
Deskripsi paket (komentar)
Semua pernyataan
load()Fungsi
package().Panggilan ke aturan dan makro
Buildifier membedakan antara komentar mandiri dan komentar yang dilampirkan ke elemen. Jika komentar tidak dilampirkan ke elemen tertentu, gunakan baris kosong setelahnya. Perbedaan ini penting saat melakukan perubahan otomatis (misalnya, untuk menyimpan atau menghapus komentar saat menghapus aturan).
# Standalone comment (such as to make a section in a file)
# Comment for the cc_library below
cc_library(name = "cc")
Referensi ke target dalam paket saat ini
File harus dirujuk berdasarkan jalur relatifnya ke direktori paket
(tanpa pernah menggunakan referensi atas, seperti ..). File yang dihasilkan harus
diberi awalan ":" untuk menunjukkan bahwa file tersebut bukan sumber. File sumber
tidak boleh diawali dengan :. Aturan harus diawali dengan :. Misalnya, dengan asumsi x.cc adalah file sumber:
cc_library(
name = "lib",
srcs = ["x.cc"],
hdrs = [":gen_header"],
)
genrule(
name = "gen_header",
srcs = [],
outs = ["x.h"],
cmd = "echo 'int x();' > $@",
)
Penamaan target
Nama target harus deskriptif. Jika target berisi satu file sumber, target umumnya harus memiliki nama yang berasal dari sumber tersebut (misalnya, cc_library untuk chat.cc dapat diberi nama chat, atau java_library untuk DirectMessage.java dapat diberi nama direct_message).
Target eponim untuk paket (target dengan nama yang sama dengan direktori yang berisi) harus menyediakan fungsi yang dijelaskan oleh nama direktori. Jika tidak ada target tersebut, jangan buat target dengan nama yang sama.
Sebaiknya gunakan nama pendek saat merujuk ke target eponim (//x, bukan //x:x). Jika Anda berada dalam paket yang sama, sebaiknya gunakan referensi lokal (:x, bukan //x).
Hindari penggunaan nama target "reserved" yang memiliki arti khusus. Hal ini mencakup
all, __pkg__, dan __subpackages__. Nama-nama ini memiliki semantik
khusus dan dapat menyebabkan kebingungan serta perilaku yang tidak terduga saat digunakan.
Jika tidak ada konvensi tim yang berlaku, berikut adalah beberapa rekomendasi tidak mengikat yang banyak digunakan di Google:
- Secara umum, gunakan "snake_case"
- Untuk
java_librarydengan satusrc, ini berarti menggunakan nama yang tidak sama dengan nama file tanpa ekstensi - Untuk aturan Java
*_binarydan*_test, gunakan "Upper CamelCase". Hal ini memungkinkan nama target cocok dengan salah satusrc. Untukjava_test, hal ini memungkinkan atributtest_classdisimpulkan dari nama target.
- Untuk
- Jika ada beberapa varian target tertentu, tambahkan akhiran untuk
membedakan (seperti.
:foo_dev,:foo_prod, atau:bar_x86,:bar_x64) - Menambahkan akhiran
_testke target dengan_test,_unittest,Test, atauTests - Hindari akhiran yang tidak bermakna seperti
_libatau_library(kecuali jika diperlukan untuk menghindari konflik antara target_librarydan_binaryyang sesuai) - Untuk target terkait proto:
- Target
proto_libraryharus memiliki nama yang diakhiri dengan_proto - Aturan
*_proto_librarykhusus bahasa harus cocok dengan proto yang mendasarinya, tetapi mengganti_protodengan akhiran khusus bahasa seperti:cc_proto_library:_cc_protojava_proto_library:_java_protojava_lite_proto_library:_java_proto_lite
- Target
Visibilitas
Visibilitas harus dicakup seketat mungkin, sambil tetap mengizinkan akses
oleh pengujian dan dependensi terbalik. Gunakan __pkg__ dan __subpackages__ sesuai
kebutuhan.
Hindari menyetel paket default_visibility ke //visibility:public.
//visibility:public harus ditetapkan satu per satu hanya untuk target di API publik project. Ini bisa berupa library yang dirancang untuk diandalkan oleh project eksternal atau biner yang dapat digunakan oleh proses build project eksternal.
Dependensi
Dependensi harus dibatasi pada dependensi langsung (dependensi yang diperlukan oleh sumber yang tercantum dalam aturan). Jangan mencantumkan dependensi transitif.
Dependensi lokal paket harus dicantumkan terlebih dahulu dan dirujuk dengan cara yang kompatibel dengan bagian Referensi ke target dalam paket saat ini di atas (bukan berdasarkan nama paket absolutnya).
Lebih baik mencantumkan dependensi secara langsung, sebagai satu daftar. Menempatkan dependensi "umum" beberapa target ke dalam variabel mengurangi kemudahan pemeliharaan, membuat alat tidak dapat mengubah dependensi target, dan dapat menyebabkan dependensi yang tidak digunakan.
Gumpalan
Tunjukkan "tidak ada target" dengan []. Jangan gunakan glob yang tidak cocok dengan apa pun: hal ini lebih rentan terhadap error dan kurang jelas daripada daftar kosong.
Rekursif
Jangan gunakan glob rekursif untuk mencocokkan file sumber (misalnya,
glob(["**/*.java"])).
Glob rekursif membuat file BUILD sulit dipahami karena file tersebut melewati subdirektori yang berisi file BUILD.
Glob rekursif umumnya kurang efisien dibandingkan memiliki file BUILD per
direktori dengan grafik dependensi yang ditentukan di antaranya karena hal ini memungkinkan
paralelisme dan caching jarak jauh yang lebih baik.
Sebaiknya buat file BUILD di setiap direktori dan tentukan
grafik dependensi di antaranya.
Non-rekursif
Glob non-rekursif umumnya dapat diterima.
Menghindari pemahaman daftar
Hindari penggunaan pemahaman daftar di tingkat teratas file BUILD.bazel.
Mengotomatiskan panggilan berulang dengan membuat setiap target bernama dengan panggilan makro atau aturan tingkat teratas yang terpisah. Beri setiap parameter name singkat agar lebih jelas.
Pemahaman daftar mengurangi hal berikut:
- Kemudahan pemeliharaan. Pengelola manusia dan perubahan otomatis skala besar sulit atau tidak mungkin memperbarui pemahaman daftar dengan benar.
- Kemudahan untuk ditemukan. Karena pola tidak memiliki parameter
name, sulit untuk menemukan aturan berdasarkan nama.
Penerapan umum pola pemahaman daftar adalah untuk membuat pengujian. Contoh:
[[java_test(
name = "test_%s_%s" % (backend, count),
srcs = [ ... ],
deps = [ ... ],
...
) for backend in [
"fake",
"mock",
]] for count in [
1,
10,
]]
Sebaiknya gunakan alternatif yang lebih sederhana. Misalnya, tentukan makro yang
membuat satu pengujian dan panggil untuk setiap name tingkat teratas:
my_java_test(name = "test_fake_1",
...)
my_java_test(name = "test_fake_10",
...)
...
Jangan gunakan variabel deps
Jangan gunakan variabel daftar untuk merangkum dependensi umum:
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 + [ ... ],
)
Demikian pula, jangan gunakan target library dengan
exports untuk mengelompokkan dependensi.
Sebagai gantinya, cantumkan dependensi secara terpisah untuk setiap target:
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",
...
],
)
Biarkan Gazelle dan alat lainnya memeliharanya. Akan ada pengulangan, tetapi Anda tidak perlu memikirkan cara mengelola dependensi.
Lebih memilih string literal
Meskipun Starlark menyediakan operator string untuk penyambungan (+) dan
pemformatan (%), gunakan dengan hati-hati. Anda mungkin tergoda untuk memisahkan bagian string yang sama untuk membuat ekspresi lebih ringkas atau memecah baris panjang. Namun,
Nilai string yang terpisah-pisah lebih sulit dibaca sekilas.
Alat otomatis seperti buildozer dan Penelusuran Kode mengalami kesulitan menemukan nilai dan memperbaruinya dengan benar saat nilai dipecah.
Dalam file
BUILD, keterbacaan lebih penting daripada menghindari pengulangan (lihat DAMP versus DRY).Panduan Gaya ini memperingatkan agar tidak memisahkan string bernilai label dan secara eksplisit mengizinkan baris panjang.
Buildifier otomatis menggabungkan string yang digabungkan saat mendeteksi bahwa string tersebut adalah label.
Oleh karena itu, sebaiknya gunakan string literal eksplisit daripada string yang digabungkan atau diformat, terutama dalam atribut jenis label seperti name dan deps. Misalnya, fragmen BUILD ini:
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",
)
akan lebih baik ditulis ulang sebagai
proto_library(
name = "foo_proto",
deps = ["//a/b:other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)
Membatasi simbol yang diekspor oleh setiap file .bzl
Minimalkan jumlah simbol (aturan, makro, konstanta, fungsi) yang diekspor oleh
setiap file .bzl (Starlark) publik. Sebaiknya file mengekspor
beberapa simbol hanya jika simbol tersebut pasti akan digunakan bersama. Jika tidak, pisahkan
menjadi beberapa file .bzl, yang masing-masing memiliki bzl_library sendiri.
Simbol yang berlebihan dapat menyebabkan file .bzl berkembang menjadi "kumpulan" simbol yang luas, sehingga perubahan pada satu file akan memaksa Bazel untuk membangun ulang banyak target.
Konvensi lainnya
Gunakan huruf besar dan garis bawah untuk mendeklarasikan konstanta (seperti
GLOBAL_CONSTANT), gunakan huruf kecil dan garis bawah untuk mendeklarasikan variabel (sepertimy_variable).Label tidak boleh dipisah, meskipun panjangnya lebih dari 79 karakter. Label harus berupa literal string jika memungkinkan. Alasan: Memudahkan pencarian dan penggantian. Hal ini juga meningkatkan keterbacaan.
Nilai atribut nama harus berupa string konstanta literal (kecuali dalam makro). Alasan: Alat eksternal menggunakan atribut nama untuk merujuk aturan. Mereka perlu menemukan aturan tanpa harus menafsirkan kode.
Saat menetapkan atribut jenis boolean, gunakan nilai boolean, bukan nilai bilangan bulat. Karena alasan lama, aturan masih mengonversi bilangan bulat menjadi boolean sesuai kebutuhan, tetapi hal ini tidak disarankan. Alasan:
flaky = 1dapat salah dibaca sebagai mengatakan "deflake target ini dengan menjalankannya kembali sekali".flaky = Truedengan jelas mengatakan "pengujian ini tidak stabil".
Perbedaan dengan panduan gaya Python
Meskipun kompatibilitas dengan panduan gaya Python adalah tujuan, ada beberapa perbedaan:
Tidak ada batasan panjang baris yang ketat. Komentar panjang dan string panjang sering kali dibagi menjadi 79 kolom, tetapi hal ini tidak wajib. Hal ini tidak boleh diterapkan dalam peninjauan kode atau skrip pra-pengiriman. Alasan: Label bisa panjang dan melebihi batas ini. File
BUILDbiasanya dibuat atau diedit oleh alat, yang tidak sesuai dengan batas panjang baris.Penggabungan string implisit tidak didukung. Gunakan operator
+. Alasan: FileBUILDberisi banyak daftar string. Koma mudah dilupakan, yang menyebabkan hasil yang sama sekali berbeda. Hal ini telah menyebabkan banyak bug di masa lalu. Lihat juga diskusi ini.Gunakan spasi di sekitar tanda
=untuk argumen kata kunci dalam aturan. Alasan: Argumen bernama jauh lebih sering digunakan daripada di Python dan selalu berada di baris terpisah. Spasi meningkatkan keterbacaan. Konvensi ini sudah ada sejak lama, dan tidak perlu mengubah semua fileBUILDyang ada.Secara default, gunakan tanda petik ganda untuk string. Alasan: Hal ini tidak ditentukan dalam panduan gaya Python, tetapi merekomendasikan konsistensi. Jadi, kami memutuskan untuk hanya menggunakan string dengan tanda kutip ganda. Banyak bahasa menggunakan tanda petik ganda untuk literal string.
Gunakan satu baris kosong di antara dua definisi tingkat teratas. Alasan: Struktur file
BUILDtidak seperti file Python biasa. Hanya memiliki pernyataan tingkat teratas. Menggunakan satu baris kosong membuat fileBUILDlebih pendek.