Starlark adalah bahasa konfigurasi seperti Python
yang awalnya dikembangkan untuk digunakan di Bazel dan sejak itu diadopsi
oleh alat lain. File BUILD dan .bzl Bazel ditulis dalam dialek Starlark yang dikenal sebagai "Bahasa Build", meskipun sering disebut sebagai "Starlark", terutama saat menekankan bahwa fitur diekspresikan 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 sebagainya.
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, 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. Logikanya akan masuk ke sana, tetapi Anda dapat mengosongkan fungsi untuk saat ini. Argumen ctx
memberikan informasi tentang target.
Anda dapat memuat aturan dan menggunakannya dari file BUILD.
Buat file BUILD di 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 aturan tersebut tidak melakukan apa pun, aturan tersebut sudah berperilaku seperti aturan lainnya: aturan tersebut memiliki nama wajib, dan mendukung atribut umum seperti visibility, testonly, dan tags.
Model evaluasi
Sebelum melanjutkan, penting untuk 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 lengkapnya di
referensi API.
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
Buat beberapa pengamatan:
- "bzl file evaluation" dicetak terlebih dahulu. Sebelum mengevaluasi file
BUILD, Bazel mengevaluasi semua file yang dimuatnya. Jika beberapa fileBUILDmemuat foo.bzl, Anda hanya akan melihat satu kemunculan "bzl file evaluation" karena Bazel menyimpan hasil evaluasi dalam cache. - Fungsi callback
_foo_binary_impltidak dipanggil. Kueri Bazel memuat fileBUILD, tetapi tidak menganalisis target.
Untuk menganalisis target, gunakan cquery ("kueri yang dikonfigurasi") atau perintah build:
$ bazel build :all
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 dapat Anda lihat, _foo_binary_impl kini dipanggil dua kali - sekali untuk setiap target.
Perhatikan bahwa "bzl file evaluation" maupun "BUILD file" tidak dicetak lagi, karena evaluasi foo.bzl di-cache setelah panggilan ke bazel query.
Bazel hanya mengeluarkan pernyataan print saat benar-benar dieksekusi.
Membuat file
Agar aturan Anda lebih berguna, perbarui aturan tersebut untuk menghasilkan file. Pertama, deklarasikan file dan beri nama. Dalam contoh ini, buat file dengan nama yang sama dengan target:
ctx.actions.declare_file(ctx.label.name)
Jika Anda menjalankan bazel build :all sekarang, Anda akan mendapatkan error:
The following files have no generating action:
bin2
Setiap kali Anda mendeklarasikan file, Anda harus memberi tahu Bazel cara membuatnya dengan membuat tindakan. Gunakan ctx.actions.write,
untuk membuat file dengan konten yang diberikan.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
Kode ini 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 mengajarkan Bazel cara membuat file. Namun, Bazel tidak akan membuat file hingga benar-benar diminta. Jadi, hal terakhir yang harus dilakukan adalah memberi tahu Bazel bahwa file tersebut adalah output dari aturan, dan bukan file sementara yang digunakan dalam penerapan 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 saat ini, asumsikan bahwa baris terakhir adalah cara untuk memilih output 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 telah berhasil membuat file.
Atribut
Agar aturan lebih berguna, tambahkan atribut baru menggunakan
modul attrdan perbarui definisi aturan.
Tambahkan atribut string bernama username:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
Selanjutnya, tetapkan di 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]))]
Perhatikan bahwa Anda dapat membuat atribut wajib atau menetapkan nilai default. Lihat
dokumentasi attr.string.
Anda juga dapat menggunakan jenis atribut lainnya, seperti boolean
atau daftar bilangan bulat.
Dependensi
Atribut dependensi, seperti attr.label
dan attr.label_list,
mendeklarasikan dependensi dari target yang memiliki atribut ke target yang
labelnya muncul dalam nilai atribut. Jenis atribut ini membentuk dasar grafik target.
Dalam file BUILD, label target muncul sebagai objek string, seperti //pkg:name. Dalam fungsi penerapan, target akan dapat diakses sebagai objek
Target. Misalnya, lihat file yang ditampilkan
oleh target menggunakan Target.files.
Banyak 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 dengan allow_files dan menentukan daftar ekstensi file yang diterima (atau True 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"])
File ini kemudian dapat diakses di bagian 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 menampilkan string yang dibuat dalam fungsi penerapan aturan, tetapi hal ini memiliki dua masalah. Pertama, saat template menjadi lebih besar, akan lebih efisien memori untuk menempatkannya dalam file terpisah dan menghindari pembuatan string besar selama fase analisis. Kedua, menggunakan file terpisah lebih mudah bagi pengguna. 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 Anda tidak ingin mengekspos template kepada pengguna akhir dan selalu menggunakan template yang sama, Anda dapat menetapkan nilai default dan membuat atribut 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 hello_world
target memiliki dependensi pada file ini. Jangan lupa untuk membuat file ini terlihat
oleh paket lain dengan memperbarui file BUILD dan menggunakan
exports_files:
exports_files(["file.cc.tpl"])
Melanjutkan
- Lihat dokumentasi referensi untuk aturan.
- Pelajari depsets.
- Lihat repositori contoh yang menyertakan contoh aturan tambahan.