Aturan menentukan serangkaian tindakan yang dilakukan Bazel pada input untuk menghasilkan serangkaian output, yang dirujuk dalam penyedia yang ditampilkan oleh fungsi implementasi aturan. Misalnya, aturan biner C++ mungkin:
- Ambil kumpulan
.cpp
file sumber (input). - Menjalankan
g++
pada file sumber (tindakan). - Tampilkan penyedia
DefaultInfo
beserta output yang dapat dieksekusi dan file lainnya yang akan tersedia 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 untuk aturan ini. Sebagai penulis aturan, Anda tidak hanya harus mempertimbangkan input yang diberikan pengguna ke aturan, tetapi juga semua alat dan library yang diperlukan untuk menjalankan tindakan.
Sebelum membuat atau mengubah aturan apa pun, pastikan Anda telah memahami fase build Bazel. Penting untuk memahami tiga fase build (pemuatan, analisis, dan eksekusi). Anda juga perlu mempelajari makro untuk memahami perbedaan antara aturan dan makro. Untuk memulai, tinjau Tutorial Aturan terlebih dahulu. Kemudian, gunakan halaman ini sebagai referensi.
Beberapa aturan sudah tertanam di Bazel. Aturan native ini, seperti
genrule
dan filegroup
, memberikan beberapa dukungan inti.
Dengan menentukan aturan 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 harus memutuskan atribut yang didukungnya dan cara aturan tersebut menghasilkan output-nya.
Fungsi implementation
aturan menentukan perilaku persisnya selama fase analisis. Fungsi ini tidak menjalankan
perintah eksternal apa pun. Sebaliknya, skrip itu mendaftarkan tindakan yang akan digunakan
nanti selama fase eksekusi untuk membuat output aturan, jika
diperlukan.
Pembuatan aturan
Dalam file .bzl
, gunakan fungsi aturan untuk menentukan aturan baru, dan simpan hasilnya dalam variabel global. Panggilan ke rule
menentukan
atribut dan
fungsi implementasi:
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 khususnya
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 dalam menentukan target. Tindakan ini disebut membuat instance aturan. 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 untuk implementasi 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 (yang dibuat menggunakan modul
attr
) ke parameter attrs
rule
.
Atribut umum, seperti
name
dan visibility
, secara implisit ditambahkan ke semua aturan. Atribut tambahan secara implisit ditambahkan ke aturan pengujian dan yang dapat dieksekusi secara khusus. Atribut yang
secara implisit ditambahkan 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. Sering kali, skema atribut menentukan ekstensi file mana yang diharapkan untuk jenis 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 dependensi tersebut. (Misalnya,cc_library
menyediakanCcInfo
.)data
menentukan file yang akan tersedia pada runtime untuk setiap file yang dapat dieksekusi dan bergantung pada target. Ini akan memungkinkan penentuan file arbitrer.
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. Setiap atribut 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 sesuai dengan 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 error jika ada siklus dalam grafik dependensi target.
Atribut pribadi dan dependensi implisit
Atribut dependensi dengan nilai default akan membuat dependensi implisit. Hal ini
implisit karena merupakan bagian dari grafik target yang tidak ditentukan oleh pengguna
dalam file BUILD
. Dependensi implisit berguna untuk melakukan hard-coding terhadap
hubungan antara aturan dan fitur (dependensi waktu build, seperti
compiler), karena sering kali pengguna tidak tertarik untuk menentukan
alat yang digunakan aturan. Di dalam fungsi implementasi aturan, dependensi ini diperlakukan
sama dengan dependensi lainnya.
Jika ingin memberikan dependensi implisit tanpa mengizinkan pengguna
mengganti nilai tersebut, Anda dapat membuat atribut menjadi private dengan memberinya nama
yang diawali dengan garis bawah (_
). Atribut pribadi harus memiliki nilai
default. Umumnya, hanya atribut pribadi yang dapat digunakan untuk 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 implementasi example_library
menghasilkan tindakan yang memanggil
compiler, meskipun pengguna tidak meneruskan labelnya sebagai input. Karena
_compiler
adalah atribut pribadi, 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. Dengan begitu, pengguna dapat mengganti
compiler yang berbeda jika perlu, tetapi label compiler
tidak perlu diketahui.
Dependensi implisit umumnya digunakan untuk alat yang berada di repositori yang sama dengan implementasi aturan. Jika alat berasal dari platform eksekusi atau repositori yang berbeda, aturan tersebut harus mendapatkan alat tersebut dari toolchain.
Atribut output
Atribut output, seperti attr.output
dan
attr.output_list
, mendeklarasikan file output yang
dihasilkan target. Hal ini berbeda dari atribut dependensi dalam dua hal:
- Fungsi ini menentukan target file output, bukan merujuk ke target yang ditentukan di tempat lain.
- Target file output bergantung pada target aturan pembuatan instance, 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 sebuah aturan memiliki
satu atribut output, aturan tersebut biasanya bernama out
atau outs
.
Atribut output adalah cara yang lebih disukai untuk membuat output yang telah dideklarasikan, yang dapat secara khusus bergantung pada atau diminta pada command line.
Fungsi penerapan
Setiap aturan memerlukan fungsi implementation
. Fungsi-fungsi ini dijalankan
secara ketat dalam fase analisis dan mengubah
grafik target yang dihasilkan dalam fase pemuatan menjadi grafik
tindakan yang akan dilakukan selama fase eksekusi. Dengan demikian,
fungsi implementasi tidak dapat benar-benar membaca atau menulis file.
Fungsi implementasi aturan biasanya bersifat pribadi (dinamai dengan garis bawah di awal). Secara konvensional, nama kolom tersebut sama dengan aturannya, tetapi diberi akhiran _impl
.
Fungsi implementasi mengambil tepat satu parameter: konteks aturan, yang secara konvensional bernama ctx
. Metode ini menampilkan daftar penyedia.
Targets
Dependensi direpresentasikan pada waktu analisis sebagai objek
Target
. Objek ini berisi penyedia yang dihasilkan saat
fungsi implementasi target dijalankan.
ctx.attr
memiliki kolom yang sesuai dengan nama setiap
atribut dependensi, yang berisi objek Target
yang mewakili setiap dependensi
langsung menggunakan 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(...))]
Semuanya dapat diakses menggunakan notasi indeks ([]
), dengan jenis penyedia sebagai kunci. Penyedia 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 memberikannya
ke tindakan kompilasi target dan konsumennya, aturan tersebut dapat
mengumpulkannya seperti berikut:
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 darinya.
Files
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, peristiwa tersebut diteruskan ke fungsi yang memunculkan tindakan (lihat ctx.actions
) untuk membuat bagian dari grafik tindakan.
File
dapat berupa file sumber atau file yang dihasilkan. Setiap file yang dihasilkan
harus merupakan output dari tepat satu tindakan. File sumber tidak boleh menjadi output
dari tindakan apa pun.
Untuk setiap atribut dependensi, kolom
ctx.files
yang sesuai berisi daftar output default dari 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 implementasi aturan dapat membuat {i>output<i}.
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 telah dideklarasikan, seperti yang dibuat untuk
atribut output, objek File
dapat diambil
dari kolom ctx.outputs
yang sesuai.
Tindakan
Tindakan menjelaskan cara menghasilkan serangkaian output dari sekumpulan input, misalnya "jalankan gcc pada hello.c dan dapatkan hello.o". Saat tindakan dibuat, Bazel tidak langsung menjalankan perintah. Class ini mendaftarkannya dalam grafik dependensi karena suatu tindakan dapat bergantung pada output tindakan lain. Misalnya, di C, linker harus dipanggil setelah compiler.
Fungsi tujuan umum 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 mengumpulkan
argumen tindakan secara efisien. Hal ini menghindari dependensi yang merata 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 dependensi file input dan menghasilkan daftar file output (yang tidak kosong). Kumpulan file input dan output harus diketahui selama fase analisis. Ini mungkin bergantung pada nilai atribut, termasuk penyedia dari dependensi, tetapi tidak dapat bergantung pada hasil eksekusi. Misalnya, jika tindakan menjalankan perintah ekstrak, Anda harus menentukan file yang ingin di-inflate (sebelum menjalankan ekstrak). Tindakan yang membuat sejumlah variabel file secara internal dapat menggabungkannya 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 output-nya. Mereka dapat menulis file lain, tetapi apa pun yang tidak ada dalam output tidak akan tersedia bagi konsumen. Semua output yang dideklarasikan harus ditulis oleh beberapa tindakan.
Tindakan sebanding dengan fungsi murni: Tindakan tersebut hanya boleh 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). Hal ini penting karena outputnya akan disimpan dalam cache dan digunakan kembali.
Dependensi diselesaikan oleh Bazel, yang memutuskan tindakan mana yang akan dijalankan. Akan dianggap error jika ada siklus dalam grafik dependensi. Membuat tindakan tidak menjamin bahwa tindakan tersebut akan dieksekusi, yang bergantung pada apakah output-nya 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 untuk diteruskan pada command line alat, atau hal lain yang harus diketahui pelanggan target.
Karena fungsi penerapan aturan hanya dapat membaca penyedia dari dependensi langsung target yang dibuat instance-nya, aturan harus meneruskan informasi apa pun dari dependensi target yang perlu diketahui oleh konsumen target, umumnya dengan mengakumulasinya ke dalam depset
.
Penyedia target ditentukan oleh daftar objek penyedia yang ditampilkan oleh fungsi implementasi.
Fungsi implementasi lama juga dapat ditulis dalam gaya lama, dengan
fungsi implementasi menampilkan struct
, bukan daftar
objek penyedia. Gaya ini sangat tidak disarankan dan aturan harus dimigrasikan darinya.
Output default
Output default target adalah output yang diminta secara default saat
target diminta untuk melakukan build di command line. Misalnya, //pkg:foo
target java_library
memiliki foo.jar
sebagai output default, sehingga
akan dibuat 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 implementasi aturan atau parameter files
tidak ditentukan, DefaultInfo.files
akan ditetapkan secara default ke semua
output yang telah dideklarasikan sebelumnya (umumnya, output 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 dipangkas. Jika output hanya digunakan oleh konsumen target, tindakan tersebut tidak akan dilakukan saat target dibuat secara terpisah. Hal ini membuat proses debug menjadi lebih sulit karena mem-build ulang target yang gagal saja tidak akan mereproduksi kegagalan tersebut.
Runfile
Runfile adalah sekumpulan file yang digunakan oleh target pada runtime (bukan waktu build). Selama fase eksekusi, Bazel membuat hierarki direktori yang berisi symlink yang mengarah ke runfile. Tindakan ini akan mentahapkan lingkungan untuk biner agar dapat mengakses runfile selama runtime.
Runfile 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 secara implisit ditambahkan ke runfile.
Beberapa aturan menentukan atribut, umumnya bernama
data
, yang outputnya ditambahkan ke
runfile target. Runfile juga harus digabungkan dari data
, serta
dari atribut apa pun yang mungkin memberikan kode untuk akhirnya dieksekusi, 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 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 melindungi pembuatan instance penyedia dengan logika validasi dan prapemrosesan kustom. 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 dari dua nilai: simbol
penyedia yang merupakan nilai hasil biasa saat init
tidak digunakan, dan "konstruktor
raw".
Dalam hal ini, ketika simbol penyedia dipanggil, bukannya langsung menampilkan
instance baru, melainkan akan meneruskan argumen bersama ke callback init
. Nilai yang ditampilkan callback harus berupa nama kolom yang memetakan dict (string) ke nilai; nilai ini digunakan untuk menginisialisasi kolom instance baru. Perhatikan bahwa
callback mungkin memiliki tanda tangan, dan jika argumen tidak cocok dengan tanda tangan,
error akan dilaporkan seolah-olah callback tersebut 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
# 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,
)
Implementasi aturan kemudian dapat membuat instance penyedia seperti 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 pada variabel yang namanya diawali dengan garis bawah (_new_exampleinfo
di atas), sehingga kode pengguna tidak dapat memuatnya dan menghasilkan instance penyedia arbitrer.
Penggunaan lain untuk init
adalah mencegah pengguna memanggil simbol
penyedia sepenuhnya, dan memaksa mereka untuk menggunakan fungsi factory:
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 khusus yang dapat dieksekusi yang targetnya juga dapat dipanggil oleh perintah bazel test
. Aturan pengujian dan yang dapat dieksekusi dibuat dengan
menyetel 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 berakhiran _test
. (Nama target pengujian juga sering kali diakhiri dengan _test
berdasarkan konvensi, tetapi tidak wajib.) Aturan non-pengujian tidak boleh memiliki akhiran ini.
Kedua jenis aturan ini 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
). Secara implisit, file ini juga 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. Untuk
tindakan ctx.actions.run
atau
ctx.actions.run_shell
, hal ini harus dilakukan
oleh alat dasar yang dipanggil oleh tindakan tersebut. Untuk
tindakan ctx.actions.write
, teruskan is_executable = True
.
Sebagai perilaku lama, aturan yang dapat dieksekusi memiliki
output ctx.outputs.executable
khusus yang telah dideklarasikan sebelumnya. File ini berfungsi sebagai file
default yang dapat dieksekusi jika Anda tidak menentukannya menggunakan DefaultInfo
; jika tidak, file tidak boleh
digunakan. 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 yang ditambahkan untuk semua aturan. Default atribut yang ditambahkan secara implisit tidak dapat diubah, meskipun hal 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 Runfile
Jika target yang dapat dieksekusi dijalankan dengan bazel run
(atau test
), root
direktori runfiles berdekatan dengan file yang dapat dieksekusi tersebut. 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
dalam direktori runfiles sesuai dengan
File.short_path
.
Biner yang dieksekusi langsung oleh bazel
berdekatan dengan root direktori runfiles
. Namun, biner yang dipanggil dari runfile tidak dapat membuat asumsi yang sama. Untuk mengurangi hal ini, setiap biner harus menyediakan cara untuk menerima root runfile-nya sebagai parameter menggunakan lingkungan, atau argumen atau flag command line. Hal ini memungkinkan biner meneruskan root runfile kanonis yang benar ke biner yang dipanggilnya. Jika hal ini tidak ditetapkan, biner dapat menebak bahwa biner tersebut adalah
biner pertama yang dipanggil dan mencari direktori runfile yang berdekatan.
Topik lanjutan
Meminta file output
Satu target dapat memiliki beberapa file output. Saat perintah bazel build
dijalankan, beberapa output dari target yang diberikan ke perintah dianggap sebagai diminta. Bazel hanya mem-build file yang diminta ini dan file yang bergantung
secara langsung atau tidak langsung. (Dalam hal grafik tindakan, Bazel hanya
mengeksekusi tindakan yang dapat dijangkau sebagai dependensi transitif dari
file yang diminta.)
Selain output default, semua output yang telah dideklarasikan dapat
diminta secara eksplisit pada command line. Aturan dapat menentukan output yang telah dideklarasikan
sebelumnya menggunakan atribut output. Dalam hal ini, pengguna
secara eksplisit memilih label untuk output saat mereka membuat instance aturan. Untuk mendapatkan
objek File
untuk atribut output, gunakan atribut
yang sesuai dari ctx.outputs
. Aturan juga dapat secara implisit menentukan output yang telah dideklarasikan berdasarkan nama target, tetapi fitur ini tidak digunakan lagi.
Selain output default, ada grup output, yang merupakan 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 dalam output default atau grup
output.
Grup output dapat ditentukan dengan
penyedia OutputGroupInfo
. Perlu diperhatikan bahwa tidak seperti banyak
penyedia bawaan lainnya, 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]),
),
...
]
Tidak seperti sebagian besar penyedia, OutputGroupInfo
dapat ditampilkan oleh
aspek dan target aturan tempat aspek tersebut diterapkan, selama elemen tersebut tidak menentukan grup output yang sama. Dalam hal ini, penyedia
yang dihasilkan akan digabungkan.
Perlu diperhatikan bahwa OutputGroupInfo
umumnya tidak boleh digunakan untuk menyampaikan jenis
file tertentu dari target ke tindakan konsumennya. Sebagai gantinya, tentukan penyedia khusus aturan untuk properti tersebut.
Konfigurasi
Bayangkan Anda ingin membangun biner C++ untuk arsitektur yang berbeda. Build bisa bersifat kompleks dan melibatkan beberapa langkah. Beberapa biner perantara, seperti compiler dan generator kode, harus berjalan di platform eksekusi (yang dapat berupa host Anda, atau eksekutor jarak jauh). Beberapa biner seperti output akhir harus dibuat untuk arsitektur target.
Karena alasan ini, Bazel memiliki konsep "konfigurasi" dan transisi. Target teratas (yang diminta pada command line) sudah menjadi bawaan dalam konfigurasi "target", sedangkan alat yang harus berjalan di platform eksekusi bawaan 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 ini terjadi, aplikasi akan dianalisis dan berpotensi untuk dibangun beberapa kali.
Secara default, Bazel mem-build dependensi target dalam konfigurasi yang sama seperti target itu sendiri, dengan kata lain tanpa transisi. Jika dependensi adalah alat yang diperlukan untuk membantu mem-build target, atribut yang sesuai harus menentukan transisi ke konfigurasi exec. Hal ini menyebabkan alat dan semua dependensinya di-build untuk platform eksekusi.
Untuk setiap atribut dependensi, Anda dapat menggunakan cfg
untuk menentukan apakah dependensi
harus dibuat 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 pembuatan alat yang 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 dijalankan sebagai bagian dari build (seperti compiler atau generator kode)
harus dibangun untuk konfigurasi exec. Dalam hal ini, tentukan cfg = "exec"
dalam
atribut.
Jika tidak, file yang dapat dieksekusi yang digunakan saat runtime (misalnya, bagian dari pengujian) harus
di-build untuk konfigurasi target. Dalam hal ini, tentukan cfg = "target"
dalam
atribut.
cfg = "target"
tidak benar-benar melakukan apa pun: ini hanyalah nilai kenyamanan untuk membantu desainer aturan secara eksplisit tentang niat mereka. Jika executable = False
,
yang berarti cfg
bersifat opsional, hanya tetapkan ini jika benar-benar membantu meningkatkan keterbacaan.
Anda juga dapat menggunakan cfg = my_transition
untuk menggunakan
transisi yang ditentukan pengguna, yang memberikan
banyak fleksibilitas kepada penulis aturan 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 mewakili hal ini. Jika Anda melihat referensi ke "host" dalam kode atau dokumentasi lama, itulah yang dirujuk. 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 wajib 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 Runfile
Biasanya, jalur relatif file dalam hierarki runfiles sama dengan jalur
relatif file tersebut dalam hierarki sumber atau hierarki output yang dihasilkan. Jika argumen ini
perlu berbeda karena beberapa alasan, Anda dapat menentukan argumen root_symlinks
atau
symlinks
. root_symlinks
adalah jalur yang memetakan kamus ke
file, yang jalur tersebut terkait dengan root direktori runfile. Kamus
symlinks
sama, tetapi jalur secara implisit diawali 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 hierarki runfile. Hal 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 setiap 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 suatu 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. Subkumpulan
target yang dianggap dikontrol oleh tanda
--instrumentation_filter
.
Target pengujian dikecualikan, kecuali
--instrument_test_targets
ditentukan.
Jika penerapan aturan menambahkan instrumentasi cakupan pada waktu build, penerapan tersebut harus memperhitungkan hal tersebut dalam fungsi penerapannya. ctx.coverage_instrumented menampilkan True
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 (baik sumber target diinstrumentasi 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 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 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, dependensi 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
dalam dependency_attributes
, bukan source_attributes
, tetapi akan menghindari kebutuhan konfigurasi cakupan eksplisit untuk semua aturan dalam rantai dependensi.)
Tindakan Validasi
Terkadang, Anda perlu memvalidasi sesuatu tentang build, dan informasi yang diperlukan untuk melakukan validasi tersebut hanya tersedia dalam artefak (file sumber atau file yang dihasilkan). Karena informasi ini berada 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 begitu pula build.
Contoh validasi yang dapat dijalankan adalah analisis statis, analisis lint, pemeriksaan dependensi dan konsistensi, serta pemeriksaan gaya.
Tindakan validasi juga dapat membantu meningkatkan performa build dengan memindahkan bagian tindakan yang tidak diperlukan untuk mem-build artefak ke dalam tindakan terpisah. Misalnya, jika satu tindakan yang melakukan kompilasi dan analisis lint dapat dipisahkan menjadi tindakan kompilasi dan tindakan lint, tindakan analisis 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 tindakan validasi 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 aturan menjalankan tindakan tersebut? Secara historis, pendekatannya adalah membuat tindakan validasi menghasilkan file kosong, dan menambahkan output tersebut secara artifisial ke input beberapa tindakan penting lainnya dalam build:
Ini berfungsi, karena Bazel akan selalu menjalankan tindakan validasi saat tindakan kompilasi dijalankan, tetapi ini memiliki kelemahan yang signifikan:
Tindakan validasi berada di jalur penting build. Karena Bazel berpikir bahwa output kosong diperlukan untuk menjalankan tindakan kompilasi, tindakan validasi akan dijalankan terlebih dahulu, meskipun tindakan kompilasi akan mengabaikan input tersebut. Hal ini akan mengurangi paralelisme dan memperlambat build.
Jika tindakan lain dalam build mungkin berjalan, bukan tindakan kompilasi, output kosong tindakan validasi juga perlu ditambahkan ke tindakan tersebut (misalnya, output jar sumber
java_library
). Hal ini juga akan menjadi masalah jika tindakan baru yang mungkin dijalankan, bukan tindakan kompilasi, ditambahkan nanti, dan output validasi kosong tidak sengaja dihentikan.
Solusi untuk masalah ini adalah dengan 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 artifisial ke input tindakan lain.
Grup ini bersifat istimewa karena outputnya selalu diminta, terlepas dari nilai flag --output_groups
, dan terlepas dari bagaimana target itu ditentukan (misalnya, pada command line, sebagai dependensi, atau melalui output implisit target). Perlu diperhatikan bahwa caching normal dan inkrementalitas
masih berlaku: jika input untuk tindakan validasi tidak berubah dan
tindakan validasi sebelumnya berhasil, tindakan validasi tidak akan
dijalankan.
Penggunaan grup output ini masih mengharuskan tindakan validasi menghasilkan beberapa file, bahkan file kosong. Tindakan ini mungkin memerlukan penggabungan beberapa alat yang biasanya tidak menghasilkan output agar file dapat dibuat.
Tindakan validasi target tidak dijalankan dalam tiga kasus:
- Kapan target diandalkan sebagai alat
- Jika target bergantung sebagai dependensi implisit (misalnya, atribut yang dimulai dengan "_")
- Saat target dibangun dalam konfigurasi exec.
Diasumsikan bahwa target ini memiliki build dan pengujiannya sendiri yang 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 dari jenis aturan ini
akan tetap berjalan jika target bergantung pada label, atau salah satu
output implisit target tersebut bergantung secara langsung atau tidak langsung.
Biasanya output tindakan validasi harus masuk ke grup output validasi, dan tidak ditambahkan ke input tindakan lain, karena hal ini dapat mengalahkan keuntungan paralelisme. Namun, perlu diperhatikan 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 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 flag command line --run_validations
, yang nilai defaultnya adalah benar (true).
Fitur yang tidak digunakan lagi
Output yang dipradeklarasikan 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 output dan template string untuk menghasilkan label output yang telah dideklarasikan sebelumnya. Memilih menggunakan output yang tidak dideklarasikan sebelumnya dan secara eksplisit menambahkan output 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
mengacu pada output yang dapat dieksekusi yang telah dideklarasikan sebelumnya dengan nama yang sama dengan target aturan. Memilih mendeklarasikan output secara eksplisit, misalnya denganctx.actions.declare_file(ctx.label.name)
, dan memastikan bahwa perintah yang menghasilkan file yang dapat dieksekusi menetapkan izinnya untuk memungkinkan eksekusi. Teruskan output yang dapat dieksekusi secara eksplisit ke parameterexecutable
dariDefaultInfo
.
Fitur Runfile yang harus dihindari
ctx.runfiles
dan jenis runfiles
memiliki sekumpulan fitur yang kompleks, banyak di antaranya disimpan karena alasan lama.
Rekomendasi berikut membantu mengurangi kompleksitas:
Hindari penggunaan mode
collect_data
dancollect_default
ctx.runfiles
. Mode ini secara implisit mengumpulkan runfile 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 runfile "default" dan "data" dipertahankan karena alasan lama. Misalnya, beberapa aturan menempatkan output defaultnya didata_runfiles
, tetapi tidak didefault_runfiles
. Daripada menggunakandata_runfiles
, aturan harus kedua menyertakan output default dan menggabungkandefault_runfiles
dari atribut yang menyediakan runfile (sering kalidata
).Saat mengambil
runfiles
dariDefaultInfo
(umumnya hanya untuk menggabungkan runfile 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
. Objek tersebut diakses menggunakan operator titik, dan dibuat dengan menempatkan kolom dalam struct
yang ditampilkan oleh fungsi implementasi aturan, bukan daftar objek penyedia:
return struct(example_info = struct(headers = depset(...)))
Penyedia tersebut dapat diambil dari kolom objek Target
yang sesuai:
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
Gaya ini tidak digunakan lagi dan sebaiknya tidak digunakan dalam kode baru; lihat informasi berikut untuk mengetahui informasi yang dapat membantu Anda melakukan migrasi. Mekanisme penyedia baru menghindari konflik nama. Fungsi ini juga mendukung penyembunyian data, dengan mengharuskan kode yang mengakses instance penyedia untuk mengambilnya menggunakan simbol penyedia.
Untuk saat ini, penyedia lama masih didukung. Aturan dapat menampilkan penyedia lama dan modern seperti 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 (sehingga tidak membuat penyedia lama yang sesuai):
Kolom
files
,runfiles
,data_runfiles
,default_runfiles
, danexecutable
sesuai dengan kolom dengan nama yang sama dariDefaultInfo
. Anda tidak boleh menentukan salah satu kolom ini sambil 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
. Pastikan untuk mengubah dari string ke simbol
saat bermigrasi. Untuk kumpulan aturan yang kompleks atau besar di mana sulit untuk memperbarui semua aturan 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 mendeklarasikan bahwa mereka menampilkan penyedia lama, perbarui deklarasi 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 agar mewajibkan penyedia modern. Secara opsional, Anda dapat menyisipkan pekerjaan ini dengan langkah 1 dengan meminta konsumen menerima atau mewajibkan salah satu penyedia: Menguji keberadaan penyedia lama menggunakan
hasattr(target, 'foo')
, atau penyedia baru menggunakanFooInfo in target
.Hapus sepenuhnya penyedia lama dari semua aturan.