Halaman ini membahas dasar-dasar penggunaan makro dan mencakup kasus penggunaan umum, proses debug, dan konvensi.
Makro adalah fungsi yang dipanggil dari file BUILD yang dapat membuat instance aturan.
Makro terutama digunakan untuk enkapsulasi dan penggunaan ulang kode aturan yang ada dan
makro lainnya.
Makro terdiri dari dua jenis: makro simbolik, yang dijelaskan di halaman ini, dan makro lama. Jika memungkinkan, sebaiknya gunakan makro simbolik untuk kejelasan kode.
Makro simbolik menawarkan argumen yang diketik (konversi string ke label, relatif terhadap
tempat makro dipanggil) dan kemampuan untuk membatasi dan menentukan
visibilitas target yang dibuat. Makro ini dirancang agar dapat dievaluasi secara lambat (yang akan ditambahkan dalam rilis Bazel mendatang). Makro simbolik tersedia secara default di Bazel 8. Jika dokumen ini menyebutkan macros, yang
dimaksud adalah makro simbolik.
Contoh makro simbolik yang dapat dieksekusi dapat ditemukan di repositori contoh.
Penggunaan
Makro ditentukan dalam .bzl file dengan memanggil fungsi
macro() dengan
dua parameter wajib: attrs dan implementation.
Atribut
attrs menerima kamus nama atribut ke jenis
atribut, yang mewakili
argumen ke makro. Dua atribut umum – name dan visibility –
secara implisit ditambahkan 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
selects. Jika atribut 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 bagian select.
Warisan 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 simbol aturan atau
makro ke argumen macro()'s
inherit_attrs. (Anda juga dapat menggunakan string khusus "common"
dan bukan simbol aturan atau 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
attrs mengganti atribut yang diwarisi dengan nama yang sama. Anda juga dapat
menghapus atribut yang diwarisi dengan menggunakan None sebagai nilai dalam attrs
kamus:
# 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 yang diwarisi dan tidak wajib selalu diganti menjadi
None, terlepas dari nilai default definisi atribut asli. Jika
Anda perlu memeriksa atau mengubah atribut yang diwarisi dan tidak wajib – misalnya, jika Anda ingin menambahkan tag ke atribut tags yang diwarisi – Anda harus
memastikan untuk menangani kasus None dalam fungsi implementasi makro:
# macro/macro.bzl
def _my_macro_impl(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,
)
...
Implementasi
implementation menerima fungsi yang berisi logika makro.
Fungsi implementasi sering kali membuat target dengan memanggil satu atau beberapa aturan, dan
biasanya bersifat pribadi (diberi nama dengan garis bawah di awal). Secara konvensional,
fungsi implementasi diberi nama yang sama dengan makronya, tetapi diberi awalan _ dan akhiran
_impl.
Tidak seperti fungsi implementasi aturan, yang menggunakan satu argumen (ctx) yang
berisi referensi ke atribut, fungsi implementasi 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 sisa
**kwargs, yang dapat diteruskan ke panggilan yang
memanggil aturan atau submakro yang diwarisi. (Hal ini membantu memastikan makro Anda tidak akan
rusak jika aturan atau makro yang Anda warisi menambahkan atribut
baru.)
Pernyataan
Makro dideklarasikan dengan memuat dan memanggil definisinya dalam file BUILD.
```starlark
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 BUILD file, tetapi berguna saat
menggabungkan makro secara terprogram di dalam makro lain.
Detail
Konvensi penamaan untuk target yang dibuat
Nama target atau submakro yang dibuat oleh makro simbolik harus
cocok dengan parameter name makro atau harus diawali dengan name yang diikuti
oleh _ (pilihan), . atau -. Misalnya, my_macro(name = "foo") hanya dapat
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 dibuat 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 simbolik memiliki beberapa batasan tambahan dibandingkan dengan makro lama.
Makro simbolik
- harus menggunakan argumen
namedan argumenvisibility - harus memiliki fungsi
implementation - tidak boleh menampilkan nilai
- tidak boleh mengubah argumennya
- tidak boleh memanggil
native.existing_rules()kecuali jika merupakanfinalizermakro - tidak boleh memanggil
native.package() - tidak boleh memanggil
glob() - tidak boleh 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
- tidak dapat merujuk ke target pribadi pemanggilnya (lihat visibilitas dan makro untuk mengetahui detail selengkapnya).
Visibilitas dan makro
Sistem visibilitas membantu melindungi detail implementasi makro (simbolik) dan pemanggilnya.
Secara default, target yang dibuat dalam makro simbolik terlihat dalam makro
itu sendiri, tetapi tidak selalu terlihat oleh pemanggil makro. Makro dapat "mengekspor" a
target sebagai API publik dengan meneruskan nilai atributnya sendiri visibility, seperti dalam some_rule(..., visibility = visibility).
Ide utama visibilitas makro adalah:
Visibilitas diperiksa berdasarkan makro yang mendeklarasikan target, bukan paket yang memanggil makro.
- Dengan kata lain, berada dalam paket yang sama tidak dengan sendirinya membuat satu target terlihat oleh target lain. Hal ini melindungi target internal makro agar tidak menjadi dependensi makro lain atau target tingkat atas dalam paket.
Semua atribut
visibility, baik pada aturan maupun makro, secara otomatis menyertakan tempat aturan atau makro dipanggil.- Dengan demikian, target terlihat tanpa syarat oleh target lain yang dideklarasikan di
makro yang sama (atau file
BUILD, jika tidak dalam makro).
- Dengan demikian, target terlihat tanpa syarat oleh target lain yang dideklarasikan di
makro yang sama (atau file
Dalam praktiknya, hal ini berarti bahwa saat makro mendeklarasikan target tanpa menetapkan
visibility, target akan ditetapkan secara default sebagai internal makro. (Visibilitas default paket
tidak berlaku dalam makro.) Mengekspor target berarti target terlihat
oleh apa pun yang ditentukan pemanggil makro dalam atribut visibility makro,
ditambah paket pemanggil makro itu sendiri, serta kode makro itu sendiri.
Cara lain untuk memikirkannya adalah visibilitas makro menentukan siapa
(selain makro itu sendiri) yang dapat melihat target yang diekspor makro.
# tool/BUILD
...
some_rule(
name = "some_tool",
visibility = ["//macro:__pkg__"],
)
# macro/macro.bzl
def _impl(name, visibility):
cc_library(
name = name + "_helper",
...
# No visibility passed in. Same as passing `visibility = None` or
# `visibility = ["//visibility:private"]`. Visible to the //macro
# package only.
)
cc_binary(
name = name + "_exported",
deps = [
# Allowed because we're also in //macro. (Targets in any other
# instance of this macro, or any other macro in //macro, can see it
# too.)
name + "_helper",
# Allowed by some_tool's visibility, regardless of what BUILD file
# we're called from.
"//tool:some_tool",
],
...
visibility = visibility,
)
my_macro = macro(implementation = _impl, ...)
# pkg/BUILD
load("//macro:macro.bzl", "my_macro")
...
my_macro(
name = "foo",
...
)
some_rule(
...
deps = [
# Allowed, its visibility is ["//pkg:__pkg__", "//macro:__pkg__"].
":foo_exported",
# Disallowed, its visibility is ["//macro:__pkg__"] and
# we are not in //macro.
":foo_helper",
]
)
Jika my_macro dipanggil dengan visibility = ["//other_pkg:__pkg__"], atau jika
paket //pkg telah menetapkan default_visibility ke nilai tersebut, maka
//pkg:foo_exported juga dapat digunakan dalam //other_pkg/BUILD atau dalam
makro yang ditentukan di //other_pkg:defs.bzl, tetapi //pkg:foo_helper akan tetap
dilindungi.
Makro dapat mendeklarasikan bahwa target terlihat oleh paket teman dengan meneruskan
visibility = ["//some_friend:__pkg__"] (untuk target internal) atau
visibility = visibility + ["//some_friend:__pkg__"] (untuk target yang diekspor).
Perhatikan bahwa makro mendeklarasikan target dengan visibilitas publik (visibility = ["//visibility:public"]) adalah antipola. Hal ini karena membuat
target terlihat tanpa syarat oleh setiap paket, meskipun pemanggil
menentukan visibilitas yang lebih terbatas.
Semua pemeriksaan visibilitas dilakukan sehubungan dengan makro simbolik yang saat ini berjalan paling dalam. Namun, ada mekanisme delegasi visibilitas: Jika makro meneruskan label sebagai nilai atribut ke makro dalam, setiap penggunaan label di makro dalam akan diperiksa sehubungan dengan makro luar. Lihat halaman visibilitas untuk mengetahui detail selengkapnya.
Ingatlah bahwa makro lama sepenuhnya transparan terhadap sistem visibilitas, dan berperilaku seolah-olah lokasinya adalah file BUILD atau makro simbolik yang dipanggil.
Finalizer dan visibilitas
Target yang dideklarasikan dalam finalizer aturan, selain melihat target yang mengikuti aturan visibilitas makro simbolik biasa, dapat juga melihat semua target yang terlihat oleh paket target finalizer.
Artinya, jika Anda memigrasikan makro lama berbasis native.existing_rules() ke
finalizer, target yang dideklarasikan oleh finalizer akan tetap dapat melihat
dependensi lamanya.
Namun, perhatikan bahwa target dapat dideklarasikan dalam makro simbolik sehingga target finalizer tidak dapat melihatnya di bawah sistem visibilitas – meskipun finalizer dapat mengintrospeksi atributnya menggunakan
native.existing_rules().
Select
Jika atribut configurable (default) dan nilainya bukan None,
fungsi implementasi makro akan melihat nilai atribut yang digabungkan
dalam select sepele. Hal ini memudahkan penulis makro untuk menemukan bug
jika tidak mengantisipasi bahwa nilai atribut dapat berupa select.
Misalnya, perhatikan 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 ini akan menyebabkan _my_macro_impl
dipanggil dengan parameter deps yang ditetapkan ke select({"//conditions:default":
["//a"]}). Jika hal ini menyebabkan fungsi implementasi gagal (misalnya, karena
kode mencoba mengindeks nilai seperti dalam deps[0], yang tidak diizinkan untuk
selects), 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 non-configurable (attr.label_list(configurable = False)). Hal
ini memastikan bahwa pengguna tidak diizinkan untuk meneruskan nilai select.
Target aturan membalikkan transformasi ini, dan menyimpan select sepele sebagai nilai tanpa syarat; 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 oleh makro.
Jika nilai atribut yang dapat dikonfigurasi adalah None, atribut tersebut tidak akan digabungkan dalam
select. Hal ini memastikan bahwa pengujian seperti my_attr == None masih berfungsi, dan saat atribut diteruskan ke aturan dengan default yang dihitung, aturan berperilaku dengan benar (yaitu, seolah-olah atribut tidak diteruskan sama sekali). Atribut tidak selalu dapat mengambil nilai None tetapi hal ini dapat
terjadi untuk jenis attr.label() dan untuk atribut yang diwarisi dan tidak wajib.
Finalizer
Finalizer aturan adalah makro simbolik khusus yang – terlepas dari posisi leksikal
nya dalam file BUILD – dievaluasi pada tahap akhir pemuatan paket,
setelah semua target non-finalizer ditentukan. Tidak seperti makro simbolik
biasa, finalizer dapat memanggil native.existing_rules(), yang berperilaku
sedikit berbeda dengan makro lama: hanya menampilkan kumpulan target aturan
non-finalizer. Finalizer dapat menegaskan status kumpulan tersebut atau
menentukan target baru.
Untuk mendeklarasikan finalizer, 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 evaluasi dan perluasan makro lambat. Fitur ini belum tersedia.
Saat ini, semua makro dievaluasi segera setelah file BUILD dimuat, yang dapat berdampak negatif pada performa target dalam paket yang juga memiliki makro tidak terkait yang mahal. Pada masa mendatang, makro simbolik non-finalizer hanya akan dievaluasi jika diperlukan untuk build. Skema penamaan awalan membantu Bazel menentukan makro mana yang akan diperluas mengingat target yang diminta.
Pemecahan masalah migrasi
Berikut adalah beberapa masalah umum 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 simbolik menggunakan atribut daftar label:
# BUILD file
my_macro(
...,
deps = glob(...),
)
- Makro lama memiliki parameter yang bukan jenis starlark
attryang valid.
Tarik logika sebanyak mungkin ke dalam makro simbolik bertingkat, tetapi pertahankan makro tingkat atas sebagai makro lama.
- Makro lama memanggil aturan yang membuat target yang melanggar skema penamaan
Tidak masalah, jangan bergantung pada target "yang melanggar". Pemeriksaan penamaan akan diabaikan secara diam-diam.