Halaman ini membahas dasar-dasar penggunaan makro dan menyertakan kasus penggunaan, proses debug, dan konvensi umum.
Makro adalah fungsi yang dipanggil dari file BUILD
yang dapat membuat instance aturan.
Makro terutama digunakan untuk enkapsulasi dan penggunaan kembali kode dari aturan yang ada dan
makro lainnya.
Makro terdiri dari dua ragam: makro simbolis, yang dijelaskan di halaman ini, dan makro lama. Jika memungkinkan, sebaiknya gunakan makro simbolis untuk kejelasan kode.
Makro simbolis menawarkan argumen yang diketik (konversi string ke label, relatif terhadap tempat makro dipanggil) dan kemampuan untuk membatasi serta menentukan visibilitas target yang dibuat. Fungsi ini dirancang agar dapat menerima evaluasi
lambat (yang akan ditambahkan dalam rilis Bazel mendatang). Makro simbolis
tersedia secara default di Bazel 8. Jika dokumen ini menyebutkan macros
, dokumen tersebut
mengacu pada makro simbolis.
Penggunaan
Makro ditentukan dalam file .bzl
dengan memanggil
fungsi macro()
dengan
dua parameter yang diperlukan: attrs
dan implementation
.
Atribut
attrs
menerima kamus nama atribut ke jenis
atribut, yang mewakili
argumen ke makro. Dua atribut umum – name
dan visibility
–
ditambahkan secara implisit ke semua makro dan tidak disertakan dalam kamus yang diteruskan
ke attrs
.
# macro/macro.bzl
my_macro = macro(
attrs = {
"deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
"create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
},
implementation = _my_macro_impl,
)
Deklarasi jenis atribut menerima
parameter,
mandatory
, default
, dan doc
. Sebagian besar jenis atribut juga menerima
parameter configurable
, yang menentukan apakah atribut menerima
select
. Jika atributnya adalah configurable
, atribut tersebut akan mengurai nilai non-select
sebagai select
yang tidak dapat dikonfigurasi – "foo"
akan menjadi
select({"//conditions:default": "foo"})
. Pelajari lebih lanjut di pilihan.
Pewarisan atribut
Makro sering kali dimaksudkan untuk menggabungkan aturan (atau makro lain), dan penulis
makro sering kali ingin meneruskan sebagian besar atribut simbol yang digabungkan
tanpa perubahan, menggunakan **kwargs
, ke target utama makro (atau makro dalam utama).
Untuk mendukung pola ini, makro dapat mewarisi atribut dari aturan atau makro
lain dengan meneruskan aturan atau
simbol makro ke argumen
inherit_attrs
macro()
. (Anda juga dapat menggunakan string khusus "common"
bukan aturan atau simbol makro untuk mewarisi atribut umum yang ditentukan untuk
semua aturan build
Starlark.)
Hanya atribut publik yang diwarisi, dan atribut dalam kamus attrs
makro itu sendiri akan menggantikan atribut yang diwarisi dengan nama yang sama. Anda juga dapat
menghapus atribut yang diwarisi dengan menggunakan None
sebagai nilai dalam kamus
attrs
:
# macro/macro.bzl
my_macro = macro(
inherit_attrs = native.cc_library,
attrs = {
# override native.cc_library's `local_defines` attribute
local_defines = attr.string_list(default = ["FOO"]),
# do not inherit native.cc_library's `defines` attribute
defines = None,
},
...
)
Nilai default atribut turunan yang tidak wajib selalu diganti menjadi
None
, terlepas dari nilai default definisi atribut asli. Jika
Anda perlu memeriksa atau mengubah atribut non-wajib yang diwarisi – misalnya, jika Anda ingin menambahkan tag ke atribut tags
yang diwarisi – Anda harus
memastikan untuk menangani kasus None
dalam fungsi penerapan makro:
# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
# Append a tag; tags attr is an inherited non-mandatory attribute, and
# therefore is None unless explicitly set by the caller of our macro.
my_tags = (tags or []) + ["another_tag"]
native.cc_library(
...
tags = my_tags,
**kwargs,
)
...
Penerapan
implementation
menerima fungsi yang berisi logika makro.
Fungsi implementasi sering membuat target dengan memanggil satu atau beberapa aturan, dan
biasanya bersifat pribadi (diberi nama dengan garis bawah di awal). Secara konvensional,
namanya sama dengan makronya, tetapi diawali dengan _
dan diakhiri dengan
_impl
.
Tidak seperti fungsi penerapan aturan, yang menggunakan satu argumen (ctx
) yang berisi referensi ke atribut, fungsi penerapan makro menerima parameter untuk setiap argumen.
# macro/macro.bzl
def _my_macro_impl(name, visibility, deps, create_test):
cc_library(
name = name + "_cc_lib",
deps = deps,
)
if create_test:
cc_test(
name = name + "_test",
srcs = ["my_test.cc"],
deps = deps,
)
Jika makro mewarisi atribut, fungsi implementasinya harus memiliki
parameter kata kunci residual **kwargs
, yang dapat diteruskan ke panggilan yang
memanggil aturan atau submakro yang diwarisi. (Tindakan ini membantu memastikan bahwa makro Anda tidak akan
rusak jika aturan atau makro yang diwarisi menambahkan atribut
baru.)
Pernyataan
Makro dideklarasikan dengan memuat dan memanggil definisinya dalam file BUILD
.
# pkg/BUILD
my_macro(
name = "macro_instance",
deps = ["src.cc"] + select(
{
"//config_setting:special": ["special_source.cc"],
"//conditions:default": [],
},
),
create_tests = True,
)
Tindakan ini akan membuat target
//pkg:macro_instance_cc_lib
dan//pkg:macro_instance_test
.
Sama seperti dalam panggilan aturan, jika nilai atribut dalam panggilan makro ditetapkan ke None
,
atribut tersebut akan diperlakukan seolah-olah dihilangkan oleh pemanggil makro. Misalnya, dua panggilan makro berikut setara:
# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])
Hal ini umumnya tidak berguna dalam file BUILD
, tetapi berguna saat
menggabungkan makro secara terprogram di dalam makro lain.
Detail
Konvensi penamaan untuk target yang dibuat
Nama target atau submakro apa pun yang dibuat oleh makro simbolis harus
cocok dengan parameter name
makro atau harus diawali dengan name
, diikuti dengan
_
(lebih disukai), .
, atau -
. Misalnya, my_macro(name = "foo")
hanya boleh
membuat file atau target bernama foo
, atau diawali dengan foo_
, foo-
, atau foo.
,
misalnya, foo_bar
.
Target atau file yang melanggar konvensi penamaan makro dapat dideklarasikan, tetapi tidak dapat di-build dan tidak dapat digunakan sebagai dependensi.
File dan target non-makro dalam paket yang sama dengan instance makro harus tidak memiliki nama yang bertentangan dengan nama target makro potensial, meskipun eksklusivitas ini tidak diterapkan. Kami sedang dalam proses menerapkan evaluasi lambat sebagai peningkatan performa untuk makro Simbolik, yang akan terganggu dalam paket yang melanggar skema penamaan.
Pembatasan
Makro simbolis memiliki beberapa batasan tambahan dibandingkan dengan makro lama.
Makro simbolis
- harus menggunakan argumen
name
dan argumenvisibility
- harus memiliki fungsi
implementation
- mungkin tidak menampilkan nilai
- tidak boleh memutasi argumennya
- tidak boleh memanggil
native.existing_rules()
kecuali jika merupakan makrofinalizer
khusus - mungkin tidak memanggil
native.package()
- mungkin tidak memanggil
glob()
- mungkin tidak memanggil
native.environment_group()
- harus membuat target yang namanya mematuhi skema penamaan
- tidak dapat merujuk ke file input yang tidak dideklarasikan atau diteruskan sebagai argumen (lihat visibilitas dan makro untuk mengetahui detail selengkapnya).
Visibilitas dan makro
Lihat Visibilitas untuk pembahasan mendalam tentang visibilitas di Bazel.
Visibilitas target
Secara default, target yang dibuat oleh makro simbolis hanya terlihat dalam paket yang berisi file .bzl yang menentukan makro. Secara khusus, fungsi tersebut tidak terlihat oleh pemanggil makro simbolis kecuali jika pemanggil berada dalam paket yang sama dengan file .bzl makro.
Agar target terlihat oleh pemanggil makro simbolis, teruskan
visibility = visibility
ke aturan atau makro dalam. Anda juga dapat membuat
target terlihat dalam paket tambahan dengan memberikan visibilitas
yang lebih luas (atau bahkan publik).
Visibilitas default paket (seperti yang dideklarasikan di package()
) secara default diteruskan
ke parameter visibility
makro terluar, tetapi makro tersebut dapat
meneruskan (atau tidak meneruskan) visibility
tersebut ke target yang dibuat instance-nya.
Visibilitas dependensi
Target yang dirujuk dalam implementasi makro harus terlihat oleh definisi makro tersebut. Visibilitas dapat diberikan dengan salah satu cara berikut:
- Target terlihat oleh makro jika diteruskan ke makro melalui label, daftar label, atau atribut dict dengan kunci atau nilai label, baik secara eksplisit:
# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
- ... atau sebagai nilai default atribut:
# my_macro:macro.bzl
my_macro = macro(
attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])},
...
)
- Target juga terlihat oleh makro jika dideklarasikan terlihat oleh paket yang berisi file .bzl yang menentukan makro:
# other_package/BUILD
# Any macro defined in a .bzl file in //my_macro package can use this tool.
cc_binary(
name = "my_tool",
visibility = "//my_macro:\\__pkg__",
)
Memilih
Jika atribut adalah configurable
(default) dan nilainya bukan None
,
fungsi implementasi makro akan melihat nilai atribut sebagai digabungkan
dalam select
biasa. Hal ini memudahkan penulis makro untuk menemukan bug
saat mereka tidak mengantisipasi bahwa nilai atribut dapat berupa select
.
Misalnya, pertimbangkan makro berikut:
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
Jika my_macro
dipanggil dengan deps = ["//a"]
, hal itu akan menyebabkan _my_macro_impl
dipanggil dengan parameter deps
-nya ditetapkan ke select({"//conditions:default":
["//a"]})
. Jika hal ini menyebabkan fungsi implementasi gagal (misalnya, karena kode mencoba mengindeks ke nilai seperti dalam deps[0]
, yang tidak diizinkan untuk select
), penulis makro kemudian dapat membuat pilihan: mereka dapat menulis ulang makro agar hanya menggunakan operasi yang kompatibel dengan select
, atau mereka dapat menandai atribut sebagai tidak dapat dikonfigurasi (attr.label_list(configurable = False)
). Opsi kedua memastikan bahwa pengguna tidak diizinkan untuk meneruskan nilai select
.
Target aturan membalikkan transformasi ini, dan menyimpan select
biasa sebagai nilai tanpa syaratnya; dalam contoh di atas, jika _my_macro_impl
mendeklarasikan target aturan my_rule(..., deps = deps)
, deps
target aturan tersebut akan disimpan sebagai ["//a"]
. Hal ini memastikan bahwa penggabungan select
tidak menyebabkan nilai select
sepele disimpan di semua target yang dibuat instance-nya oleh makro.
Jika nilai atribut yang dapat dikonfigurasi adalah None
, nilai tersebut tidak digabungkan dalam
select
. Hal ini memastikan bahwa pengujian seperti my_attr == None
masih berfungsi, dan bahwa
saat atribut diteruskan ke aturan dengan default yang dihitung, aturan tersebut
akan berperilaku dengan benar (yaitu, seolah-olah atribut tidak diteruskan sama sekali). Atribut tidak selalu dapat menggunakan nilai None
, tetapi hal ini dapat terjadi untuk jenis attr.label()
, dan untuk atribut non-wajib yang diwarisi.
Finalizer
Finalizer aturan adalah makro simbolis khusus yang – terlepas dari posisi leksikalnya
dalam file BUILD – dievaluasi pada tahap akhir pemuatan paket,
setelah semua target non-finalizer telah ditentukan. Tidak seperti makro simbolis
biasa, finalisasi dapat memanggil native.existing_rules()
, yang berperilaku
sedikit berbeda dengan makro lama: hanya menampilkan kumpulan
target aturan non-finalisasi. Finalizer dapat menyatakan status set tersebut atau
menentukan target baru.
Untuk mendeklarasikan finalisasi, panggil macro()
dengan finalizer = True
:
def _my_finalizer_impl(name, visibility, tags_filter):
for r in native.existing_rules().values():
for tag in r.get("tags", []):
if tag in tags_filter:
my_test(
name = name + "_" + r["name"] + "_finalizer_test",
deps = [r["name"]],
data = r["srcs"],
...
)
continue
my_finalizer = macro(
attrs = {"tags_filter": attr.string_list(configurable = False)},
implementation = _impl,
finalizer = True,
)
Kemalasan
PENTING: Kami sedang dalam proses menerapkan perluasan dan evaluasi makro lambat. Fitur ini belum tersedia.
Saat ini, semua makro dievaluasi segera setelah file BUILD dimuat, yang dapat berdampak negatif pada performa untuk target dalam paket yang juga memiliki makro yang tidak terkait dan mahal. Di masa mendatang, makro simbolis non-finalizer hanya akan dievaluasi jika diperlukan untuk build. Skema penamaan awalan membantu Bazel menentukan makro mana yang akan diperluas berdasarkan target yang diminta.
Pemecahan masalah migrasi
Berikut adalah beberapa masalah umum terkait migrasi dan cara memperbaikinya.
- Panggilan makro lama
glob()
Pindahkan panggilan glob()
ke file BUILD Anda (atau ke makro lama yang dipanggil dari
file BUILD), dan teruskan nilai glob()
ke makro simbolis menggunakan
atribut daftar label:
# BUILD file
my_macro(
...,
deps = glob(...),
)
- Makro lama memiliki parameter yang bukan jenis
attr
starlark yang valid.
Tarik sebanyak mungkin logika ke dalam makro simbolis bertingkat, tetapi tetapkan makro tingkat teratas sebagai makro lama.
- Makro lama memanggil aturan yang membuat target yang melanggar skema penamaan
Tidak apa-apa, jangan bergantung pada target "yang melanggar". Pemeriksaan penamaan akan diabaikan secara diam-diam.