Aturan menentukan serangkaian tindakan yang dilakukan Bazel pada input untuk menghasilkan serangkaian output, yang dirujuk dalam penyedia yang ditampilkan oleh fungsi penerapan aturan. Misalnya, aturan biner C++ dapat:
- Ambil sekumpulan file sumber
.cpp
(input). - Jalankan
g++
pada file sumber (tindakan). - Menampilkan penyedia
DefaultInfo
dengan output yang dapat dieksekusi dan file lain agar tersedia saat runtime. - Menampilkan penyedia
CcInfo
dengan informasi khusus C++ yang dikumpulkan dari target dan dependensinya.
Dari perspektif Bazel, g++
dan library C++ standar juga merupakan input
untuk aturan ini. Sebagai penulis aturan, Anda tidak hanya harus mempertimbangkan input yang diberikan pengguna ke suatu aturan, tetapi juga semua alat dan library yang diperlukan untuk menjalankan tindakan.
Sebelum membuat atau mengubah aturan apa pun, pastikan Anda memahami fase build Bazel. Penting untuk memahami tiga fase build (pemuatan, analisis, dan eksekusi). Anda juga dapat mempelajari makro untuk memahami perbedaan antara aturan dan makro. Untuk memulai, tinjau terlebih dahulu Tutorial Aturan. Kemudian, gunakan halaman ini sebagai referensi.
Beberapa aturan sudah ada di Bazel. Aturan native ini, seperti
genrule
dan filegroup
, memberikan beberapa dukungan inti.
Dengan menentukan aturan Anda sendiri, Anda dapat menambahkan dukungan untuk bahasa dan alat yang tidak didukung secara native oleh Bazel.
Bazel menyediakan model ekstensibilitas untuk menulis aturan menggunakan bahasa
Starlark. Aturan ini ditulis dalam file .bzl
, yang dapat dimuat langsung dari file BUILD
.
Saat menentukan aturan Anda sendiri, Anda dapat memutuskan atribut yang didukung dan cara aturan tersebut menghasilkan outputnya.
Fungsi implementation
aturan menentukan perilaku persisnya selama
fase analisis. Fungsi ini tidak menjalankan perintah eksternal apa pun. Sebaliknya, fungsi ini mendaftarkan tindakan yang akan digunakan
nanti selama fase eksekusi untuk membuat output aturan, jika diperlukan.
Pembuatan aturan
Dalam file .bzl
, gunakan fungsi rule untuk menentukan aturan
baru, dan simpan hasilnya dalam variabel global. Panggilan ke rule
menentukan
atribut dan
fungsi penerapan:
example_library = rule(
implementation = _example_library_impl,
attrs = {
"deps": attr.label_list(),
...
},
)
Tindakan 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 khusus
dapat dieksekusi untuk pengujian (dengan test = True
). Jika yang terakhir, aturan adalah aturan pengujian,
dan nama aturan harus diakhiri dengan _test
.
Instansiasi 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 berupa penentuan target. Tindakan ini disebut membuat instance aturan. Tindakan ini menentukan 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 file BUILD
dievaluasi untuk membuat instance target.
Atribut
Atribut adalah argumen aturan. Atribut dapat memberikan nilai tertentu ke penerapan target, atau dapat merujuk ke target lain, sehingga membuat grafik dependensi.
Atribut khusus aturan, seperti srcs
atau deps
, ditentukan dengan meneruskan peta
dari nama atribut ke skema (dibuat menggunakan modul attr
)
ke parameter attrs
dari rule
.
Atribut umum, seperti
name
dan visibility
, secara implisit ditambahkan ke semua aturan. Atribut tambahan ditambahkan secara implisit ke
aturan yang dapat dieksekusi dan pengujian secara khusus. Atribut yang ditambahkan secara implisit ke aturan tidak dapat disertakan dalam kamus yang diteruskan ke attrs
.
Atribut dependensi
Aturan yang memproses kode sumber biasanya menentukan atribut berikut untuk menangani berbagai jenis dependensi:
srcs
menentukan file sumber yang diproses oleh tindakan target. Biasanya, skema atribut menentukan ekstensi file yang diharapkan untuk jenis file sumber yang diproses oleh aturan. Aturan untuk bahasa dengan file header biasanya 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 menyediakan dependensi tersebut. (Misalnya,cc_library
menyediakanCcInfo
.)data
menentukan file yang akan tersedia saat runtime untuk semua file yang dapat dieksekusi yang bergantung pada target. Hal ini akan memungkinkan file arbitrer 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),
...
},
)
Berikut adalah contoh atribut dependensi. Atribut apa pun yang menentukan
label input (yang ditentukan dengan
attr.label_list
,
attr.label
, atau
attr.label_keyed_string_dict
)
menentukan dependensi jenis tertentu
antara target dan target yang labelnya (atau objek
Label
yang sesuai) tercantum dalam atribut tersebut saat target
ditentukan. Repositori, dan mungkin jalur, untuk label ini 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 my_target
, sehingga
other_target
dianalisis terlebih dahulu. Terjadi error jika ada siklus dalam
grafik dependensi target.
Atribut pribadi dan dependensi implisit
Atribut dependensi dengan nilai default membuat dependensi implisit. Hal ini
implisit karena merupakan bagian dari grafik target yang tidak
ditentukan pengguna dalam file BUILD
. Dependensi implisit berguna untuk mengodekan secara permanen
hubungan antara aturan dan alat (dependensi waktu build, seperti
kompiler), karena sebagian besar waktu pengguna tidak tertarik untuk menentukan
alat yang digunakan aturan. Di dalam fungsi penerapan aturan, hal ini diperlakukan
sama seperti dependensi lainnya.
Jika ingin memberikan dependensi implisit tanpa mengizinkan pengguna mengganti nilai tersebut, Anda dapat membuat atribut pribadi dengan memberikan nama yang diawali dengan garis bawah (_
). Atribut pribadi harus memiliki nilai default. Umumnya, atribut pribadi hanya masuk akal untuk digunakan pada dependensi implisit.
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
implisit pada compiler //tools:example_compiler
. Hal ini memungkinkan fungsi penerapan
example_library
untuk menghasilkan tindakan yang memanggil
kompiler, meskipun pengguna tidak meneruskan labelnya sebagai input. Karena
_compiler
adalah atribut pribadi, maka ctx.attr._compiler
akan selalu mengarah ke //tools:example_compiler
di semua target jenis aturan ini. Atau, Anda dapat memberi nama atribut compiler
tanpa
garis bawah dan mempertahankan nilai default. Hal ini memungkinkan pengguna mengganti
kompiler lain jika perlu, tetapi tidak memerlukan pengetahuan tentang label
kompiler.
Dependensi implisit umumnya digunakan untuk alat yang berada di repositori yang sama dengan implementasi aturan. Jika alat berasal dari platform eksekusi atau repositori lain, aturan harus mendapatkan alat tersebut dari toolchain.
Atribut output
Atribut output, seperti attr.output
dan
attr.output_list
, mendeklarasikan file output yang dihasilkan
target. Atribut ini berbeda dari atribut dependensi dalam dua hal:
- Tindakan ini menentukan target file output, bukan merujuk ke target yang ditentukan di tempat lain.
- Target file output bergantung pada target aturan yang di-instantiate, bukan sebaliknya.
Biasanya, atribut output hanya digunakan saat aturan perlu membuat output
dengan nama yang ditentukan pengguna yang tidak dapat didasarkan pada nama target. Jika aturan memiliki
satu atribut output, biasanya diberi nama out
atau outs
.
Atribut output adalah cara yang lebih disukai untuk membuat output yang telah dideklarasikan sebelumnya, yang dapat secara khusus diandalkan atau diminta di command line.
Fungsi penerapan
Setiap aturan memerlukan fungsi implementation
. Fungsi ini dieksekusi
secara ketat dalam fase analisis dan mengubah
grafik target yang dihasilkan dalam fase pemuatan menjadi grafik
tindakan yang akan dilakukan selama fase eksekusi. Oleh karena itu,
fungsi penerapan tidak dapat membaca atau menulis file.
Fungsi penerapan aturan biasanya bersifat pribadi (diberi nama dengan garis bawah di depannya). Secara konvensional, namanya sama dengan aturan, tetapi dengan akhiran
_impl
.
Fungsi penerapan hanya menggunakan satu parameter: konteks aturan, yang secara konvensional diberi nama ctx
. Fungsi ini menampilkan daftar
penyedia.
Target
Dependensi direpresentasikan pada waktu analisis sebagai objek Target
. Objek ini berisi penyedia yang dibuat saat
fungsi penerapan target dijalankan.
ctx.attr
memiliki kolom yang sesuai dengan nama setiap
atribut dependensi, yang berisi objek Target
yang merepresentasikan setiap
dependensi langsung menggunakan atribut tersebut. Untuk atribut label_list
, ini adalah daftar
Targets
. Untuk atribut label
, nilainya adalah Target
atau None
tunggal.
Daftar objek penyedia ditampilkan oleh fungsi penerapan target:
return [ExampleInfo(headers = depset(...))]
Provider tersebut dapat diakses menggunakan notasi indeks ([]
), dengan jenis penyedia sebagai
kunci. Ini dapat berupa penyedia kustom yang ditentukan di Starlark atau
penyedia untuk aturan native yang tersedia sebagai variabel global Starlark.
Misalnya, jika aturan mengambil file header menggunakan atribut hdrs
dan menyediakannya ke tindakan kompilasi target dan konsumennya, aturan tersebut dapat mengumpulkannya seperti ini:
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
Ada gaya struct lama, yang sangat tidak disarankan dan aturan harus dimigrasikan dari gaya tersebut.
File
File direpresentasikan oleh objek File
. Karena Bazel tidak
melakukan I/O file selama fase analisis, objek ini tidak dapat digunakan untuk
membaca atau menulis konten file secara langsung. Sebaliknya, nilai tersebut diteruskan ke fungsi
pemancaran tindakan (lihat ctx.actions
) untuk membuat bagian
grafik tindakan.
File
dapat berupa file sumber atau file yang dihasilkan. Setiap file yang dihasilkan
harus berupa output dari tepat satu tindakan. File sumber tidak dapat berupa output dari
tindakan apa pun.
Untuk setiap atribut dependensi, kolom ctx.files
yang sesuai berisi daftar output default semua dependensi yang menggunakan 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 menetapkan allow_single_file = True
.
ctx.executable
berperilaku sama seperti ctx.file
, tetapi hanya
berisi kolom untuk atribut dependensi yang spesifikasinya menetapkan executable = True
.
Mendeklarasikan output
Selama fase analisis, fungsi penerapan aturan dapat membuat output.
Karena semua label harus diketahui selama fase pemuatan, output tambahan ini tidak memiliki label. Objek File
untuk output dapat dibuat menggunakan
ctx.actions.declare_file
dan
ctx.actions.declare_directory
.
Sering kali, nama output 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 dideklarasikan sebelumnya, seperti yang dibuat untuk
atribut output, objek File
dapat diambil
dari kolom ctx.outputs
yang sesuai.
Tindakan
Tindakan menjelaskan cara membuat serangkaian output dari serangkaian input, misalnya "jalankan gcc di hello.c dan dapatkan hello.o". Saat tindakan dibuat, Bazel tidak langsung menjalankan perintah. Tindakan ini mendaftarkannya dalam grafik dependensi, karena suatu tindakan dapat bergantung pada output tindakan lain. Misalnya, di C, linker harus dipanggil setelah compiler.
Fungsi serbaguna yang membuat tindakan ditentukan di
ctx.actions
:
ctx.actions.run
, untuk menjalankan file yang dapat dieksekusi.ctx.actions.run_shell
, untuk menjalankan perintah shell.ctx.actions.write
, untuk menulis string ke file.ctx.actions.expand_template
, untuk membuat file dari template.
ctx.actions.args
dapat digunakan untuk mengakumulasi argumen tindakan secara efisien. Menghindari perataan depsets 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 menghasilkan daftar file output (yang tidak kosong). Kumpulan file input dan output harus diketahui selama fase analisis. Hal ini mungkin bergantung pada nilai atribut, termasuk penyedia dari dependensi, tetapi tidak dapat bergantung pada hasil eksekusi. Misalnya, jika tindakan Anda menjalankan perintah unzip, Anda harus menentukan file mana yang akan diekstrak (sebelum menjalankan unzip). Tindakan yang membuat sejumlah file secara internal dapat membungkusnya dalam satu file (seperti zip, tar, atau format arsip lainnya).
Tindakan harus mencantumkan semua inputnya. Mencantumkan input yang tidak digunakan diizinkan, tetapi tidak efisien.
Tindakan harus membuat semua outputnya. Mereka dapat menulis file lain, tetapi apa pun yang tidak ada di output tidak akan tersedia bagi konsumen. Semua output yang dideklarasikan harus ditulis oleh beberapa tindakan.
Tindakan dapat dibandingkan dengan fungsi murni: Tindakan hanya boleh bergantung pada input yang diberikan, dan menghindari akses ke informasi komputer, nama pengguna, jam, jaringan, atau perangkat I/O (kecuali untuk membaca input dan menulis output). Hal ini penting karena output akan di-cache dan digunakan kembali.
Dependensi diselesaikan oleh Bazel, yang memutuskan tindakan mana yang akan dieksekusi. Error akan terjadi jika ada siklus dalam grafik dependensi. Membuat tindakan tidak menjamin bahwa tindakan tersebut akan dieksekusi, hal ini bergantung pada apakah outputnya diperlukan untuk build.
Penyedia
Penyedia adalah informasi yang diekspos oleh suatu aturan ke aturan lain yang bergantung padanya. Data ini dapat mencakup file output, library, parameter yang akan diteruskan di command line alat, atau hal lain yang harus diketahui konsumen target.
Karena fungsi penerapan aturan hanya dapat membaca penyedia dari
dependensi langsung target yang di-instance, aturan perlu meneruskan informasi
apa pun dari dependensi target yang perlu diketahui oleh konsumen target, umumnya
dengan mengakumulasikannya ke dalam depset
.
Penyedia target ditentukan oleh daftar objek penyedia yang ditampilkan oleh fungsi penerapan.
Fungsi penerapan lama juga dapat ditulis dalam gaya lama di mana
fungsi penerapan menampilkan struct
, bukan daftar
objek penyedia. Gaya ini sangat tidak disarankan dan aturan harus
dimigrasikan dari gaya ini.
Output default
Output default target adalah output yang diminta secara default saat
target diminta untuk build di command line. Misalnya, target java_library
//pkg:foo
memiliki foo.jar
sebagai output default, sehingga akan dibangun oleh perintah bazel build //pkg:foo
.
Output default ditentukan oleh parameter files
dari
DefaultInfo
:
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
Jika DefaultInfo
tidak ditampilkan oleh penerapan aturan atau parameter files
tidak ditentukan, DefaultInfo.files
secara default akan menampilkan semua output yang telah dideklarasikan sebelumnya (umumnya, yang dibuat oleh atribut output).
Aturan yang melakukan tindakan harus memberikan output default, meskipun output tersebut tidak diharapkan untuk digunakan secara langsung. Tindakan yang tidak ada dalam grafik output yang diminta akan dihapus. Jika output hanya digunakan oleh konsumen target, tindakan tersebut tidak akan dilakukan saat target dibangun secara terpisah. Hal ini membuat proses pen-debug-an menjadi lebih sulit karena membangun ulang target yang gagal saja tidak akan mereproduksi kegagalan.
Runfiles
Runfile adalah sekumpulan file yang digunakan oleh target saat runtime (bukan saat build). Selama fase eksekusi, Bazel membuat pohon direktori yang berisi symlink yang mengarah ke runfile. Langkah ini menyiapkan lingkungan untuk biner sehingga dapat mengakses file run selama runtime.
File yang dapat dijalankan dapat ditambahkan secara manual selama pembuatan aturan.
Objek runfiles
dapat dibuat oleh metode runfiles
pada konteks aturan, ctx.runfiles
dan diteruskan ke
parameter runfiles
pada DefaultInfo
. Output yang dapat dieksekusi dari
aturan yang dapat dieksekusi ditambahkan secara implisit ke file yang dapat dijalankan.
Beberapa aturan menentukan atribut, yang umumnya bernama
data
, yang outputnya ditambahkan ke
file run target. Runfile juga harus digabungkan dari data
, serta
dari atribut apa pun yang dapat menyediakan kode untuk eksekusi akhir, umumnya
srcs
(yang mungkin berisi target filegroup
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 fungsi 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 penerapan 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 melindungi instansiasi penyedia dengan logika pra-pemrosesan dan validasi kustom. Hal ini dapat digunakan untuk memastikan bahwa semua instance penyedia memenuhi invarian tertentu, atau untuk memberi pengguna API yang lebih bersih untuk mendapatkan instance.
Hal ini dilakukan dengan meneruskan callback init
ke
fungsi provider
. Jika callback ini diberikan, jenis
nilai yang ditampilkan provider()
akan berubah menjadi tuple dua nilai: simbol
penyedia yang merupakan nilai yang ditampilkan biasa saat init
tidak digunakan, dan "konstruktor
mentah".
Dalam hal ini, saat simbol penyedia dipanggil, bukan langsung menampilkan instance baru, simbol tersebut akan meneruskan argumen ke callback init
. Nilai yang ditampilkan callback harus berupa dict yang memetakan nama kolom (string) ke nilai;
nilai ini digunakan untuk melakukan inisialisasi kolom instance baru. Perhatikan bahwa
callback dapat 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 melewati callback init
.
Contoh berikut menggunakan init
untuk memproses dan memvalidasi argumennya:
# //pkg:exampleinfo.bzl
_core_headers = [...] # private constant representing standard library files
# 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(
fields = ["files_to_link", "headers"],
init = _exampleinfo_init,
)
Kemudian, penerapan 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, exampleinfo.bzl
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 ke variabel yang namanya diawali dengan garis bawah (_new_exampleinfo
di atas), sehingga kode pengguna tidak dapat memuatnya dan membuat instance penyedia arbitrer.
Penggunaan lain untuk init
adalah untuk mencegah pengguna memanggil simbol penyedia sama sekali, dan memaksa mereka menggunakan fungsi pabrik 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 dieksekusi dan aturan pengujian
Aturan yang dapat dieksekusi menentukan target yang dapat dipanggil oleh perintah bazel run
.
Aturan pengujian adalah jenis aturan yang dapat dieksekusi khusus yang targetnya juga dapat
dipanggil oleh perintah bazel test
. Aturan yang dapat dieksekusi dan pengujian dibuat dengan
menetapkan argumen executable
atau
test
masing-masing 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
. (Nama target pengujian juga sering
diakhiri dengan _test
berdasarkan konvensi, tetapi hal ini tidak wajib.) Aturan non-pengujian tidak boleh
memiliki akhiran ini.
Kedua jenis aturan harus menghasilkan file output yang dapat dieksekusi (yang mungkin atau mungkin tidak
dideklarasikan sebelumnya) yang akan dipanggil oleh perintah run
atau test
. Untuk memberi tahu Bazel output aturan mana yang akan digunakan sebagai file yang dapat dieksekusi ini, teruskan sebagai argumen
executable
dari penyedia DefaultInfo
yang ditampilkan. executable
tersebut ditambahkan ke output default aturan (sehingga Anda tidak perlu meneruskannya ke executable
dan files
). executable
juga ditambahkan secara implisit 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 menyetel bit yang dapat dieksekusi pada file. Untuk
tindakan ctx.actions.run
atau
ctx.actions.run_shell
, hal ini harus dilakukan
oleh alat pokok yang dipanggil oleh tindakan tersebut. 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
dapat dieksekusi default jika Anda tidak menentukannya menggunakan DefaultInfo
; file ini tidak boleh
digunakan jika tidak. Mekanisme output ini tidak digunakan lagi karena tidak mendukung
penyesuaian nama file yang dapat dieksekusi pada waktu analisis.
Lihat contoh aturan yang dapat dieksekusi dan aturan pengujian.
Aturan yang dapat dieksekusi dan aturan pengujian memiliki atribut tambahan yang ditentukan secara implisit, selain atribut yang ditambahkan untuk semua aturan. Default atribut yang ditambahkan secara implisit tidak dapat diubah, meskipun hal ini dapat diatasi dengan membungkus aturan pribadi dalam makro Starlark yang mengubah default:
def example_test(size = "small", **kwargs):
_example_test(size = size, **kwargs)
_example_test = rule(
...
)
Lokasi file yang dapat dijalankan
Saat target yang dapat dieksekusi dijalankan dengan bazel run
(atau test
), root direktori file yang dapat dijalankan berdekatan dengan file yang dapat dieksekusi. Jalur tersebut terkait 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 direktori runfiles sesuai dengan
File.short_path
.
Biner yang dieksekusi langsung oleh bazel
berdekatan dengan root direktori runfiles
. Namun, biner yang disebut from runfile tidak dapat membuat asumsi yang sama. Untuk memitigasi hal ini, setiap biner harus menyediakan cara untuk
menerima root runfile-nya sebagai parameter menggunakan lingkungan, atau argumen
atau tanda command line. Hal ini memungkinkan biner meneruskan root file yang benar dan kanonis
ke biner yang dipanggilnya. Jika tidak disetel, biner dapat menebak bahwa biner tersebut adalah
biner pertama yang dipanggil dan mencari direktori runfiles yang berdekatan.
Topik lanjutan
Meminta file output
Satu target dapat memiliki beberapa file output. Saat perintah bazel build
dijalankan, beberapa output target yang diberikan ke perintah dianggap diminta. Bazel hanya membuat file yang diminta ini dan file yang bergantung
langsung atau tidak langsung padanya. (Dalam hal grafik tindakan, Bazel hanya
mengeksekusi tindakan yang dapat dijangkau sebagai dependensi transitif dari
file yang diminta.)
Selain output default, output yang telah dideklarasikan sebelumnya dapat
diminta secara eksplisit di command line. Aturan dapat menentukan output
yang telah dideklarasikan sebelumnya menggunakan atribut output. Dalam hal ini, pengguna secara eksplisit memilih label untuk output saat membuat instance aturan. Untuk mendapatkan objek
File
untuk atribut output, gunakan atribut
ctx.outputs
yang sesuai. Aturan dapat
secara implisit menentukan output yang telah dideklarasikan sebelumnya berdasarkan
nama target juga, tetapi fitur ini tidak digunakan lagi.
Selain output default, ada grup output, yaitu kumpulan
file output yang dapat diminta bersama. Hal ini dapat diminta dengan
--output_groups
. Misalnya, jika target //pkg:mytarget
adalah jenis aturan yang memiliki grup output debug_files
, file ini dapat dibuat dengan menjalankan bazel build //pkg:mytarget
--output_groups=debug_files
. Karena output yang tidak dideklarasikan sebelumnya tidak memiliki label, output tersebut hanya dapat diminta dengan muncul di output default atau grup output.
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]),
),
...
]
Selain itu, tidak seperti kebanyakan penyedia, OutputGroupInfo
dapat ditampilkan oleh aspek dan target aturan yang menerapkan aspek tersebut, selama keduanya tidak menentukan grup output yang sama. Dalam hal ini, penyedia
yang dihasilkan akan digabungkan.
Perhatikan bahwa OutputGroupInfo
umumnya tidak boleh digunakan untuk menyampaikan jenis file tertentu dari target ke tindakan konsumennya. Tentukan
penyedia khusus aturan untuk itu.
Konfigurasi
Bayangkan Anda ingin membuat biner C++ untuk arsitektur yang berbeda. Build dapat menjadi rumit dan melibatkan beberapa langkah. Beberapa biner perantara, seperti compiler dan generator kode, harus berjalan di platform eksekusi (yang bisa berupa host Anda, atau executor jarak jauh). Beberapa biner seperti output akhir harus dibangun untuk arsitektur target.
Oleh karena itu, Bazel memiliki konsep "konfigurasi" dan transisi. Target paling atas (yang diminta di command line) dibuat dalam konfigurasi "target", sementara alat yang harus dijalankan di platform eksekusi dibuat dalam konfigurasi "exec". Aturan dapat menghasilkan tindakan yang berbeda berdasarkan konfigurasi, misalnya untuk mengubah arsitektur CPU yang diteruskan ke compiler. Dalam beberapa kasus, library yang sama mungkin diperlukan untuk konfigurasi yang berbeda. Jika hal ini terjadi, model akan dianalisis dan berpotensi dibangun beberapa kali.
Secara default, Bazel membangun dependensi target dalam konfigurasi yang sama dengan target itu sendiri, dengan kata lain tanpa transisi. Jika dependensi adalah alat yang diperlukan untuk membantu membangun target, atribut yang sesuai harus menentukan transisi ke konfigurasi exec. Hal ini menyebabkan alat dan semua dependensinya dibangun untuk platform eksekusi.
Untuk setiap atribut dependensi, Anda dapat menggunakan cfg
untuk memutuskan apakah dependensi harus dibangun dalam konfigurasi yang sama atau bertransisi ke konfigurasi exec.
Jika atribut dependensi memiliki tanda executable = True
, cfg
harus ditetapkan
secara eksplisit. Tindakan ini dilakukan untuk mencegah pembuatan alat secara tidak sengaja untuk konfigurasi yang salah.
Lihat contoh
Secara umum, sumber, library dependen, dan file yang dapat dieksekusi yang akan diperlukan saat runtime dapat menggunakan konfigurasi yang sama.
Alat yang dieksekusi sebagai bagian dari build (seperti compiler atau generator kode)
harus dibuat untuk konfigurasi exec. Dalam hal ini, tentukan cfg = "exec"
di
atribut.
Jika tidak, file yang dapat dieksekusi yang digunakan saat runtime (seperti sebagai bagian dari pengujian) harus dibangun untuk konfigurasi target. Dalam hal ini, tentukan cfg = "target"
di
atribut.
cfg = "target"
sebenarnya tidak melakukan apa pun: ini murni nilai praktis untuk
membantu desainer aturan menyatakan maksud mereka secara eksplisit. Jika executable = False
,
yang berarti cfg
bersifat opsional, tetapkan hanya jika benar-benar membantu keterbacaan.
Anda juga dapat menggunakan cfg = my_transition
untuk menggunakan
transisi yang ditentukan pengguna, yang memungkinkan
penulis aturan memiliki fleksibilitas yang besar dalam mengubah konfigurasi, dengan
kelemahan
membuat grafik build lebih besar dan kurang mudah dipahami.
Catatan: Secara historis, Bazel tidak memiliki konsep platform eksekusi, dan semua tindakan build dianggap berjalan di mesin host. Versi Bazel sebelum 6.0 membuat konfigurasi "host" yang berbeda untuk merepresentasikan hal ini. Jika Anda melihat referensi ke "host" dalam kode atau dokumentasi lama, itulah yang dimaksud. Sebaiknya gunakan Bazel 6.0 atau yang lebih baru untuk menghindari overhead konseptual tambahan ini.
Fragmen konfigurasi
Aturan dapat mengakses
fragmen konfigurasi seperti
cpp
dan java
. Namun, semua fragmen yang diperlukan harus dideklarasikan untuk menghindari error 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
...
)
Symlink file yang dapat dijalankan
Biasanya, jalur relatif file di hierarki runfiles sama dengan jalur relatif file tersebut di hierarki sumber atau hierarki output yang dihasilkan. Jika perlu berbeda karena alasan tertentu, Anda dapat menentukan argumen root_symlinks
atau
symlinks
. root_symlinks
adalah dictionary yang memetakan jalur ke file, dengan jalur yang bersifat relatif terhadap root direktori runfiles. Kamus
symlinks
sama, tetapi jalur secara implisit diberi awalan dengan
nama ruang kerja utama (bukan nama repositori yang berisi
target saat ini).
...
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 yang berbeda ke jalur yang sama di pohon runfile. Tindakan ini akan menyebabkan build gagal
dengan error yang menjelaskan konflik. Untuk memperbaikinya, Anda harus mengubah argumen
ctx.runfiles
untuk menghapus tabrakan. Pemeriksaan ini akan dilakukan untuk
semua target yang menggunakan aturan Anda, serta target apa pun yang bergantung pada
target tersebut. Hal ini sangat berisiko jika alat Anda kemungkinan akan digunakan secara transitif oleh alat lain; nama symlink harus unik di seluruh runfile alat dan semua dependensinya.
Cakupan kode
Saat perintah coverage
dijalankan,
build mungkin perlu menambahkan instrumentasi cakupan untuk target tertentu. Build juga mengumpulkan daftar file sumber yang diinstrumentasi. Subset target yang dipertimbangkan dikontrol oleh tanda --instrumentation_filter
.
Target pengujian dikecualikan, kecuali jika
--instrument_test_targets
ditentukan.
Jika penerapan aturan menambahkan instrumentasi cakupan pada waktu build, penerapan tersebut harus memperhitungkannya dalam fungsi penerapannya. ctx.coverage_instrumented menampilkan True
dalam mode cakupan jika sumber target harus diinstrumentasi:
# 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 (terlepas dari apakah sumber target diinstrumentasikan secara khusus atau tidak) dapat dikondisikan pada ctx.configuration.coverage_enabled.
Jika aturan secara langsung menyertakan sumber dari dependensinya sebelum kompilasi (seperti file header), aturan tersebut mungkin juga perlu mengaktifkan instrumentasi waktu kompilasi jika sumber dependensi harus diinstrumentasi:
# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if 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 memberikan informasi tentang atribut mana yang relevan untuk
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, InstrumentedFilesInfo
default akan dibuat dengan setiap
atribut dependensi non-alat yang tidak menetapkan
cfg
ke "exec"
dalam skema atribut. di
dependency_attributes
. (Ini bukan perilaku yang ideal, karena menempatkan atribut
seperti srcs
di dependency_attributes
, bukan source_attributes
, tetapi
menghindari kebutuhan akan konfigurasi cakupan eksplisit untuk semua aturan dalam
rantai dependensi.)
Menguji aturan
Aturan pengujian memerlukan penyiapan tambahan untuk membuat laporan cakupan. Aturan itu sendiri harus menambahkan atribut implisit berikut:
my_test = rule(
...,
attrs = {
...,
# Implicit dependencies used by Bazel to generate coverage reports.
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
executable = True,
cfg = config.exec(exec_group = "test"),
),
"_collect_cc_coverage": attr.label(
default = "@bazel_tools//tools/test:collect_cc_coverage",
executable = True,
cfg = config.exec(exec_group = "test"),
)
},
test = True,
)
Dengan menggunakan configuration_field
, dependensi pada alat penggabungan LCOV Java dapat dihindari selama cakupan tidak diminta.
Saat dijalankan, pengujian akan memancarkan informasi cakupan dalam bentuk satu atau
beberapa file LCOV
dengan nama unik ke direktori yang ditentukan oleh variabel
lingkungan COVERAGE_DIR
. Kemudian, Bazel akan menggabungkan file ini menjadi satu file LCOV menggunakan alat _lcov_merger
. Jika ada, alat ini juga akan mengumpulkan cakupan C/C++ menggunakan alat
_collect_cc_coverage
.
Cakupan dasar pengukuran
Karena cakupan hanya dikumpulkan untuk kode yang ada di pohon dependensi
pengujian, laporan cakupan dapat menyesatkan karena tidak selalu mencakup semua
kode yang cocok dengan tanda --instrumentation_filter
.
Oleh karena itu, Bazel memungkinkan aturan untuk menentukan file cakupan dasar menggunakan atribut baseline_coverage_files
dari ctx.instrumented_files_info
). File ini harus dibuat dalam format LCOV oleh tindakan yang ditentukan pengguna dan seharusnya mencantumkan semua baris, cabang, fungsi, dan/atau blok dalam file sumber target (sesuai dengan parameter sources_attributes
dan extensions
). Untuk
file sumber dalam target yang diinstrumentasi untuk cakupan, Bazel menggabungkan
cakupan dasar ke dalam laporan cakupan gabungan yang dihasilkan dengan
--combined_report
dan dengan demikian memastikan bahwa file yang tidak diuji tetap muncul sebagai
tidak tercakup.
Jika aturan tidak menyediakan file cakupan dasar, Bazel akan membuat informasi cakupan sintetis yang hanya menyebutkan jalur file sumber, tetapi tidak berisi informasi apa pun tentang isinya.
Tindakan Validasi
Terkadang Anda perlu memvalidasi sesuatu tentang build, dan informasi yang diperlukan untuk melakukan validasi tersebut hanya tersedia di artefak (file sumber atau file yang dihasilkan). Karena informasi ini ada dalam artefak, aturan tidak dapat melakukan validasi ini pada waktu analisis karena aturan tidak dapat membaca file. Sebagai gantinya, tindakan harus melakukan validasi ini pada waktu eksekusi. Jika validasi gagal, tindakan akan gagal, dan build juga akan gagal.
Contoh validasi yang mungkin dijalankan adalah analisis statis, linting, pemeriksaan dependensi dan konsistensi, serta pemeriksaan gaya.
Tindakan validasi juga dapat membantu meningkatkan performa build dengan memindahkan bagian tindakan yang tidak diperlukan untuk membangun artefak ke dalam tindakan terpisah. Misalnya, jika satu tindakan yang melakukan kompilasi dan linting dapat dipisahkan menjadi tindakan kompilasi dan tindakan linting, maka tindakan linting dapat dijalankan sebagai tindakan validasi dan dijalankan secara paralel dengan tindakan lain.
"Tindakan validasi" ini sering kali tidak menghasilkan apa pun yang digunakan di tempat lain dalam build, karena hanya perlu menegaskan hal-hal tentang inputnya. Namun, hal 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 file kosong, dan menambahkan output tersebut secara buatan ke input beberapa tindakan penting lainnya dalam build:
Hal ini berfungsi, karena Bazel akan selalu menjalankan tindakan validasi saat tindakan kompilasi dijalankan, tetapi hal ini memiliki kekurangan yang signifikan:
Tindakan validasi berada di jalur kritis build. Karena Bazel menganggap output kosong diperlukan untuk menjalankan tindakan kompilasi, Bazel akan menjalankan tindakan validasi terlebih dahulu, meskipun tindakan kompilasi akan mengabaikan input. Hal ini mengurangi paralelisme dan memperlambat build.
Jika tindakan lain dalam build mungkin berjalan, bukan tindakan kompilasi, maka output kosong dari tindakan validasi juga perlu ditambahkan ke tindakan tersebut (misalnya, output jar sumber
java_library
). Hal ini juga menjadi masalah jika tindakan baru yang mungkin berjalan, bukan tindakan kompilasi, ditambahkan nanti, dan output validasi kosong tidak sengaja dihilangkan.
Solusi untuk masalah ini adalah menggunakan Grup Output Validasi.
Grup Output Validasi
Grup Output Validasi adalah grup output yang dirancang untuk menyimpan output tindakan validasi yang tidak digunakan, sehingga tidak perlu ditambahkan secara buatan ke input tindakan lain.
Grup ini istimewa karena outputnya selalu diminta, terlepas dari
nilai tanda --output_groups
, dan terlepas dari cara target
bergantung (misalnya, di command line, sebagai dependensi, atau melalui
output implisit target). Perhatikan bahwa penyimpanan dalam cache dan inkrementalitas normal
tetap berlaku: jika input ke tindakan validasi tidak berubah dan
tindakan validasi sebelumnya berhasil, maka tindakan validasi tidak akan
dijalankan.
Penggunaan grup output ini tetap memerlukan tindakan validasi untuk menghasilkan beberapa file, bahkan file kosong. Hal ini mungkin memerlukan pembungkusan beberapa alat yang biasanya tidak membuat output sehingga file dibuat.
Tindakan validasi target tidak dijalankan dalam tiga kasus:
- Saat target bergantung pada alat
- Saat target bergantung sebagai dependensi implisit (misalnya, atribut yang dimulai dengan "_")
- Saat target dibuat dalam konfigurasi exec.
Diasumsikan bahwa target ini memiliki build dan pengujian terpisah yang akan mengungkap kegagalan validasi.
Menggunakan Grup Output Validasi
Grup Output Validasi diberi nama _validation
dan digunakan seperti grup output lainnya:
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 salah satu output implisit target bergantung secara langsung atau tidak langsung.
Biasanya, output tindakan validasi hanya boleh masuk ke grup output validasi, dan tidak boleh ditambahkan ke input tindakan lain, karena hal ini dapat mengurangi keuntungan paralelisme. Namun, perhatikan bahwa Bazel tidak memiliki pemeriksaan khusus untuk menerapkan hal ini. Oleh karena itu, Anda harus menguji bahwa output tindakan validasi tidak ditambahkan ke input tindakan apa pun dalam pengujian 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)
Bendera Tindakan Validasi
Menjalankan tindakan validasi dikontrol oleh tanda baris perintah --run_validations
, yang secara default bernilai benar (true).
Fitur yang tidak digunakan lagi
Output yang tidak digunakan lagi dan dideklarasikan sebelumnya
Ada dua cara yang tidak digunakan lagi untuk menggunakan output yang telah dideklarasikan sebelumnya:
Parameter
outputs
darirule
menentukan pemetaan antara nama atribut output dan template string untuk membuat label output yang telah dideklarasikan sebelumnya. Lebih baik menggunakan output yang tidak dideklarasikan sebelumnya dan menambahkan output secara eksplisit keDefaultInfo.files
. Gunakan label target aturan sebagai input untuk aturan yang menggunakan output, bukan label output yang telah dideklarasikan sebelumnya.Untuk aturan yang dapat dieksekusi,
ctx.outputs.executable
merujuk ke output yang dapat dieksekusi yang telah dideklarasikan sebelumnya dengan nama yang sama seperti target aturan. Sebaiknya deklarasikan output secara eksplisit, misalnya denganctx.actions.declare_file(ctx.label.name)
, dan pastikan perintah yang membuat file yang dapat dieksekusi menetapkan izinnya untuk mengizinkan eksekusi. Teruskan output yang dapat dieksekusi secara eksplisit ke parameterexecutable
dariDefaultInfo
.
Fitur runfile yang harus dihindari
Jenis ctx.runfiles
dan runfiles
memiliki serangkaian fitur yang kompleks, yang sebagian besar dipertahankan karena alasan lama.
Rekomendasi berikut membantu mengurangi kompleksitas:
Hindari penggunaan mode
collect_data
dancollect_default
darictx.runfiles
. Mode ini secara implisit mengumpulkan file yang dapat dieksekusi di seluruh 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 file run "default" dan "data" dipertahankan karena alasan lama. Misalnya, beberapa aturan menempatkan output defaultnya didata_runfiles
, tetapi tidak didefault_runfiles
. Daripada menggunakandata_runfiles
, aturan keduanya harus menyertakan output default dan menggabungkandefault_runfiles
dari atribut yang menyediakan runfile (sering kalidata
).Saat mengambil
runfiles
dariDefaultInfo
(biasanya hanya untuk menggabungkan file yang dapat dieksekusi 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
. Atribut tersebut diakses menggunakan operator titik, dan dibuat dengan menempatkan kolom dalam struct
yang ditampilkan oleh fungsi penerapan aturan, bukan daftar objek penyedia:
return struct(example_info = struct(headers = depset(...)))
Penyedia tersebut dapat diambil dari kolom yang sesuai pada objek Target
:
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
Gaya ini tidak digunakan lagi dan tidak boleh digunakan dalam kode baru; lihat informasi berikut yang dapat membantu Anda melakukan migrasi. Mekanisme penyedia baru menghindari konflik nama. API ini juga mendukung penyembunyian data, dengan mewajibkan kode apa pun yang mengakses instance penyedia untuk mengambilnya menggunakan simbol penyedia.
Untuk saat ini, penyedia lama masih didukung. Aturan dapat menampilkan 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
dan dep[MyInfo].y
.
Selain providers
, struct yang ditampilkan juga dapat mengambil beberapa kolom lain yang memiliki arti khusus (dan dengan demikian tidak membuat penyedia lama yang sesuai):
Kolom
files
,runfiles
,data_runfiles
,default_runfiles
, danexecutable
sesuai dengan kolom bernama sama diDefaultInfo
. Anda tidak diizinkan untuk menentukan salah satu kolom ini sekaligus menampilkan penyediaDefaultInfo
.Kolom
output_groups
mengambil nilai struct dan sesuai denganOutputGroupInfo
.
Dalam deklarasi aturan provides
, dan dalam deklarasi atribut
dependensi providers
, penyedia lama diteruskan sebagai string dan penyedia modern diteruskan
oleh simbol Info
-nya. Pastikan untuk mengubah dari string menjadi simbol saat melakukan migrasi. Untuk set aturan yang kompleks atau besar yang sulit diperbarui
secara atomik, Anda mungkin akan lebih mudah jika mengikuti urutan
langkah berikut:
Ubah aturan yang menghasilkan penyedia lama untuk menghasilkan penyedia lama dan modern, menggunakan sintaksis sebelumnya. Untuk aturan yang menyatakan bahwa aturan tersebut mengembalikan penyedia lama, perbarui pernyataan tersebut untuk menyertakan penyedia lama dan modern.
Ubah aturan yang menggunakan penyedia lama agar menggunakan penyedia modern. Jika ada deklarasi atribut yang memerlukan penyedia lama, perbarui juga deklarasi tersebut agar memerlukan penyedia modern. Secara opsional, Anda dapat menjalin pekerjaan ini dengan langkah 1 dengan membuat konsumen menerima atau mewajibkan salah satu penyedia: Uji keberadaan penyedia lama menggunakan
hasattr(target, 'foo')
, atau penyedia baru menggunakanFooInfo in target
.Hapus sepenuhnya penyedia lama dari semua aturan.