Starlark terlihat seperti Python
bahasa konfigurasi yang awalnya dikembangkan untuk
digunakan di Bazel dan sejak mengadopsi
oleh {i>tool<i} lainnya. File BUILD
dan .bzl
Bazel ditulis dalam dialek
Starlark dikenal sebagai "{i>Build Language<i}", meskipun sering kali
disebut sebagai "Starlark", terutama
ketika menekankan bahwa suatu fitur
yang diekspresikan dalam Build Language bukan merupakan suku cadang
dari Bazel. Bazel meningkatkan bahasa inti dengan berbagai fungsi terkait build
seperti glob
, genrule
, java_binary
, dan sebagainya.
Lihat Dokumentasi Bazel dan Starlark untuk detail selengkapnya, dan Template SIG Aturan sebagai untuk set 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. Logikanya akan masuk ke sana, tetapi Anda
biarkan fungsi ini 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 aturan tersebut tidak melakukan apa pun, aturan tersebut sudah berperilaku seperti aturan lainnya: aturan ini memiliki
nama wajib, mendukung atribut umum seperti visibility
, testonly
, dan
tags
.
Model evaluasi
Sebelum melangkah lebih jauh, penting untuk memahami cara mengevaluasi kode.
Update 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 dianalisis. Objek ctx
memiliki
banyak {i>field<i} dan metode
yang berguna; Anda bisa menemukan daftar lengkapnya 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" akan dicetak terlebih dahulu. Sebelum mengevaluasi file
BUILD
, Bazel mengevaluasi semua file yang dimuatnya. Jika beberapa fileBUILD
sedang dimuat foo.bzl, Anda hanya akan melihat satu kemunculan "evaluasi file bzl" karena Bazel menyimpan hasil evaluasi dalam cache. - Fungsi callback
_foo_binary_impl
tidak dipanggil. Kueri Bazel dimuatBUILD
file, tetapi tidak menganalisis target.
Untuk menganalisis target, gunakan cquery
("dikonfigurasi
kueri") 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
sekarang dipanggil dua kali - sekali untuk setiap target.
Perhatikan bahwa baik "evaluasi file bzl" atau "BUILD file" dicetak lagi,
karena evaluasi foo.bzl
di-cache setelah panggilan ke bazel query
.
Bazel hanya memunculkan pernyataan print
saat benar-benar dieksekusi.
Membuat file
Untuk membuat aturan lebih berguna, perbarui aturan untuk membuat file. Pertama, deklarasikan dan beri nama file tersebut. Dalam contoh ini, buat {i>file<i} dengan nama yang sama dengan targetnya:
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 Anda mendeklarasikan file, Anda harus
memberi tahu Bazel cara membuatnya dengan
menciptakan 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 mengajari Bazel
cara membuat file. Tapi Bazel tidak akan
membuat {i>file<i} sampai
sebenarnya diminta. Jadi hal terakhir yang harus dilakukan adalah
memberi tahu Bazel bahwa file
adalah output dari aturan, dan bukan file sementara yang digunakan di dalam aturan
terlepas dari implementasi layanan.
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,
mengasumsikan bahwa baris terakhir adalah cara untuk memilih {i>output<i} dari sebuah 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
Untuk membuat aturan lebih bermanfaat, 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 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]))]
Perhatikan 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, seperti attr.label
dan attr.label_list
,
mendeklarasikan dependensi dari target yang memiliki atribut ke target yang
label akan muncul di nilai atribut. Atribut semacam 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, melihat 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
file input (seperti file sumber dalam repositori), Anda dapat melakukannya dengan
allow_files
dan tentukan daftar ekstensi file yang diterima (atau True
untuk
izinkan ekstensi file):
"srcs": attr.label_list(allow_files = [".java"]),
Daftar file dapat diakses dengan ctx.files.<attribute name>
. Sebagai
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. Anda juga
dapat menggunakan ctx.actions.write
untuk menghasilkan string yang dibuat dalam aturan
implementasi yang sebenarnya, tetapi ini memiliki dua masalah. Pertama, saat {i>template<i}
jadi lebih besar, akan lebih hemat memori jika
dimasukkan ke dalam file terpisah dan menghindari
membangun {i>string<i} besar selama fase analisis. Kedua, dengan menggunakan
lebih nyaman bagi pengguna. Sebagai gantinya, gunakan
ctx.actions.expand_template
,
yang melakukan substitusi pada file {i>template<i}.
Buat atribut template
untuk mendeklarasikan dependensi pada template
file:
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 {i>template<i} ke pengguna akhir dan selalu gunakan atribut yang sama, Anda dapat menetapkan nilai default dan menjadikannya pribadi:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
Atribut yang dimulai dengan garis bawah bersifat pribadi dan tidak dapat diatur dalam
File BUILD
. Template sekarang menjadi dependensi implisit: Setiap hello_world
target memiliki dependensi pada file ini. Jangan lupa untuk membuat file ini dapat dilihat
ke paket lain dengan memperbarui file BUILD
dan menggunakan
exports_files
:
exports_files(["file.cc.tpl"])
Lebih jauh
- Lihat dokumentasi referensi untuk aturan.
- Pelajari depset.
- Lihat repositori contoh yang menyertakan contoh aturan tambahan.