Makro Lama

Makro lama adalah fungsi tidak terstruktur yang dipanggil dari file BUILD yang dapat membuat target. Pada akhir fase pemuatan, makro lama tidak ada lagi, dan Bazel hanya melihat kumpulan konkret aturan yang di-instance.

Alasan Anda tidak boleh menggunakan makro lama (dan harus menggunakan makro Simbolik)

Jika memungkinkan, Anda harus menggunakan makro simbolis.

Makro simbolis

  • Mencegah tindakan dari jarak jauh
  • Memungkinkan penyembunyian detail implementasi melalui visibilitas terperinci
  • Mengambil atribut yang diketik, yang pada gilirannya berarti konversi label dan pemilihan otomatis.
  • Lebih mudah dibaca
  • Akan segera memiliki evaluasi lambat

Penggunaan

Kasus penggunaan umum untuk makro adalah saat Anda ingin menggunakan kembali aturan.

Misalnya, genrule dalam file BUILD menghasilkan file menggunakan //:generator dengan argumen some_arg yang dikodekan secara permanen dalam perintah:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

Jika Anda ingin membuat lebih banyak file dengan argumen yang berbeda, Anda dapat mengekstraksi kode ini ke fungsi makro. Untuk membuat makro yang disebut file_generator, yang memiliki parameter name dan arg, kita dapat mengganti genrule dengan yang berikut:

load("//path:generator.bzl", "file_generator")

file_generator(
    name = "file",
    arg = "some_arg",
)

file_generator(
    name = "file-two",
    arg = "some_arg_two",
)

file_generator(
    name = "file-three",
    arg = "some_arg_three",
)

Di sini, Anda memuat simbol file_generator dari file .bzl yang berada dalam paket //path. Dengan menempatkan definisi fungsi makro dalam file .bzl terpisah, Anda dapat menjaga file BUILD tetap bersih dan deklaratif. File .bzl dapat dimuat dari paket mana pun di ruang kerja.

Terakhir, di path/generator.bzl, tulis definisi makro untuk mengenkapsulasi dan memparameterisasi definisi genrule asli:

def file_generator(name, arg, visibility=None):
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location //:generator) %s > $@" % arg,
    tools = ["//:generator"],
    visibility = visibility,
  )

Anda juga dapat menggunakan makro untuk menggabungkan aturan. Contoh ini menunjukkan genrule yang dirantai, di mana genrule menggunakan output genrule sebelumnya sebagai input:

def chained_genrules(name, visibility=None):
  native.genrule(
    name = name + "-one",
    outs = [name + ".one"],
    cmd = "$(location :tool-one) $@",
    tools = [":tool-one"],
    visibility = ["//visibility:private"],
  )

  native.genrule(
    name = name + "-two",
    srcs = [name + ".one"],
    outs = [name + ".two"],
    cmd = "$(location :tool-two) $< $@",
    tools = [":tool-two"],
    visibility = visibility,
  )

Contoh ini hanya menetapkan nilai visibilitas ke genrule kedua. Hal ini memungkinkan pembuat makro menyembunyikan output aturan perantara agar tidak bergantung pada target lain di ruang kerja.

Memperluas makro

Saat Anda ingin menyelidiki fungsi makro, gunakan perintah query dengan --output=build untuk melihat bentuk yang diperluas:

$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
  name = "file",
  tools = ["//:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location //:generator) some_arg > $@",
)

Membuat instance aturan native

Aturan native (aturan yang tidak memerlukan pernyataan load()) dapat di-instantiate dari modul native:

def my_macro(name, visibility=None):
  native.cc_library(
    name = name,
    srcs = ["main.cc"],
    visibility = visibility,
  )

Jika Anda perlu mengetahui nama paket (misalnya, file BUILD mana yang memanggil makro), gunakan fungsi native.package_name(). Perhatikan bahwa native hanya dapat digunakan dalam file .bzl, dan tidak dalam file BUILD.

Resolusi label dalam makro

Karena makro lama dievaluasi dalam fase pemuatan, string label seperti "//foo:bar" yang muncul dalam makro lama ditafsirkan relatif terhadap file BUILD tempat makro digunakan, bukan relatif terhadap file .bzl tempat makro ditentukan. Perilaku ini umumnya tidak diinginkan untuk makro yang dimaksudkan untuk digunakan di repositori lain, seperti karena merupakan bagian dari set aturan Starlark yang dipublikasikan.

Untuk mendapatkan perilaku yang sama seperti aturan Starlark, sertakan string label dengan konstruktor Label:

# @my_ruleset//rules:defs.bzl
def my_cc_wrapper(name, deps = [], **kwargs):
  native.cc_library(
    name = name,
    deps = deps + select({
      # Due to the use of Label, this label is resolved within @my_ruleset,
      # regardless of its site of use.
      Label("//config:needs_foo"): [
        # Due to the use of Label, this label will resolve to the correct target
        # even if the canonical name of @dep_of_my_ruleset should be different
        # in the main repo, such as due to repo mappings.
        Label("@dep_of_my_ruleset//tools:foo"),
      ],
      "//conditions:default": [],
    }),
    **kwargs,
  )

Dengan mengaktifkan tanda --incompatible_eagerly_resolve_select_keys, semua kunci yang merupakan string label akan otomatis diselesaikan ke objek Label relatif terhadap paket file yang berisi panggilan select. Jika tidak dipilih, bungkus string label dengan native.package_relative_label().

Proses debug

  • bazel query --output=build //my/path:all akan menunjukkan tampilan file BUILD setelah evaluasi. Semua makro, glob, loop lama akan diperluas. Batasan yang diketahui: Ekspresi select tidak ditampilkan dalam output.

  • Anda dapat memfilter output berdasarkan generator_function (fungsi yang membuat aturan) atau generator_name (atribut nama makro): bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'

  • Untuk mengetahui di mana tepatnya aturan foo dibuat dalam file BUILD, Anda dapat mencoba trik berikut. Masukkan baris ini di dekat bagian atas file BUILD: cc_library(name = "foo"). Jalankan Bazel. Anda akan mendapatkan pengecualian saat aturan foo dibuat (karena konflik nama), yang akan menampilkan stack trace lengkap.

  • Anda juga dapat menggunakan print untuk proses debug. Pesan menampilkan pesan sebagai baris log DEBUG selama fase pemuatan. Kecuali dalam kasus yang jarang terjadi, hapus panggilan print, atau jadikan panggilan tersebut bersyarat di bawah parameter debugging yang secara default ditetapkan ke False sebelum mengirimkan kode ke depot.

Error

Jika Anda ingin menampilkan error, gunakan fungsi fail. Jelaskan dengan jelas kepada pengguna apa yang salah dan cara memperbaiki file BUILD mereka. Error tidak dapat ditangkap.

def my_macro(name, deps, visibility=None):
  if len(deps) < 2:
    fail("Expected at least two values in deps")
  # ...

Konvensi

  • Semua fungsi publik (fungsi yang tidak dimulai dengan garis bawah) yang meng-instance aturan harus memiliki argumen name. Argumen ini tidak boleh opsional (jangan berikan nilai default).

  • Fungsi publik harus menggunakan string dokumen yang mengikuti konvensi Python.

  • Dalam file BUILD, argumen name makro harus berupa argumen kata kunci (bukan argumen posisional).

  • Atribut name dari aturan yang dibuat oleh makro harus menyertakan argumen nama sebagai awalan. Misalnya, macro(name = "foo") dapat membuat cc_library foo dan genrule foo_gen.

  • Dalam sebagian besar kasus, parameter opsional harus memiliki nilai default None. None dapat diteruskan langsung ke aturan native, yang memperlakukannya sama seperti jika Anda tidak meneruskan argumen apa pun. Jadi, Anda tidak perlu menggantinya dengan 0, False, atau [] untuk tujuan ini. Sebagai gantinya, makro harus menunda ke aturan yang dibuatnya, karena defaultnya mungkin rumit atau dapat berubah dari waktu ke waktu. Selain itu, parameter yang ditetapkan secara eksplisit ke nilai defaultnya terlihat berbeda dengan parameter yang tidak pernah ditetapkan (atau ditetapkan ke None) saat diakses melalui bahasa kueri atau internal sistem build.

  • Makro harus memiliki argumen visibility opsional.