Makro

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 hadir dalam 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: attrs dan implementation.

Atribut

attrs menerima kamus nama atribut ke jenis atribut, yang mewakili argumen ke makro. Dua atribut umum - nama dan visibilitas - 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 select. Jika atribut 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.

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, 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,
        )

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.

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 seperti instance makro tidak boleh 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.

batasan

Makro simbolis memiliki beberapa batasan tambahan dibandingkan dengan makro lama.

Makro simbolis

  • harus menggunakan argumen name dan argumen visibility
  • harus memiliki fungsi implementation
  • mungkin tidak menampilkan nilai
  • tidak boleh memutasi args
  • tidak boleh memanggil native.existing_rules() kecuali jika merupakan makro finalizer khusus
  • tidak boleh 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 untuk mengetahui detail selengkapnya).

Visibilitas

TODO: Luaskan bagian ini

Visibilitas target

Secara default, target yang dibuat oleh makro simbolis dapat dilihat oleh paket tempat target tersebut dibuat. Atribut ini juga menerima atribut visibility, yang dapat memperluas visibilitas tersebut kepada pemanggil makro (dengan meneruskan atribut visibility langsung dari panggilan makro ke target yang dibuat) dan ke paket lain (dengan menentukannya secara eksplisit dalam visibilitas target).

Visibilitas dependensi

Makro harus memiliki visibilitas ke file dan target yang dirujuknya. Mereka dapat melakukannya dengan salah satu cara berikut:

  • Ditetapkan secara eksplisit sebagai nilai attr ke makro

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • Default implisit dari nilai attr
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
  • Sudah terlihat oleh definisi makro
# other_package/BUILD
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

Memilih

Jika atribut adalah configurable, fungsi penerapan makro akan selalu melihat nilai atribut sebagai nilai 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"]}).

Target aturan membalikkan transformasi ini, dan menyimpan select biasa sebagai nilai tanpa syaratnya; dalam contoh ini, jika _my_macro_impl mendeklarasikan target aturan my_rule(..., deps = deps), deps target aturan tersebut akan disimpan sebagai ["//a"].

Finalizer

Penyelesaian aturan adalah makro simbolis khusus yang - terlepas dari posisi leksikalnya dalam file BUILD - dievaluasi pada tahap akhir pemuatan paket, setelah semua target non-finalizer 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. Penyelesaian dapat menyatakan status set tersebut atau menentukan target baru.

Untuk mendeklarasikan finaler, 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. Ke depannya, 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 simbolik bertingkat, tetapi pertahankan makro tingkat atas sebagai makro lama.

  • Makro lama memanggil aturan yang membuat target yang melanggar skema penamaan

Tidak apa-apa, jangan bergantung pada target "yang menyinggung". Pemeriksaan penamaan akan diabaikan secara diam-diam.