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 "Build Language", meskipun sering kali hanya
disebut sebagai "Starlark", terutama saat menekankan bahwa fitur
diekspresikan dalam Build Language, bukan sebagai bagian bawaan atau "native"
dari Bazel. Bazel meningkatkan bahasa inti dengan banyak fungsi terkait build
seperti glob
, genrule
, java_binary
, dan sebagainya.
Lihat dokumentasi Bazel dan Starlark untuk mengetahui detail selengkapnya, dan template Rules SIG 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. Logika akan ditempatkan di 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
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 tidak melakukan apa pun, aturan ini sudah berperilaku seperti aturan lainnya: memiliki
nama wajib, 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 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
Buat beberapa pengamatan:
- "Evaluasi file bzl" dicetak terlebih dahulu. Sebelum mengevaluasi file
BUILD
, Bazel mengevaluasi semua file yang dimuatnya. Jika beberapa fileBUILD
memuat foo.bzl, Anda hanya akan melihat satu kemunculan "evaluasi file bzl" karena Bazel meng-cache hasil evaluasi. - Fungsi callback
_foo_binary_impl
tidak 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: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 dapat Anda lihat, _foo_binary_impl
kini 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, tetapi hanya memutar ulang peristiwa pencetakan. Terlepas dari
status cache, Anda akan 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 yang diberikan.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
Kode tersebut 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
file tersebut benar-benar diminta. Jadi, hal terakhir yang harus dilakukan adalah memberi tahu Bazel bahwa file
adalah output 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,
anggaplah 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 attr
dan 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 lain, 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 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 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 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, seiring template menjadi
lebih besar, memori akan menjadi lebih efisien untuk menempatkannya dalam file terpisah dan menghindari
pembuatan string besar selama fase analisis. Kedua, menggunakan file
terpisah lebih praktis 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 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 diawali dengan garis bawah bersifat pribadi dan tidak dapat ditetapkan dalam
file BUILD
. Template kini menjadi dependensi implisit: Setiap target
hello_world
memiliki dependensi pada file ini. Jangan lupa untuk membuat file ini terlihat
oleh paket lain dengan mengupdate file BUILD
dan menggunakan
exports_files
:
exports_files(["file.cc.tpl"])
Melangkah lebih jauh
- Lihat dokumentasi referensi untuk aturan.
- Memahami depset.
- Lihat repositori contoh yang menyertakan contoh aturan tambahan.