BazelCon 2022 akan hadir pada 16-17 November ke New York dan online.
Daftar sekarang.

Tutorial Aturan

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Starlark adalah bahasa konfigurasi seperti Python yang awalnya dikembangkan untuk digunakan di Bazel dan sejak diadopsi oleh alat lain. File BUILD dan .bzl Bazel ditulis dalam dialek Starlark yang dikenal sebagai "Build Language", meskipun sering hanya disebut sebagai "Starlark", terutama saat menekankan bahwa dinyatakan dalam Bahasa Build, bukan sebagai bagian bawaan atau "native" dari Bazel. Bazel menambah bahasa inti dengan berbagai fungsi terkait build seperti glob, genrule, java_binary, dan seterusnya.

Lihat dokumentasi Bazel dan Starlark untuk mengetahui detail selengkapnya, dan template SIG Aturan sebagai titik awal untuk kumpulan aturan baru.

Aturan kosong

Untuk membuat aturan pertama Anda, buat file foo.bzl:

def _foo_binary_impl(ctx):
    pass

foo_binary = rule(
    implementation = _foo_binary_impl,
)

Saat memanggil fungsi rule, Anda harus menentukan fungsi callback. Logika akan masuk ke sana, tetapi Anda dapat membiarkan fungsi kosong untuk saat ini. Argumen ctx memberikan informasi tentang target.

Anda dapat memuat aturan dan menggunakannya dari file BUILD.

Buat file BUILD dalam direktori yang sama:

load(":foo.bzl", "foo_binary")

foo_binary(name = "bin")

Sekarang, target dapat dibuat:

$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)

Meskipun tidak melakukan apa pun, aturan tersebut sudah berperilaku seperti aturan lainnya: memiliki nama wajib, aturan mendukung atribut umum seperti visibility, testonly, dan tags.

Model evaluasi

Sebelum melanjutkan, Anda harus memahami cara kode dievaluasi.

Perbarui foo.bzl dengan beberapa pernyataan cetak:

def _foo_binary_impl(ctx):
    print("analyzing", ctx.label)

foo_binary = rule(
    implementation = _foo_binary_impl,
)

print("bzl file evaluation")

dan BUILD:

load(":foo.bzl", "foo_binary")

print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")

ctx.label sesuai dengan label target yang sedang dianalisis. Objek ctx memiliki banyak kolom dan metode yang berguna; Anda dapat menemukan daftar lengkap di referensi API.

Buat kueri kode:

$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1

Lakukan beberapa pengamatan:

  • "Evaluasi file bzl" dicetak terlebih dahulu. Sebelum mengevaluasi file BUILD, Bazel mengevaluasi semua file yang dimuatnya. Jika beberapa file BUILD memuat foo.bzl, Anda hanya akan melihat satu kemunculan "evaluasi file bzl" karena Bazel men-cache hasil evaluasi.
  • Fungsi callback _foo_binary_impl tidak dipanggil. Kueri Bazel memuat file BUILD, tetapi tidak menganalisis target.

Untuk menganalisis target, gunakan cquery ("kueri terkonfigurasi") atau perintah build:

$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...

Seperti yang Anda lihat, _foo_binary_impl sekarang dipanggil dua kali - sekali untuk setiap target.

Beberapa pembaca akan melihat bahwa "evaluasi file bzl" dicetak lagi, meskipun evaluasi foo.bzl di-cache setelah panggilan ke bazel query. Bazel tidak mengevaluasi ulang kode, Bazel hanya memutar ulang peristiwa cetak. Terlepas dari status cache, Anda mendapatkan output yang sama.

Membuat file

Agar aturan Anda lebih berguna, perbarui aturan untuk membuat file. Pertama, deklarasikan file dan beri nama. Dalam contoh ini, buat file dengan nama yang sama seperti target:

ctx.actions.declare_file(ctx.label.name)

Jika menjalankan bazel build :all sekarang, Anda akan mendapatkan error:

The following files have no generating action:
bin2

Setiap kali mendeklarasikan file, Anda harus memberi tahu Bazel cara membuatnya dengan membuat tindakan. Gunakan ctx.actions.write untuk membuat file dengan konten tertentu.

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello\n",
    )

Kode valid, tetapi tidak akan melakukan apa pun:

$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)

Fungsi ctx.actions.write mendaftarkan tindakan, yang mengajari Bazel cara membuat file. Namun, Bazel tidak akan membuat file tersebut sampai benar-benar diminta. Jadi, hal terakhir yang harus dilakukan adalah memberi tahu Bazel bahwa file tersebut adalah output aturan, dan bukan file sementara yang digunakan dalam implementasi aturan.

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello!\n",
    )
    return [DefaultInfo(files = depset([out]))]

Lihat fungsi DefaultInfo dan depset nanti. Untuk sekarang, asumsikan bahwa baris terakhir adalah cara untuk memilih output dari aturan.

Sekarang, jalankan Bazel:

$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
  bazel-bin/bin1

$ cat bazel-bin/bin1
Hello!

Anda berhasil membuat file.

Atribut

Untuk membuat aturan lebih berguna, tambahkan atribut baru menggunakan modul attr dan perbarui definisi aturan.

Tambahkan atribut string yang disebut username:

foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "username": attr.string(),
    },
)

Selanjutnya, tetapkan dalam file BUILD:

foo_binary(
    name = "bin",
    username = "Alice",
)

Untuk mengakses nilai dalam fungsi callback, gunakan ctx.attr.username. Contoh:

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello {}!\n".format(ctx.attr.username),
    )
    return [DefaultInfo(files = depset([out]))]

Perlu diperhatikan bahwa Anda dapat mewajibkan atribut atau menetapkan nilai default. Lihat dokumentasi attr.string. Anda juga dapat menggunakan jenis atribut lain, seperti boolean atau daftar bilangan bulat.

Dependensi

Atribut dependensi, sepertiattr.label danattr.label_list , deklarasikan deklarasi dari target yang memiliki atribut ke target yang labelnya muncul dalam nilai atribut. Atribut seperti ini membentuk dasar grafik target.

Dalam file BUILD, label target muncul sebagai objek string, seperti //pkg:name. Dalam fungsi implementasi, target akan dapat diakses sebagai objek Target. Misalnya, lihat file yang ditampilkan oleh target menggunakan Target.files.

Beberapa file

Secara default, hanya target yang dibuat oleh aturan yang dapat muncul sebagai dependensi (seperti target foo_library()). Jika Anda ingin atribut menerima target yang merupakan file input (seperti file sumber di repositori), Anda dapat melakukannya denganallow_files dan tentukan daftar ekstensi file yang diterima (atauTrue untuk mengizinkan ekstensi file apa pun):

"srcs": attr.label_list(allow_files = [".java"]),

Daftar file dapat diakses dengan ctx.files.<attribute name>. Misalnya, daftar file dalam atribut srcs dapat diakses melalui

ctx.files.srcs

Satu file

Jika Anda hanya memerlukan satu file, gunakan allow_single_file:

"src": attr.label(allow_single_file = [".java"])

Kemudian, file ini dapat diakses di ctx.file.<attribute name>:

ctx.file.src

Membuat file dengan template

Anda dapat membuat aturan yang menghasilkan file .cc berdasarkan template. Selain itu, Anda dapat menggunakan ctx.actions.write untuk menghasilkan string yang dibuat dalam fungsi penerapan aturan, tetapi hal ini memiliki dua masalah. Pertama, karena template semakin besar, akan menjadi lebih efisien memori untuk menempatkannya dalam file terpisah dan menghindari pembuatan string besar selama fase analisis. Kedua, pengguna akan lebih mudah menggunakan file terpisah. Sebagai gantinya, gunakan ctx.actions.expand_template, yang melakukan penggantian pada file template.

Buat atribut template untuk mendeklarasikan dependensi pada file template:

def _hello_world_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + ".cc")
    ctx.actions.expand_template(
        output = out,
        template = ctx.file.template,
        substitutions = {"{NAME}": ctx.attr.username},
    )
    return [DefaultInfo(files = depset([out]))]

hello_world = rule(
    implementation = _hello_world_impl,
    attrs = {
        "username": attr.string(default = "unknown person"),
        "template": attr.label(
            allow_single_file = [".cc.tpl"],
            mandatory = True,
        ),
    },
)

Pengguna dapat menggunakan aturan seperti ini:

hello_world(
    name = "hello",
    username = "Alice",
    template = "file.cc.tpl",
)

cc_binary(
    name = "hello_bin",
    srcs = [":hello"],
)

Jika tidak ingin menampilkan template ke pengguna akhir dan selalu menggunakan template yang sama, Anda dapat menetapkan nilai default dan membuat atributnya bersifat pribadi:

    "_template": attr.label(
        allow_single_file = True,
        default = "file.cc.tpl",
    ),

Atribut yang dimulai dengan garis bawah bersifat pribadi dan tidak dapat ditetapkan dalam file BUILD. Template sekarang menjadi dependensi implisit: Setiap target hello_world memiliki dependensi pada file ini. Jangan lupa untuk memperlihatkan file ini kepada paket lain dengan memperbarui file BUILD dan menggunakan exports_files:

exports_files(["file.cc.tpl"])

Melangkah lebih jauh