Aturan menentukan serangkaian tindakan yang dilakukan Bazel input untuk menghasilkan satu set {i>output<i}, yang direferensikan pada penyedia yang ditampilkan oleh fungsi implementasi. Misalnya, sebuah C++ aturan biner mungkin:
- Ambil kumpulan file sumber
.cpp
(input). - Jalankan
g++
di file sumber (tindakan). - Tampilkan penyedia
DefaultInfo
dengan output yang dapat dieksekusi dan file lainnya agar tersedia pada saat runtime. - Tampilkan penyedia
CcInfo
dengan informasi khusus C++ yang dikumpulkan dari target dan dependensinya.
Dari perspektif Bazel, g++
dan library C++ standar juga merupakan input
terhadap aturan ini. Sebagai penulis aturan, Anda harus mempertimbangkan tidak hanya
ke aturan, tetapi juga semua alat dan library yang diperlukan untuk menjalankan
tindakannya.
Sebelum membuat atau memodifikasi aturan apa pun, pastikan Anda memahami metode Bazel fase build. Penting untuk memahami tiga fase pembangunan (pemuatan, analisis, dan eksekusi). Juga berguna untuk pelajari makro untuk memahami perbedaan antara aturan dan makro. Untuk memulai, tinjau Tutorial Aturan terlebih dahulu. Lalu, gunakan halaman ini sebagai referensi.
Beberapa aturan dibuat di Bazel sendiri. Aturan native ini, seperti
cc_library
dan java_binary
, menyediakan beberapa dukungan inti untuk bahasa tertentu.
Dengan menentukan aturan sendiri, Anda dapat menambahkan dukungan serupa untuk bahasa dan alat
yang tidak didukung oleh Bazel secara native.
Bazel menyediakan model ekstensibilitas untuk menulis aturan menggunakan
Starlark. Aturan ini ditulis dalam .bzl
file, yang
dapat dimuat langsung dari file BUILD
.
Saat menentukan aturan Anda sendiri, Anda harus memutuskan atribut apa yang didukung dan bagaimana ia menghasilkan {i>outputnya<i}.
Fungsi implementation
aturan menentukan perilaku persisnya selama
fase analisis. Fungsi ini tidak menjalankan
menggunakan perintah eksternal. Sebaliknya, ini mendaftarkan tindakan yang akan digunakan
nanti selama fase eksekusi untuk membuat output aturan, jika output tersebut
diperlukan.
Pembuatan aturan
Di dalam file .bzl
, gunakan fungsi rule untuk menentukan
aturan, dan menyimpan hasilnya dalam variabel global. Panggilan ke rule
menentukan
atribut dan atribut
fungsi penerapan:
example_library = rule(
implementation = _example_library_impl,
attrs = {
"deps": attr.label_list(),
...
},
)
Ini menentukan jenis aturan bernama example_library
.
Panggilan ke rule
juga harus menentukan apakah aturan membuat
Output yang dapat dieksekusi (dengan executable=True
), atau secara spesifik
pengujian yang dapat dieksekusi (dengan test=True
). Jika yang terakhir, aturannya adalah aturan pengujian,
dan nama aturan harus diakhiri dengan _test
.
Pembuatan instance target
Aturan dapat dimuat dan dipanggil dalam file BUILD
:
load('//some/pkg:rules.bzl', 'example_library')
example_library(
name = "example_target",
deps = [":another_target"],
...
)
Setiap panggilan ke aturan build tidak menampilkan nilai, tetapi memiliki efek samping dari penetapan target. Langkah ini disebut membuat instance aturan. Ini menetapkan nama untuk target baru dan nilai untuk atribut target.
Aturan juga dapat dipanggil dari fungsi Starlark dan dimuat dalam file .bzl
.
Fungsi Starlark yang memanggil aturan disebut makro Starlark.
Makro Starlark pada akhirnya harus dipanggil dari file BUILD
, dan hanya dapat
dipanggil selama fase pemuatan, saat BUILD
file dievaluasi untuk membuat instance target.
Atribut
Atribut adalah argumen aturan. Atribut dapat memberikan nilai tertentu ke penerapan target, atau mereka dapat merujuk ke target, sehingga membuat grafik dependensi.
Atribut khusus aturan, seperti srcs
atau deps
, ditentukan dengan meneruskan peta
dari nama atribut hingga skema (dibuat menggunakan attr
modul) ke parameter attrs
dari rule
.
Atribut umum, seperti
name
dan visibility
, secara implisit ditambahkan ke semua aturan. Tambahan
secara implisit ditambahkan ke
aturan yang dapat dijalankan dan diuji secara spesifik. Atribut yang
secara implisit ditambahkan ke aturan tidak dapat dimasukkan dalam kamus yang diteruskan ke
attrs
.
Atribut dependensi
Aturan yang memproses kode sumber biasanya menentukan atribut berikut yang akan ditangani berbagai jenis dependensi:
srcs
menentukan file sumber yang diproses oleh tindakan target. Sering kali, skema atribut menentukan ekstensi file mana yang diharapkan untuk pengurutan file sumber yang diproses aturan. Aturan untuk bahasa dengan file header umumnya menentukan atributhdrs
terpisah untuk header yang diproses oleh target dan konsumennya.deps
menentukan dependensi kode untuk target. Skema atribut harus menentukan penyedia mana yang harus disediakan oleh dependensi tersebut. (Untuk contoh,cc_library
menyediakanCcInfo
.)data
menentukan file yang akan disediakan saat runtime untuk semua file yang dapat dieksekusi yang bergantung pada target. Itu seharusnya memungkinkan file arbitrer yang ditentukan.
example_library = rule(
implementation = _example_library_impl,
attrs = {
"srcs": attr.label_list(allow_files = [".example"]),
"hdrs": attr.label_list(allow_files = [".header"]),
"deps": attr.label_list(providers = [ExampleInfo]),
"data": attr.label_list(allow_files = True),
...
},
)
Ini adalah contoh atribut dependensi. Atribut yang menentukan
label input (yang didefinisikan dengan
attr.label_list
,
attr.label
, atau
attr.label_keyed_string_dict
)
menentukan dependensi jenis tertentu
antara target dan target yang labelnya (atau label yang sesuai
Label
objek) tercantum dalam atribut tersebut saat target
didefinisikan. Repositori, dan mungkin jalur, untuk label ini telah diselesaikan
relatif terhadap target yang ditentukan.
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
Dalam contoh ini, other_target
adalah dependensi dari my_target
sehingga
other_target
dianalisis terlebih dahulu. Akan terjadi kesalahan jika
ada siklus di dalam
grafik dependensi target.
Atribut pribadi dan dependensi implisit
Atribut dependensi dengan nilai default membuat dependensi implisit. Ini
implisit karena merupakan bagian dari grafik target yang tidak
tentukan dalam file BUILD
. Dependensi implisit berguna untuk {i>hard-coding <i}
hubungan antara aturan dan alat (dependensi waktu build, seperti
kompiler), karena sering kali pengguna
tidak tertarik untuk menentukan
alat yang digunakan aturan. Di dalam fungsi penerapan aturan, nilai ini diperlakukan
sama dengan dependensi lainnya.
Jika Anda ingin menyediakan dependensi implisit tanpa mengizinkan pengguna untuk
mengganti nilai tersebut, Anda dapat membuat atribut menjadi pribadi dengan memberinya nama
yang dimulai dengan garis bawah (_
). Atribut pribadi harus memiliki nilai default
masing-masing. Umumnya hanya masuk akal untuk menggunakan
atribut privat untuk perintah implisit
dependensi.
example_library = rule(
implementation = _example_library_impl,
attrs = {
...
"_compiler": attr.label(
default = Label("//tools:example_compiler"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
)
Dalam contoh ini, setiap target jenis example_library
memiliki
dependensi pada compiler //tools:example_compiler
. Hal ini memungkinkan
fungsi implementasi example_library
untuk menghasilkan tindakan yang memanggil
compiler, meskipun pengguna tidak meneruskan labelnya sebagai input. Sejak
_compiler
adalah atribut pribadi, yang berarti ctx.attr._compiler
akan selalu menunjuk ke //tools:example_compiler
di semua target aturan ini
. Atau, Anda dapat memberi nama atribut compiler
tanpa
garis bawah dan tetap gunakan
nilai {i>default<i}. Hal ini memungkinkan pengguna untuk mengganti
kompilator berbeda jika perlu, tetapi tidak
membutuhkan pemahaman terhadap
label.
Dependensi implisit umumnya digunakan untuk alat yang berada di repositori sebagai implementasi aturan. Jika alat tersebut berasal dari platform eksekusi atau repositori lain, harus mendapatkan alat tersebut dari toolchain.
Atribut output
Atribut output, seperti attr.output
dan
attr.output_list
, deklarasikan file output yang
target dimunculkan. Atribut ini berbeda dari atribut dependensi dalam dua cara:
- Parameter ini menentukan target file output, bukan mengacu ke target yang telah ditentukan di tempat lain.
- Target file output bergantung pada target aturan yang dibuat instance-nya, bukan sebaliknya.
Biasanya, atribut output hanya digunakan ketika aturan perlu membuat output
dengan nama yang ditetapkan pengguna yang tidak dapat didasarkan pada nama target. Jika aturan memiliki
satu atribut output, biasanya bernama out
atau outs
.
Atribut output adalah cara yang direkomendasikan untuk membuat output yang dideklarasikan sebelumnya, yang dapat secara khusus bergantung pada atau yang diminta di command line.
Fungsi penerapan
Setiap aturan memerlukan fungsi implementation
. Fungsi-fungsi ini dijalankan
hanya dalam fase analisis dan mengubah
grafik target yang dihasilkan dalam fase pemuatan menjadi grafik
tindakan yang harus dilakukan selama fase eksekusi. Dengan demikian,
implementasi tidak bisa benar-benar
membaca atau menulis file.
Fungsi penerapan aturan biasanya bersifat pribadi (dinamai dengan awalan
garis bawah). Secara konvensional, {i>string<i} diberi nama yang sama dengan aturannya, tetapi diberi akhiran
dengan _impl
.
Fungsi penerapan mengambil tepat satu parameter:
konteks aturan, yang secara konvensional diberi nama ctx
. Mereka mengembalikan
daftar
penyedia.
Target
Dependensi direpresentasikan pada waktu analisis sebagai Target
objek terstruktur dalam jumlah besar. Objek ini berisi penyedia yang dihasilkan saat
fungsi implementasi target telah dieksekusi.
ctx.attr
memiliki kolom yang sesuai dengan nama masing-masing
atribut dependensi, berisi objek Target
yang mewakili setiap
dependensi melalui atribut tersebut. Untuk atribut label_list
, ini adalah daftar
Targets
. Untuk atribut label
, ini adalah satu Target
atau None
.
Daftar objek penyedia ditampilkan oleh fungsi implementasi target:
return [ExampleInfo(headers = depset(...))]
Penyedia tersebut dapat diakses menggunakan notasi indeks ([]
), dengan jenis penyedia sebagai
kunci. Ini bisa berupa penyedia kustom yang ditentukan di Starlark atau
penyedia untuk aturan native tersedia sebagai Starlark
variabel global.
Misalnya, jika aturan mengambil file header melalui atribut hdrs
dan memberikan
mereka ke tindakan kompilasi target dan konsumennya, maka bisa
kumpulkan seperti ini:
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
Untuk gaya lama yang mana struct
ditampilkan dari
fungsi implementasi target, bukan daftar objek penyedia:
return struct(example_info = struct(headers = depset(...)))
Penyedia dapat diambil dari kolom objek Target
yang sesuai:
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
Gaya ini sangat tidak disarankan dan aturannya harus bermigrasi dari lokasi tersebut.
File
File direpresentasikan oleh objek File
. Karena Bazel tidak
melakukan I/O file selama fase analisis, objek ini tidak dapat digunakan untuk
secara langsung membaca atau
menulis isi file. Sebaliknya, akan diteruskan ke tindakan-emitting
fungsi (lihat ctx.actions
) untuk membuat bagian-bagian dari
grafik tindakan.
File
dapat berupa file sumber atau file yang dihasilkan. Setiap file yang dibuat
harus merupakan output dari satu tindakan saja. File sumber tidak bisa menjadi {i>output<i} dari
tindakan apa pun.
Untuk setiap atribut dependensi, isian
yang sesuai dari
ctx.files
berisi daftar output default dari semua
dependensi melalui atribut tersebut:
def _example_library_impl(ctx):
...
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
...
ctx.file
berisi satu File
atau None
untuk
atribut dependensi yang spesifikasinya ditetapkan allow_single_file=True
.
ctx.executable
berperilaku sama seperti ctx.file
, tetapi hanya
berisi kolom untuk atribut dependensi yang spesifikasinya ditetapkan executable=True
.
Mendeklarasikan output
Selama fase analisis, fungsi penerapan aturan dapat menghasilkan output.
Karena semua label harus diketahui
selama fase pemuatan, label tambahan ini
output tidak memiliki label. Objek File
untuk output dapat dibuat menggunakan
ctx.actions.declare_file
dan
ctx.actions.declare_directory
. Sering kali,
nama {i>output<i} didasarkan
pada nama target,
ctx.label.name
:
def _example_library_impl(ctx):
...
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
...
Untuk output yang telah dideklarasikan, seperti output yang dibuat untuk
atribut output, objek File
dapat diambil
dari kolom ctx.outputs
yang sesuai.
Tindakan
Sebuah tindakan menjelaskan cara menghasilkan satu set {i>output<i} dari satu set input, contoh "jalankan gcc pada hello.c dan dapatkan hello.o". Saat tindakan dibuat, Bazel tidak menjalankan perintah dengan seketika. Ia mendaftarkannya dalam grafik dependensi, karena suatu tindakan dapat bergantung pada {i>output<i} dari tindakan lain. Misalnya, dalam C, penaut harus dipanggil setelah compiler.
Fungsi tujuan umum yang membuat tindakan didefinisikan di
ctx.actions
:
ctx.actions.run
, untuk menjalankan file yang dapat dieksekusi.ctx.actions.run_shell
, untuk menjalankan shell perintah.ctx.actions.write
, untuk menulis string ke file.ctx.actions.expand_template
, ke membuat file dari template.
ctx.actions.args
dapat digunakan untuk secara efisien
mengumpulkan argumen untuk tindakan. Menghindari depset yang diratakan hingga
waktu eksekusi:
def _example_library_impl(ctx):
...
transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
inputs = depset(srcs, transitive=[headers])
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
args = ctx.actions.args()
args.add_joined("-h", headers, join_with=",")
args.add_joined("-s", srcs, join_with=",")
args.add("-o", output_file)
ctx.actions.run(
mnemonic = "ExampleCompile",
executable = ctx.executable._compiler,
arguments = [args],
inputs = inputs,
outputs = [output_file],
)
...
Tindakan mengambil daftar atau depset file input dan membuat daftar (tidak kosong) file output. Kumpulan file input dan output harus diketahui selama fase analisis. Hal itu mungkin tergantung pada nilai , termasuk penyedia dari dependensi, tetapi tidak dapat bergantung pada hasil eksekusi. Misalnya, jika tindakan Anda menjalankan perintah {i>unzip<i}, Anda harus menentukan file mana yang ingin di-inflate (sebelum menjalankan ekstrak). Tindakan yang membuat sejumlah variabel file secara internal dapat menggabungkannya dalam file tunggal (seperti zip, tar, atau format arsip lainnya).
Tindakan harus mencantumkan semua inputnya. Input listingan yang tidak digunakan adalah diizinkan, tetapi tidak efisien.
Tindakan harus membuat semua output-nya. Mereka dapat menulis file lain, tetapi segala sesuatu yang tidak ada dalam {i>output<i} tidak akan tersedia bagi konsumen. Semua output yang dinyatakan harus ditulis oleh tindakan tertentu.
Tindakan sebanding dengan fungsi murni: Tindakan tersebut seharusnya hanya bergantung pada input yang diberikan, dan menghindari mengakses informasi komputer, nama pengguna, jam, jaringan, atau perangkat I/O (kecuali untuk input pembacaan dan output penulisan). Ini adalah penting karena {i>output<i} akan di-cache dan digunakan kembali.
Dependensi diselesaikan oleh Bazel, yang akan memutuskan tindakan mana yang telah dijalankan. Akan terjadi error jika terdapat siklus pada grafik dependensi. Membuat suatu tindakan tidak menjamin bahwa tindakan itu akan dieksekusi, itu tergantung pada apakah output-nya diperlukan untuk build.
Penyedia
Penyedia adalah informasi yang diekspos oleh aturan ke aturan lain yang yang bergantung pada {i>database.<i} Data ini dapat mencakup file output, library, parameter yang akan diteruskan pada baris perintah alat, atau apa pun yang harus diketahui oleh konsumen target lebih lanjut.
Karena fungsi implementasi aturan hanya bisa membaca penyedia dari
dependensi langsung target yang dibuat instance, aturan perlu meneruskan
informasi dari dependensi target yang perlu diketahui oleh
konsumen, umumnya dengan mengakumulasinya ke dalam depset
.
Penyedia target ditentukan oleh daftar objek Provider
yang ditampilkan oleh
fungsi implementasi.
Fungsi implementasi lama juga dapat ditulis dalam gaya lama di mana
fungsi implementasi menampilkan struct
, bukan daftar
objek penyedia. Gaya ini sangat tidak disarankan dan aturannya harus
bermigrasi dari lokasi tersebut.
Output default
Output default target adalah output yang diminta secara default saat
target diminta untuk dibangun di baris perintah. Sebagai contoh,
java_library
target //pkg:foo
memiliki foo.jar
sebagai output default, sehingga
akan dibuat oleh perintah bazel build //pkg:foo
.
Output default ditentukan oleh parameter files
DefaultInfo
:
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
Jika DefaultInfo
tidak ditampilkan oleh penerapan aturan atau files
parameter tidak ditentukan, default DefaultInfo.files
adalah semua
output yang telah dideklarasikan (umumnya, output yang dibuat oleh output
).
Aturan yang melakukan tindakan harus memberikan output default, meskipun output tersebut tidak dapat digunakan secara langsung. Tindakan yang tidak ada dalam grafik output yang diminta akan dipangkas. Jika {i>output<i} hanya digunakan oleh konsumen target, tindakan tersebut tidak akan dilakukan ketika target dibuat secara terpisah. Ini membuat proses debug menjadi lebih sulit karena membangun ulang hanya target yang gagal tidak akan melakukan reka ulang kegagalan.
{i>Runfile<i}
Runfiles adalah kumpulan file yang digunakan oleh target saat runtime (bukan build sebelumnya). Selama fase eksekusi, Bazel membuat pohon direktori yang berisi {i>symlink<i} yang menunjuk ke {i>runfile<i}. Proses ini akan tahap biner agar dapat mengakses {i>runfile<i} selama {i>runtime<i}.
Runfile dapat ditambahkan secara manual selama pembuatan aturan.
Objek runfiles
dapat dibuat dengan metode runfiles
di konteks aturan, ctx.runfiles
dan diteruskan ke
runfiles
pada DefaultInfo
. Output yang dapat dieksekusi dari
aturan yang dapat dieksekusi secara implisit ditambahkan ke runfile.
Beberapa aturan menetapkan atribut, yang diberi nama secara umum
data
, yang outputnya ditambahkan
target {i>runfiles<i}. Runfile juga harus digabungkan dari data
, serta
dari atribut apa pun yang dapat menyediakan kode untuk eksekusi akhir, umumnya
srcs
(yang mungkin berisi filegroup
target dengan data
terkait) dan
deps
.
def _example_library_impl(ctx):
...
runfiles = ctx.runfiles(files = ctx.files.data)
transitive_runfiles = []
for runfiles_attr in (
ctx.attr.srcs,
ctx.attr.hdrs,
ctx.attr.deps,
ctx.attr.data,
):
for target in runfiles_attr:
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
runfiles = runfiles.merge_all(transitive_runfiles)
return [
DefaultInfo(..., runfiles = runfiles),
...
]
Penyedia kustom
Penyedia dapat ditentukan menggunakan provider
untuk menyampaikan informasi khusus aturan:
ExampleInfo = provider(
"Info needed to compile/link Example code.",
fields={
"headers": "depset of header Files from transitive dependencies.",
"files_to_link": "depset of Files from compilation.",
})
Fungsi implementasi aturan kemudian dapat membuat dan menampilkan instance penyedia:
def _example_library_impl(ctx):
...
return [
...
ExampleInfo(
headers = headers,
files_to_link = depset(
[output_file],
transitive = [
dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
],
),
)
]
Inisialisasi kustom penyedia
Anda dapat menjaga pembuatan instance penyedia dengan pra-pemrosesan dan logika validasi. Hal ini dapat digunakan untuk memastikan bahwa semua instance penyedia mematuhi invarian tertentu, atau memberi pengguna API yang lebih bersih untuk untuk mendapatkan instance.
Hal ini dilakukan dengan meneruskan callback init
ke
Fungsi provider
. Jika callback ini diberikan, metode
jenis nilai yang ditampilkan provider()
berubah menjadi tuple dari dua nilai: penyedia
simbol yang merupakan nilai hasil biasa saat init
tidak digunakan, dan "raw
"Konstruktor".
Dalam hal ini, ketika simbol penyedia dipanggil, alih-alih langsung mengembalikan
instance baru, tindakan ini akan meneruskan argumen ke callback init
. Tujuan
nilai yang ditampilkan callback harus berupa nama kolom pemetaan dict (string) ke nilai;
ini digunakan untuk menginisialisasi kolom-kolom dari instance baru. Perhatikan bahwa
mungkin memiliki tanda tangan apa pun, dan jika argumen tidak cocok dengan tanda tangan
error dilaporkan seolah-olah callback dipanggil secara langsung.
Sebaliknya, konstruktor mentah akan mengabaikan callback init
.
Contoh berikut menggunakan init
untuk melakukan prapemrosesan dan memvalidasi argumennya:
# //pkg:exampleinfo.bzl
_core_headers = [...] # private constant representing standard library files
# It's possible to define an init accepting positional arguments, but
# keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
if not files_to_link and not allow_empty_files_to_link:
fail("files_to_link may not be empty")
all_headers = depset(_core_headers, transitive = headers)
return {'files_to_link': files_to_link, 'headers': all_headers}
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init)
export ExampleInfo
Selanjutnya, implementasi aturan dapat membuat instance penyedia sebagai berikut:
ExampleInfo(
files_to_link=my_files_to_link, # may not be empty
headers = my_headers, # will automatically include the core headers
)
Konstruktor mentah dapat digunakan untuk menentukan fungsi factory publik alternatif
yang tidak melalui logika init
. Misalnya, dalam exampleinfo.bzl kita
dapat menentukan:
def make_barebones_exampleinfo(headers):
"""Returns an ExampleInfo with no files_to_link and only the specified headers."""
return _new_exampleinfo(files_to_link = depset(), headers = all_headers)
Biasanya, konstruktor mentah terikat dengan variabel yang namanya dimulai dengan
garis bawah (_new_exampleinfo
di atas), sehingga kode pengguna tidak dapat memuatnya dan
menghasilkan instance penyedia arbitrer.
Penggunaan lain untuk init
adalah hanya mencegah pengguna memanggil penyedia
simbol sama sekali, dan memaksa mereka untuk
menggunakan fungsi {i>factory<i} sebagai gantinya:
def _exampleinfo_init_banned(*args, **kwargs):
fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init_banned)
def make_exampleinfo(...):
...
return _new_exampleinfo(...)
Aturan yang dapat dijalankan dan aturan pengujian
Aturan yang dapat dieksekusi menentukan target yang dapat dipanggil oleh perintah bazel run
.
Aturan pengujian adalah jenis aturan khusus
yang dapat dieksekusi yang targetnya juga
dipanggil oleh perintah bazel test
. Aturan yang dapat dijalankan
dan pengujian dibuat oleh
menetapkan executable
atau
Argumen test
ke True
dalam panggilan ke rule
:
example_binary = rule(
implementation = _example_binary_impl,
executable = True,
...
)
example_test = rule(
implementation = _example_binary_impl,
test = True,
...
)
Aturan pengujian harus memiliki nama yang diakhiri dengan _test
. (Uji juga nama target
diakhiri dengan _test
berdasarkan konvensi, tetapi ini tidak diwajibkan.) Aturan non-pengujian tidak boleh
memiliki akhiran ini.
Kedua jenis aturan tersebut harus menghasilkan file output yang dapat dieksekusi (yang mungkin atau tidak
dideklarasikan sebelumnya) yang akan dipanggil oleh perintah run
atau test
. Untuk memberitahu
Bazel {i>output<i} aturan mana yang akan digunakan
sebagai {i>executable<i} ini, teruskan sebagai
Argumen executable
dari DefaultInfo
yang ditampilkan
penyedia layanan. executable
tersebut ditambahkan ke output default aturan (sehingga Anda
tidak perlu meneruskannya ke executable
dan files
). Hal ini juga secara implisit
ditambahkan ke runfiles:
def _example_binary_impl(ctx):
executable = ctx.actions.declare_file(ctx.label.name)
...
return [
DefaultInfo(executable = executable, ...),
...
]
Tindakan yang menghasilkan file ini harus menetapkan bit yang dapat dieksekusi pada file tersebut. Sebagai
ctx.actions.run
atau
Tindakan ctx.actions.run_shell
yang harus dilakukan
oleh fitur dasar yang dipanggil oleh tindakan. Untuk
Tindakan ctx.actions.write
, teruskan is_executable=True
.
Sebagai perilaku lama, aturan yang dapat dieksekusi memiliki
output khusus ctx.outputs.executable
yang telah dideklarasikan sebelumnya. File ini berfungsi sebagai
default yang dapat dieksekusi jika Anda tidak menentukannya menggunakan DefaultInfo
; tidak boleh
digunakan sebaliknya. Mekanisme output ini tidak digunakan lagi karena tidak mendukung
menyesuaikan nama file {i>executable<i}
pada waktu analisis.
Lihat contoh aturan yang dapat dijalankan dan aturan pengujian.
Aturan yang dapat dijalankan dan aturan pengujian memiliki didefinisikan secara implisit, selain yang ditambahkan untuk semua aturan. Default dari Atribut yang ditambahkan secara implisit tidak dapat diubah, meskipun ini dapat diatasi dengan menggabungkan aturan pribadi dalam makro Starlark yang mengubah default:
def example_test(size="small", **kwargs):
_example_test(size=size, **kwargs)
_example_test = rule(
...
)
Lokasi Runfiles
Saat target yang dapat dieksekusi dijalankan dengan bazel run
(atau test
), root dari
direktori {i>runfiles <i}berdekatan dengan {i>executable<i}. Jalur tersebut berhubungan sebagai berikut:
# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
runfiles_root, workspace_name, runfile_path)
Jalur ke File
di bawah direktori runfiles sesuai dengan
File.short_path
.
Biner yang dieksekusi langsung oleh bazel
berdekatan dengan root
Direktori runfiles
. Akan tetapi, biner yang disebut dari runfile tidak dapat membuat
asumsi yang sama. Untuk memitigasi ini, setiap
biner harus menyediakan cara untuk
menerima root runfiles-nya sebagai parameter menggunakan lingkungan atau command line
argumen/tanda. Ini memungkinkan biner meneruskan root runfile kanonis yang benar
ke kode biner yang dipanggilnya. Jika itu tidak diatur, biner
dapat menebak bahwa itu adalah
biner pertama yang dipanggil dan cari
direktori {i>runfiles<i} di sebelahnya.
Topik lanjutan
Meminta file output
Satu target dapat memiliki beberapa file output. Saat perintah bazel build
berjalan, beberapa {i>output<i} dari target
yang diberikan ke perintah itu dianggap
diminta. Bazel hanya membangun file
yang diminta dan file yang mereka
bergantung pada orang lain secara langsung
maupun tidak langsung. (Dalam hal grafik aksi, Bazel hanya
menjalankan tindakan yang dapat dijangkau sebagai dependensi transitif dari
file yang diminta.)
Selain output default, setiap output yang dideklarasikan sebelumnya dapat
secara eksplisit diminta pada baris perintah. Aturan dapat menentukan
output melalui atribut output. Dalam hal ini, pengguna
secara eksplisit memilih label untuk {i>output<i} saat mereka membuat instance aturan. Untuk mendapatkan
File
untuk atribut output, gunakan atribut
dari ctx.outputs
. Aturan dapat
secara implisit menentukan output yang dideklarasikan sebelumnya berdasarkan
pada nama target, tetapi fitur ini sudah tidak digunakan lagi.
Selain output default, ada grup output, yang merupakan koleksi
{i>output file<i} yang mungkin
diminta bersama. Permintaan ini dapat diminta dengan
--output_groups
Sebagai
contoh, jika target //pkg:mytarget
adalah jenis aturan yang memiliki debug_files
grup output, file ini dapat dibuat dengan menjalankan bazel build //pkg:mytarget
--output_groups=debug_files
. Karena {i>output<i} yang tidak dideklarasikan tidak memiliki label,
permintaan hanya dapat diminta dengan muncul di output default atau output
ras.
Grup output dapat ditentukan dengan
Penyedia OutputGroupInfo
. Perhatikan bahwa tidak seperti banyak
penyedia bawaan, OutputGroupInfo
dapat mengambil parameter dengan nama arbitrer
untuk menentukan grup output dengan nama tersebut:
def _example_library_impl(ctx):
...
debug_file = ctx.actions.declare_file(name + ".pdb")
...
return [
DefaultInfo(files = depset([output_file]), ...),
OutputGroupInfo(
debug_files = depset([debug_file]),
all_files = depset([output_file, debug_file]),
),
...
]
Juga tidak seperti kebanyakan penyedia, OutputGroupInfo
dapat ditampilkan oleh
aspect dan target aturan tempat aspek tersebut diterapkan,
selama tidak menentukan grup output yang sama. Dalam hal ini, hasil
penyedia digabungkan.
Perlu diperhatikan bahwa OutputGroupInfo
umumnya tidak boleh digunakan untuk menyampaikan jenis tertentu
file dari target hingga
tindakan konsumennya. Mendefinisikan
penyedia khusus aturan untuk hal tersebut.
Konfigurasi
Bayangkan Anda ingin membangun biner C++ untuk arsitektur yang berbeda. Tujuan bisa menjadi kompleks dan melibatkan beberapa langkah. Beberapa tingkat menengah biner, seperti kompilator dan generator kode, harus berjalan di platform eksekusi (yang bisa jadi host Anda, atau eksekutor jarak jauh). Beberapa biner seperti {i>output<i} akhir harus dibangun untuk arsitektur target.
Karena alasan ini, Bazel memiliki konsep “konfigurasi” dan transisi. Tujuan target paling atas (yang diminta di baris perintah) dibangun di "target" konfigurasi sementara alat yang harus berjalan pada platform eksekusi dibangun dalam {i>exec<i} konfigurasi Anda. Aturan dapat menghasilkan tindakan yang berbeda berdasarkan pada konfigurasi, misalnya untuk mengubah arsitektur {i>cpu<i} yang diteruskan ke compiler. Dalam beberapa kasus, library yang sama mungkin diperlukan untuk konfigurasi standar. Jika ini terjadi, model itu akan dianalisa dan berpotensi dibangun beberapa kali.
Secara {i>default<i}, Bazel membangun dependensi target dalam konfigurasi yang sama dengan target itu sendiri, dengan kata lain tanpa transisi. Ketika sebuah dependensi adalah yang diperlukan untuk membantu membangun target, atribut terkait harus menentukan transisi ke konfigurasi exec. Hal ini menyebabkan alat dan semua dependensi yang dibangun untuk platform eksekusi.
Untuk setiap atribut dependensi, Anda dapat menggunakan cfg
untuk menentukan apakah dependensi
harus membangun dalam konfigurasi yang sama
atau transisi ke konfigurasi exec.
Jika atribut dependensi memiliki flag executable=True
, cfg
harus ditetapkan
secara eksplisit. Hal ini untuk mencegah terjadinya kesalahan
pembuatan alat secara tidak sengaja
konfigurasi Anda.
Lihat contoh
Secara umum, sumber, library dependen, dan file yang dapat dieksekusi yang akan diperlukan di {i>runtime<i} dapat menggunakan konfigurasi yang sama.
Alat yang dijalankan sebagai bagian dari build (seperti compiler atau generator kode)
harus dibuat untuk
konfigurasi exec. Dalam hal ini, tentukan cfg="exec"
di
atribut ini.
Jika tidak, file yang dapat dieksekusi yang digunakan saat runtime (seperti bagian dari pengujian) harus
dibangun untuk konfigurasi target. Dalam hal ini, tentukan cfg="target"
di
atribut ini.
cfg="target"
sebenarnya tidak melakukan apa pun: ini hanya nilai kenyamanan untuk
membantu desainer aturan
untuk menjelaskan niat mereka secara eksplisit. Saat executable=False
,
yang berarti cfg
bersifat opsional, hanya tetapkan ini jika benar-benar membantu keterbacaan.
Anda juga dapat menggunakan cfg=my_transition
untuk menggunakan
transisi yang ditentukan pengguna, yang memungkinkan
penulis aturan memiliki banyak fleksibilitas dalam mengubah konfigurasi, dengan
kekurangan dari
membuat grafik build lebih besar dan kurang dapat dipahami.
Catatan: Sebelumnya, Bazel tidak memiliki konsep platform eksekusi, dan sebagai gantinya, semua tindakan pembangunan dianggap berjalan di mesin {i>host<i}. Karena itu, ada satu "{i>host<i}" konfigurasi, dan "host" transisi yang dapat digunakan untuk membangun dependensi dalam konfigurasi {i>host<i}. Banyak aturan masih menggunakan "host" untuk {i>tool<i} mereka, tetapi saat ini tidak digunakan lagi dan dimigrasikan untuk menggunakan "exec" jika memungkinkan.
Ada banyak perbedaan antara jenis "host" dan "exec" konfigurasi:
- "host" adalah terminal, "exec" bukan: Setelah dependensi berada di dalam "host" konfigurasi, tidak ada lagi transisi yang diperbolehkan. Anda dapat terus melangkah lebih jauh transisi konfigurasi setelah Anda berada di dalam “exec” konfigurasi Anda.
- "host" yang monolitik, "exec" tidak: Hanya ada satu "{i>host<i}" konfigurasi, tetapi mungkin ada {i> exec<i} yang berbeda untuk setiap eksekusi terkelola sepenuhnya.
- "host" mengasumsikan bahwa Anda menjalankan alat pada komputer yang sama dengan Bazel, atau pada komputer yang sangat mirip. Ini tidak lagi benar: Anda dapat menjalankan build tindakan di komputer lokal Anda, atau pada eksekutor jarak jauh, dan tidak ada pastikan bahwa eksekutor jarak jauh memiliki CPU dan OS yang sama dengan mesin Linux dan Windows.
Baik "exec" dan "host" menerapkan perubahan opsi yang sama, (misalnya,
setel --compilation_mode
dari --host_compilation_mode
, setel --cpu
dari
--host_cpu
, dll.). Perbedaannya adalah "host" konfigurasi dimulai dengan
nilai default dari semua tanda lainnya, sedangkan "exec" konfigurasi
dimulai dengan nilai flag current, berdasarkan konfigurasi target.
Fragmen konfigurasi
Aturan dapat mengakses
fragmen konfigurasi seperti
cpp
, java
, dan jvm
. Namun, semua fragmen yang diperlukan harus dideklarasikan di
untuk menghindari kesalahan akses:
def _impl(ctx):
# Using ctx.fragments.cpp leads to an error since it was not declared.
x = ctx.fragments.java
...
my_rule = rule(
implementation = _impl,
fragments = ["java"], # Required fragments of the target configuration
host_fragments = ["java"], # Required fragments of the host configuration
...
)
ctx.fragments
hanya menyediakan fragmen konfigurasi untuk target
konfigurasi Anda. Jika Anda ingin mengakses fragmen untuk konfigurasi host, gunakan
Sebagai gantinya, ctx.host_fragments
.
Symlink Runfiles
Biasanya, jalur relatif file di hierarki runfiles sama dengan
jalur relatif file itu dalam pohon sumber
atau pohon {i>output<i} yang dihasilkan. Jika
harus berbeda karena alasan tertentu, Anda dapat menentukan root_symlinks
atau
Argumen symlinks
. root_symlinks
adalah kamus yang memetakan jalur untuk
file, dengan jalur relatif terhadap
root direktori runfiles. Tujuan
Kamus symlinks
sama, tetapi jalur secara implisit diawali dengan
nama ruang kerja.
...
runfiles = ctx.runfiles(
root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
)
# Creates something like:
# sometarget.runfiles/
# some/
# path/
# here.foo -> some_data_file2
# <workspace_name>/
# some/
# path/
# here.bar -> some_data_file3
Jika symlinks
atau root_symlinks
digunakan, berhati-hatilah agar tidak memetakan dua
file ke jalur yang sama di hierarki runfiles. Tindakan ini akan menyebabkan build gagal
dengan kesalahan yang
menjelaskan konflik. Untuk memperbaikinya, Anda perlu memodifikasi
Argumen ctx.runfiles
untuk menghilangkan tabrakan. Pemeriksaan ini akan
dilakukan untuk
semua target yang menggunakan aturan Anda, serta target apa pun yang bergantung pada
target. Hal ini sangat berisiko jika alat Anda kemungkinan digunakan secara transitif
oleh {i>tool<i} lain; nama {i>symlink<i} harus unik di seluruh
{i>runfile<i} suatu alat dan
semua dependensinya.
Cakupan kode
Saat perintah coverage
dijalankan,
mungkin perlu menambahkan instrumentasi cakupan
untuk target tertentu. Tujuan
juga mengumpulkan daftar file sumber yang diinstrumentasikan. Subset dari
target yang dianggap dikontrol oleh tanda
--instrumentation_filter
Target pengujian dikecualikan, kecuali
--instrument_test_targets
ditentukan.
Jika implementasi aturan menambahkan instrumentasi cakupan pada waktu build, implementasi tersebut perlu memperhitungkan hal itu dalam fungsi implementasinya. ctx.coverage_instrumented menampilkan nilai benar dalam mode cakupan jika sumber target harus diinstrumentasikan:
# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
# Do something to turn on coverage for this compile action
Logika yang harus selalu aktif dalam mode cakupan (apakah sumber target yang secara khusus diinstrumentasikan atau tidak) dapat ditentukan berdasarkan ctx.configuration.coverage_enabled.
Jika aturan secara langsung menyertakan sumber dari dependensinya sebelum kompilasi (seperti file header), mungkin juga perlu mengaktifkan instrumentasi waktu kompilasi jika dependensi' sumber harus diinstrumentasikan:
# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
(ctx.coverage_instrumented() or
any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
# Do something to turn on coverage for this compile action
Aturan juga harus menyediakan informasi tentang atribut mana yang relevan dengan
cakupan dengan penyedia InstrumentedFilesInfo
, yang dibuat menggunakan
coverage_common.instrumented_files_info
.
Parameter dependency_attributes
dari instrumented_files_info
harus mencantumkan
semua atribut dependensi runtime, termasuk dependensi kode seperti deps
dan
dependensi data seperti data
. Parameter source_attributes
harus mencantumkan
atribut file sumber aturan jika instrumentasi cakupan mungkin ditambahkan:
def _example_library_impl(ctx):
...
return [
...
coverage_common.instrumented_files_info(
ctx,
dependency_attributes = ["deps", "data"],
# Omitted if coverage is not supported for this rule:
source_attributes = ["srcs", "hdrs"],
)
...
]
Jika InstrumentedFilesInfo
tidak ditampilkan, nilai default akan dibuat dengan masing-masing
atribut dependensi non-alat yang tidak ditetapkan
cfg
ke "host"
atau "exec"
dalam skema atribut) di
dependency_attributes
. (Ini bukan perilaku yang ideal, karena ia menempatkan atribut
seperti srcs
di dependency_attributes
, bukan source_attributes
, tetapi
menghindari kebutuhan konfigurasi cakupan eksplisit untuk semua aturan di
rantai dependensi.)
Tindakan Validasi
Terkadang Anda perlu memvalidasi sesuatu tentang build, dan informasi yang diperlukan untuk melakukan validasi itu hanya tersedia dalam artefak (file sumber atau file yang dihasilkan). Karena informasi ini dalam artefak, aturan tidak dapat melakukan validasi ini pada waktu analisis karena aturan tidak dapat membaca . Sebagai gantinya, tindakan harus melakukan validasi ini pada waktu eksekusi. Kapan validasi gagal, tindakan akan gagal, begitu juga build.
Contoh validasi yang mungkin dijalankan adalah analisis statis, analisis lint, pemeriksaan dependensi dan konsistensi, dan pemeriksaan gaya.
Tindakan validasi juga dapat membantu meningkatkan performa build dengan memindahkan bagian tindakan yang tidak diperlukan untuk membangun artefak menjadi tindakan terpisah. Misalnya, jika satu tindakan yang melakukan kompilasi dan analisis lint bisa dipisahkan menjadi tindakan kompilasi dan tindakan lint, lalu lint dapat dijalankan sebagai tindakan validasi dan dijalankan secara paralel dengan tindakan lainnya.
"Tindakan validasi" ini sering kali tidak menghasilkan apa pun yang digunakan di tempat lain dalam build, karena mereka hanya perlu menegaskan hal-hal tentang input mereka. Ini menimbulkan masalah: Jika tindakan validasi tidak menghasilkan apa pun yang digunakan di tempat lain dalam build, bagaimana cara aturan menjalankan tindakan? Sebelumnya, pendekatannya adalah membuat tindakan validasi menghasilkan {i>file<i}, dan menambahkan {i>output<i} itu secara artifisial ke input dari beberapa tindakan dalam build:
Ini berhasil, karena Bazel akan selalu menjalankan tindakan validasi saat kompilasi dijalankan, tetapi ini memiliki kelemahan yang signifikan:
Tindakan validasi berada di jalur kritis build. Karena Bazel berpikir bahwa {i>output<i} kosong diperlukan untuk menjalankan tindakan kompilasi, ia akan menjalankan tindakan validasi terlebih dahulu, meskipun tindakan kompilasi akan mengabaikan input. Tindakan ini akan mengurangi paralelisme dan memperlambat build.
Jika tindakan lain dalam build mungkin berjalan, bukan kompilasi tindakan, maka output kosong dari tindakan validasi perlu ditambahkan ke tindakan tersebut juga (misalnya, output jar sumber
java_library
). Ini adalah juga masalah jika tindakan baru yang mungkin berjalan sebagai ganti tindakan kompilasi ditambahkan nanti, dan {i>output<i} validasi kosong secara tidak sengaja tertinggal.
Solusi untuk masalah ini adalah dengan menggunakan Grup Output Validasi.
Grup Output Validasi
Grup Output Validasi adalah grup output yang dirancang untuk menyimpan {i>output<i} yang tidak digunakan dari tindakan validasi, sehingga mereka tidak perlu ditambahkan ke input tindakan lain.
Grup ini istimewa karena {i>output-<i}nya selalu diminta, terlepas dari
nilai flag --output_groups
, dan terlepas dari bagaimana target tersebut
bergantung pada (misalnya, baris perintah, sebagai dependensi, atau melalui
output implisit target). Perhatikan bahwa caching dan inkrementalitas normal
masih berlaku: jika input ke tindakan validasi belum berubah dan
tindakan validasi sebelumnya berhasil, maka tindakan validasi tidak akan
akan dijalankan.
Menggunakan grup {i>output<i} ini masih membutuhkan tindakan validasi yang menghasilkan beberapa file, bahkan yang kosong. Anda mungkin perlu menggabungkan beberapa alat yang biasanya tidak membuat {i>output<i} sehingga sebuah file dibuat.
Tindakan validasi target tidak berjalan dalam tiga kasus:
- Kapan target menjadi dependensi sebagai alat
- Ketika target dijadikan dependensi sebagai dependensi implisit (misalnya, atribut yang diawali dengan "_")
- Saat target di-build di konfigurasi host atau exec.
Diasumsikan bahwa target ini memiliki build dan pengujian terpisah yang akan menemukan kegagalan validasi.
Menggunakan Grup Output Validasi
Grup Output Validasi diberi nama _validation
dan digunakan seperti yang lainnya
grup output:
def _rule_with_validation_impl(ctx):
ctx.actions.write(ctx.outputs.main, "main output\n")
ctx.actions.write(ctx.outputs.implicit, "implicit output\n")
validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
ctx.actions.run(
outputs = [validation_output],
executable = ctx.executable._validation_tool,
arguments = [validation_output.path])
return [
DefaultInfo(files = depset([ctx.outputs.main])),
OutputGroupInfo(_validation = depset([validation_output])),
]
rule_with_validation = rule(
implementation = _rule_with_validation_impl,
outputs = {
"main": "%{name}.main",
"implicit": "%{name}.implicit",
},
attrs = {
"_validation_tool": attr.label(
default = Label("//validation_actions:validation_tool"),
executable = True,
cfg = "exec"),
}
)
Perhatikan bahwa file output validasi tidak ditambahkan ke DefaultInfo
atau
input ke tindakan lainnya. Tindakan validasi untuk target jenis aturan ini
akan tetap berjalan jika target bergantung pada label, atau parameter
output implisit bergantung secara langsung atau tidak langsung.
Biasanya output dari tindakan validasi hanya perlu dimasukkan ke grup output validasi, dan tidak ditambahkan ke input tindakan lain, cara ini bisa mengalahkan keparahan paralelisme. Namun, perlu diketahui bahwa Bazel saat ini memiliki pemeriksaan khusus untuk menegakkan ini. Oleh karena itu, Anda harus menguji bahwa output tindakan validasi tidak ditambahkan ke input tindakan apa pun dalam tes untuk aturan Starlark. Contoh:
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
def _validation_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
target = analysistest.target_under_test(env)
validation_outputs = target.output_groups._validation.to_list()
for action in actions:
for validation_output in validation_outputs:
if validation_output in action.inputs.to_list():
analysistest.fail(env,
"%s is a validation action output, but is an input to action %s" % (
validation_output, action))
return analysistest.end(env)
validation_outputs_test = analysistest.make(_validation_outputs_test_impl)
Tanda Tindakan Validasi
Menjalankan tindakan validasi dikontrol oleh command line --run_validations
, yang secara default disetel ke benar (true).
Fitur yang tidak digunakan lagi
Output yang tidak digunakan lagi
Ada dua cara yang tidak digunakan lagi untuk menggunakan output yang telah dideklarasikan sebelumnya:
Parameter
outputs
darirule
menentukan pemetaan antara nama atribut {i>output<i} dan {i>template<i} string untuk menghasilkan label output yang telah dideklarasikan sebelumnya. Lebih suka menggunakan {i>output<i} yang tidak dideklarasikan dan secara eksplisit menambahkan output keDefaultInfo.files
. Gunakan label sebagai input untuk aturan yang menggunakan output, bukan di label output.Untuk aturan yang dapat dijalankan,
ctx.outputs.executable
merujuk ke {i>output<i} yang dapat dieksekusi yang telah dideklarasikan dengan nama yang sama dengan target aturan. Pilih mendeklarasikan output secara eksplisit, misalnya denganctx.actions.declare_file(ctx.label.name)
, dan pastikan bahwa perintah yang menghasilkan {i>executable<i} menetapkan izin akses untuk memungkinkan eksekusi. Eksplisit teruskan output yang dapat dieksekusi ke parameterexecutable
dariDefaultInfo
.
Fitur Runfiles yang perlu dihindari
ctx.runfiles
dan runfiles
memiliki seperangkat fitur yang kompleks, banyak di antaranya yang disimpan karena alasan lama.
Rekomendasi berikut membantu mengurangi kerumitan:
Hindari penggunaan mode
collect_data
dancollect_default
ctx.runfiles
. Mode ini secara implisit mengumpulkan {i>runfile<i} di tepi dependensi hardcode tertentu dengan cara yang membingungkan. Sebagai gantinya, tambahkan file menggunakan parameterfiles
atautransitive_files
darictx.runfiles
, atau dengan menggabungkan runfile dari dependensi denganrunfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
.Hindari penggunaan
data_runfiles
dandefault_runfiles
dari KonstruktorDefaultInfo
. Sebagai gantinya, tentukanDefaultInfo(runfiles = ...)
. Perbedaan antara "default" dan "data" {i>runfile<i} dipertahankan untuk alasan lama. Misalnya, beberapa aturan menempatkan output defaultnya didata_runfiles
, tetapi bukandefault_runfiles
. Daripada menggunakandata_runfiles
, aturan harus baik menyertakan output default dan juga menggabungkannyadefault_runfiles
dari atribut yang menyediakan runfile (sering kalidata
).Saat mengambil
runfiles
dariDefaultInfo
(umumnya hanya untuk penggabungan {i>runfiles<i} di antara aturan saat ini dan dependensinya), gunakanDefaultInfo.default_runfiles
, bukanDefaultInfo.data_runfiles
.
Bermigrasi dari penyedia lama
Sebelumnya, penyedia Bazel adalah kolom sederhana pada objek Target
. Mereka
diakses menggunakan operator titik, dan mereka dibuat dengan meletakkan kolom
dalam struct yang ditampilkan oleh fungsi implementasi aturan.
Gaya ini tidak digunakan lagi dan tidak boleh digunakan dalam kode baru; lihat di bawah untuk informasi yang dapat membantu Anda bermigrasi. Mekanisme penyedia baru menghindari penggunaan nama bentrokan besar. {i>Firewall<i} ini juga mendukung penyembunyian data, dengan mengharuskan kode untuk mengakses penyedia untuk mengambilnya menggunakan simbol penyedia.
Untuk saat ini, penyedia lama masih didukung. Aturan dapat menampilkan keduanya penyedia lama dan modern sebagai berikut:
def _old_rule_impl(ctx):
...
legacy_data = struct(x="foo", ...)
modern_data = MyInfo(y="bar", ...)
# When any legacy providers are returned, the top-level returned value is a
# struct.
return struct(
# One key = value entry for each legacy provider.
legacy_info = legacy_data,
...
# Additional modern providers:
providers = [modern_data, ...])
Jika dep
adalah objek Target
yang dihasilkan untuk instance aturan ini,
penyedia dan kontennya dapat diambil sebagai dep.legacy_info.x
,
dep[MyInfo].y
.
Selain providers
, struct yang ditampilkan juga dapat mengambil beberapa
kolom yang memiliki arti khusus (sehingga tidak membuat turunan yang sesuai
):
Kolom
files
,runfiles
,data_runfiles
,default_runfiles
, danexecutable
sesuai dengan kolom nama yang sama dariDefaultInfo
Anda tidak diizinkan untuk menentukan kolom ini sekaligus menampilkan penyediaDefaultInfo
.Kolom
output_groups
mengambil nilai struct dan sesuai denganOutputGroupInfo
.
Di provides
deklarasi aturan, dan di
providers
deklarasi dependensi
penyedia lama diteruskan sebagai string dan penyedia modern
yang diteruskan oleh simbol *Info
-nya. Pastikan untuk mengubah dari string menjadi simbol
saat bermigrasi. Untuk kumpulan aturan yang kompleks atau besar yang sulit diperbarui
semua aturan secara menyeluruh, Anda mungkin lebih
mudah jika mengikuti urutan
langkah:
Ubah aturan yang menghasilkan penyedia lama untuk menghasilkan penyedia versi lama dan penyedia modern, dengan menggunakan sintaksis di atas. Untuk aturan yang mendeklarasikan mengembalikan penyedia lama, perbarui deklarasi tersebut untuk menyertakan penyedia lama dan modern.
Ubah aturan yang menggunakan penyedia lama untuk menggunakan penyedia modern. Jika ada deklarasi atribut yang memerlukan penyedia lama, perbarui juga agar memerlukan penyedia modern. Secara opsional, Anda dapat menyisipkan pekerjaan ini dengan langkah 1, dengan meminta konsumen menyetujui/mewajibkan provider: Menguji keberadaan penyedia lama menggunakan
hasattr(target, 'foo')
, atau penyedia baru yang menggunakanFooInfo in target
.Hapus penyedia lama sepenuhnya dari semua aturan.