Halaman ini menjelaskan framework toolchain, yang merupakan cara bagi penulis aturan untuk memisahkan logika aturan mereka dari pilihan alat berbasis platform. Sebaiknya baca halaman aturan dan platform sebelum melanjutkan. Halaman ini membahas alasan diperlukannya toolchain, cara menentukan dan menggunakannya, serta cara Bazel memilih toolchain yang sesuai berdasarkan batasan platform.
Motivasi
Pertama-tama, mari kita lihat masalah yang dirancang untuk dipecahkan. Misalkan Anda menulis aturan untuk mendukung bahasa pemrograman "bar". Aturan bar_binary
Anda akan mengompilasi file *.bar
menggunakan compiler barc
, sebuah alat yang dibuat sendiri sebagai target lain di ruang kerja Anda. Karena pengguna yang menulis target bar_binary
tidak perlu menentukan dependensi pada compiler, Anda menjadikannya dependensi implisit dengan menambahkannya ke definisi aturan sebagai atribut pribadi.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
"_compiler": attr.label(
default = "//bar_tools:barc_linux", # the compiler running on linux
providers = [BarcInfo],
),
},
)
//bar_tools:barc_linux
sekarang menjadi dependensi dari setiap target bar_binary
, sehingga
di-build sebelum target bar_binary
apa pun. Atribut ini dapat diakses oleh fungsi implementasi aturan seperti atribut lainnya:
BarcInfo = provider(
doc = "Information about how to invoke the barc compiler.",
# In the real world, compiler_path and system_lib might hold File objects,
# but for simplicity they are strings for this example. arch_flags is a list
# of strings.
fields = ["compiler_path", "system_lib", "arch_flags"],
)
def _bar_binary_impl(ctx):
...
info = ctx.attr._compiler[BarcInfo]
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
Masalahnya di sini adalah label compiler di-hardcode menjadi bar_binary
, tetapi target yang berbeda mungkin memerlukan compiler yang berbeda, bergantung pada platform pembuatannya dan platform yang digunakan untuk mem-build-nya, masing-masing disebut platform target dan platform eksekusi. Selain itu, penulis aturan bahkan tidak mengetahui semua alat dan platform yang tersedia, sehingga tidak mungkin untuk melakukan hardcode pada definisi aturan.
Solusi yang kurang ideal adalah mengalihkan beban kepada pengguna, dengan membuat atribut _compiler
menjadi non-pribadi. Kemudian, setiap target dapat di-hardcode untuk dibuat untuk satu platform atau platform lainnya.
bar_binary(
name = "myprog_on_linux",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_linux",
)
bar_binary(
name = "myprog_on_windows",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_windows",
)
Anda dapat meningkatkan solusi ini menggunakan select
untuk memilih compiler
berdasarkan platform:
config_setting(
name = "on_linux",
constraint_values = [
"@platforms//os:linux",
],
)
config_setting(
name = "on_windows",
constraint_values = [
"@platforms//os:windows",
],
)
bar_binary(
name = "myprog",
srcs = ["mysrc.bar"],
compiler = select({
":on_linux": "//bar_tools:barc_linux",
":on_windows": "//bar_tools:barc_windows",
}),
)
Tapi ini merepotkan dan banyak yang harus ditanyakan dari setiap pengguna bar_binary
.
Jika gaya ini tidak digunakan secara konsisten di seluruh ruang kerja, gaya ini akan menyebabkan
build yang berfungsi dengan baik pada satu platform, tetapi gagal saat diperluas ke
skenario multi-platform. Hal ini juga tidak mengatasi masalah penambahan dukungan
untuk platform dan compiler baru tanpa mengubah aturan atau target yang ada.
Framework toolchain mengatasi masalah ini dengan menambahkan level tidak langsung tambahan. Pada dasarnya, Anda mendeklarasikan bahwa aturan Anda memiliki dependensi abstrak pada beberapa anggota kelompok target (jenis toolchain), dan Bazel akan secara otomatis me-resolve target tertentu (toolchain) berdasarkan batasan platform yang berlaku. Baik penulis aturan maupun penulis target tidak perlu mengetahui rangkaian lengkap platform dan toolchain yang tersedia.
Menulis aturan yang menggunakan toolchain
Pada framework toolchain, aturan tidak bergantung langsung pada alat, melainkan bergantung pada jenis toolchain. Jenis toolchain adalah target sederhana yang mewakili class alat dengan peran yang sama untuk platform yang berbeda. Misalnya, Anda dapat mendeklarasikan jenis yang merepresentasikan compiler batang:
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
Definisi aturan di bagian sebelumnya diubah sehingga compiler ini mendeklarasikan bahwa compiler menggunakan toolchain //bar_tools:toolchain_type
.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
Fungsi implementasi kini mengakses dependensi ini pada ctx.toolchains
,
bukan ctx.attr
, menggunakan jenis toolchain sebagai kuncinya.
def _bar_binary_impl(ctx):
...
info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
# The rest is unchanged.
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
ctx.toolchains["//bar_tools:toolchain_type"]
menampilkan
penyedia ToolchainInfo
target apa pun yang di-resolve dependensi toolchain oleh Bazel. Kolom objek
ToolchainInfo
ditetapkan oleh aturan alat pokok; di bagian
berikutnya, aturan ini ditentukan sedemikian rupa sehingga ada kolom barcinfo
yang menggabungkan
objek BarcInfo
.
Prosedur Bazel untuk me-resolve toolchain ke target dijelaskan di bawah. Hanya target toolchain yang di-resolve yang benar-benar
membuat dependensi target bar_binary
, bukan seluruh ruang toolchain
kandidat.
Toolchain Wajib dan Opsional
Secara default, saat aturan menyatakan dependensi jenis toolchain menggunakan label kosong (seperti yang ditunjukkan di atas), jenis toolchain dianggap wajib. Jika Bazel tidak dapat menemukan toolchain yang cocok (lihat Resolusi Toolchain di bawah) untuk jenis toolchain wajib, ini adalah error dan analisis akan terhenti.
Anda dapat mendeklarasikan dependensi jenis toolchain opsional, seperti berikut:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Jika jenis toolchain opsional tidak dapat diselesaikan, analisis akan dilanjutkan, dan hasil ctx.toolchains["//bar_tools:toolchain_type"]
adalah None
.
Fungsi config_common.toolchain_type
secara default disetel ke wajib.
Formulir berikut dapat digunakan:
- Jenis toolchain wajib:
toolchains = ["//bar_tools:toolchain_type"]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
- Jenis toolchain opsional:
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
...
toolchains = [
"//foo_tools:toolchain_type",
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Anda juga dapat memadupadankan formulir dalam aturan yang sama. Namun, jika jenis toolchain yang sama dicantumkan beberapa kali, jenis toolchain tersebut akan mengambil versi yang paling ketat, yaitu wajib lebih ketat daripada opsional.
Menulis aspek yang menggunakan toolchain
Aspek memiliki akses ke API toolchain yang sama seperti aturan: Anda dapat menentukan jenis toolchain yang diperlukan, mengakses toolchain melalui konteks, dan menggunakannya untuk menghasilkan tindakan baru menggunakan toolchain.
bar_aspect = aspect(
implementation = _bar_aspect_impl,
attrs = {},
toolchains = ['//bar_tools:toolchain_type'],
)
def _bar_aspect_impl(target, ctx):
toolchain = ctx.toolchains['//bar_tools:toolchain_type']
# Use the toolchain provider like in a rule.
return []
Menentukan toolchain
Untuk menentukan beberapa toolchain untuk jenis toolchain tertentu, Anda memerlukan tiga hal:
Aturan spesifik per bahasa yang mewakili jenis alat atau rangkaian alat. Berdasarkan konvensi, nama aturan ini diberi akhiran "_ toolchain".
- Catatan: Aturan
\_toolchain
tidak dapat membuat tindakan build apa pun. Sebaliknya, layanan ini mengumpulkan artefak dari aturan lain dan meneruskannya ke aturan yang menggunakan toolchain. Aturan tersebut bertanggung jawab untuk membuat semua tindakan build.
- Catatan: Aturan
Beberapa target dari jenis aturan ini, yang mewakili versi rangkaian alat atau alat untuk berbagai platform.
Untuk setiap target tersebut, target terkait dari aturan
toolchain
generik, untuk menyediakan metadata yang digunakan oleh framework toolchain. Targettoolchain
ini juga merujuk padatoolchain_type
yang terkait dengan toolchain ini. Ini berarti bahwa aturan_toolchain
tertentu dapat dikaitkan dengantoolchain_type
apa pun, dan hanya dalam instancetoolchain
yang menggunakan aturan_toolchain
ini bahwa aturan tersebut dapat dikaitkan dengantoolchain_type
.
Untuk contoh yang berjalan, berikut adalah definisi untuk aturan bar_toolchain
. Contoh kita hanya memiliki compiler, tetapi alat lain seperti linker juga dapat dikelompokkan di bawahnya.
def _bar_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
barcinfo = BarcInfo(
compiler_path = ctx.attr.compiler_path,
system_lib = ctx.attr.system_lib,
arch_flags = ctx.attr.arch_flags,
),
)
return [toolchain_info]
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler_path": attr.string(),
"system_lib": attr.string(),
"arch_flags": attr.string_list(),
},
)
Aturan ini harus menampilkan penyedia ToolchainInfo
, yang menjadi objek yang diambil oleh aturan yang menggunakan menggunakan ctx.toolchains
dan label jenis toolchain. ToolchainInfo
, seperti struct
, dapat menyimpan pasangan nilai kolom arbitrer. Spesifikasi secara tepat kolom yang ditambahkan ke ToolchainInfo
harus didokumentasikan dengan jelas pada jenis toolchain. Dalam contoh ini, nilai
ditampilkan dalam objek BarcInfo
untuk menggunakan kembali skema yang ditentukan di atas; gaya
ini mungkin berguna untuk validasi dan penggunaan ulang kode.
Sekarang Anda dapat menentukan target untuk compiler barc
tertentu.
bar_toolchain(
name = "barc_linux",
arch_flags = [
"--arch=Linux",
"--debug_everything",
],
compiler_path = "/path/to/barc/on/linux",
system_lib = "/usr/lib/libbarc.so",
)
bar_toolchain(
name = "barc_windows",
arch_flags = [
"--arch=Windows",
# Different flags, no debug support on windows.
],
compiler_path = "C:\\path\\on\\windows\\barc.exe",
system_lib = "C:\\path\\on\\windows\\barclib.dll",
)
Terakhir, Anda akan membuat definisi toolchain
untuk kedua target bar_toolchain
.
Definisi ini menautkan target bahasa tertentu ke jenis toolchain dan menyediakan informasi batasan yang memberi tahu Bazel kapan toolchain sesuai untuk platform tertentu.
toolchain(
name = "barc_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_linux",
toolchain_type = ":toolchain_type",
)
toolchain(
name = "barc_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_windows",
toolchain_type = ":toolchain_type",
)
Penggunaan sintaksis jalur relatif di atas menunjukkan bahwa definisi ini semuanya berada dalam
paket yang sama, tetapi tidak ada alasan jenis toolchain, target toolchain
khusus bahasa, dan target definisi toolchain
tidak semuanya dapat berada dalam paket
terpisah.
Lihat go_toolchain
untuk contoh nyata.
Toolchain dan konfigurasi
Pertanyaan penting bagi penulis aturan adalah, saat target bar_toolchain
dianalisis, konfigurasi apa yang dilihatnya, dan transisi apa yang harus digunakan untuk dependensi? Contoh di atas menggunakan atribut string, tetapi
apa yang akan terjadi untuk toolchain lebih rumit yang bergantung pada target lain
dalam repositori Bazel?
Mari kita lihat versi bar_toolchain
yang lebih kompleks:
def _bar_toolchain_impl(ctx):
# The implementation is mostly the same as above, so skipping.
pass
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler": attr.label(
executable = True,
mandatory = True,
cfg = "exec",
),
"system_lib": attr.label(
mandatory = True,
cfg = "target",
),
"arch_flags": attr.string_list(),
},
)
Penggunaan attr.label
sama seperti untuk aturan standar,
tetapi arti parameter cfg
sedikit berbeda.
Dependensi dari target (disebut "induk") ke toolchain melalui resolusi toolchain menggunakan transisi konfigurasi khusus yang disebut "transisi
toolchain". Transisi toolchain mempertahankan konfigurasi tetap sama, hanya saja
transisi memaksa platform eksekusi sama untuk toolchain dan
induk (jika tidak, resolusi toolchain untuk toolchain dapat memilih
platform eksekusi apa pun, dan tidak harus sama seperti untuk induk). Hal ini
memungkinkan dependensi exec
dari toolchain juga dapat dieksekusi untuk
tindakan build induk. Setiap dependensi toolchain yang menggunakan cfg =
"target"
(atau yang tidak menentukan cfg
, karena "target" adalah default) dibuat
untuk platform target yang sama dengan induknya. Hal ini memungkinkan aturan toolchain untuk
mengontribusi library (atribut system_lib
di atas) dan alat (atribut
compiler
) ke aturan build yang memerlukannya. Library sistem
ditautkan ke artefak akhir sehingga harus di-build untuk platform
yang sama, sedangkan compiler adalah alat yang dipanggil selama build, dan harus
dapat berjalan di platform eksekusi.
Mendaftarkan dan membangun dengan toolchain
Pada tahap ini, semua elemen penyusun telah disusun, dan Anda hanya perlu menyediakan
toolchain untuk prosedur resolusi Bazel. Hal ini dilakukan dengan mendaftarkan toolchain, baik dalam file MODULE.bazel
menggunakan register_toolchains()
, maupun dengan meneruskan label toolchain pada command line menggunakan flag --extra_toolchains
.
register_toolchains(
"//bar_tools:barc_linux_toolchain",
"//bar_tools:barc_windows_toolchain",
# Target patterns are also permitted, so you could have also written:
# "//bar_tools:all",
# or even
# "//bar_tools/...",
)
Jika menggunakan pola target untuk mendaftarkan toolchain, urutan pendaftaran masing-masing toolchain ditentukan oleh aturan berikut:
- Toolchain yang ditentukan dalam subpaket dari paket didaftarkan sebelum toolchain ditentukan dalam paket itu sendiri.
- Dalam paket, toolchain didaftarkan dalam urutan leksikografis namanya.
Kini, ketika Anda membuat target yang bergantung pada jenis toolchain, toolchain yang sesuai akan dipilih berdasarkan platform eksekusi dan target.
# my_pkg/BUILD
platform(
name = "my_target_platform",
constraint_values = [
"@platforms//os:linux",
],
)
bar_binary(
name = "my_bar_binary",
...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
Bazel akan melihat bahwa //my_pkg:my_bar_binary
dibuat dengan platform yang
memiliki @platforms//os:linux
, sehingga me-resolve referensi
//bar_tools:toolchain_type
ke //bar_tools:barc_linux_toolchain
.
Tindakan ini akan membuat //bar_tools:barc_linux
, tetapi tidak
//bar_tools:barc_windows
.
Resolusi toolchain
Untuk setiap target yang menggunakan toolchain, prosedur resolusi toolchain Bazel akan menentukan dependensi toolchain konkret target tersebut. Prosedur ini memerlukan input berupa serangkaian jenis toolchain yang diperlukan, platform target, daftar platform eksekusi yang tersedia, dan daftar toolchain yang tersedia. Output-nya adalah toolchain yang dipilih untuk setiap jenis toolchain serta platform eksekusi yang dipilih untuk target saat ini.
Platform eksekusi dan toolchain yang tersedia dikumpulkan dari
grafik dependensi eksternal melalui
panggilan register_execution_platforms
dan
register_toolchains
dalam
file MODULE.bazel
.
Toolchain dan platform eksekusi tambahan juga dapat ditentukan pada
command line melalui
--extra_execution_platforms
dan
--extra_toolchains
.
Platform host secara otomatis disertakan sebagai platform eksekusi yang tersedia.
Platform dan toolchain yang tersedia dilacak sebagai daftar yang diurutkan untuk determinisme,
dengan preferensi yang diberikan ke item sebelumnya dalam daftar.
Kumpulan toolchain yang tersedia, dalam urutan prioritas, dibuat dari
--extra_toolchains
dan register_toolchains
:
- Toolchain yang terdaftar menggunakan
--extra_toolchains
akan ditambahkan terlebih dahulu. (Dalam ini, toolchain terakhir memiliki prioritas tertinggi.) - Toolchain yang didaftarkan menggunakan
register_toolchains
dalam grafik dependensi eksternal transitif, dengan urutan berikut: (Dalam urutan berikut, toolchain yang pertama disebutkan memiliki prioritas tertinggi.)- Toolchain yang didaftarkan oleh modul root (seperti,
MODULE.bazel
di root ruang kerja); - Toolchain yang terdaftar dalam file
WORKSPACE
pengguna, termasuk dalam makro apa pun yang dipanggil dari sana; - Toolchain yang didaftarkan oleh modul non-root (seperti pada dependensi yang ditentukan oleh modul root, dependensinya, dan sebagainya);
- Toolchain yang terdaftar dalam "akhiran WORKSPACE"; hanya digunakan oleh aturan native tertentu yang dipaketkan dengan penginstalan Bazel.
- Toolchain yang didaftarkan oleh modul root (seperti,
CATATAN: Target pseudo seperti :all
, :*
, dan
/...
diurutkan oleh mekanisme pemuatan paket Bazel, yang menggunakan pengurutan leksikografis.
Langkah-langkah penyelesaiannya adalah sebagai berikut.
Klausa
target_compatible_with
atauexec_compatible_with
cocok dengan platform jika, untuk setiapconstraint_value
dalam daftar, platform juga memilikiconstraint_value
tersebut (baik secara eksplisit maupun default).Jika platform memiliki
constraint_value
dariconstraint_setting
yang tidak direferensikan oleh klausa, hal tersebut tidak akan memengaruhi pencocokan.Jika target yang dibuat menentukan atribut
exec_compatible_with
(atau definisi aturannya menentukan argumenexec_compatible_with
), daftar platform eksekusi yang tersedia akan difilter untuk menghapus yang tidak cocok dengan batasan eksekusi.Daftar toolchain yang tersedia difilter untuk menghapus toolchain yang menentukan
target_settings
yang tidak cocok dengan konfigurasi saat ini.Untuk setiap platform eksekusi yang tersedia, Anda akan mengaitkan setiap jenis toolchain dengan toolchain pertama yang tersedia, jika ada, yang kompatibel dengan platform eksekusi ini dan platform target.
Setiap platform eksekusi yang gagal menemukan toolchain wajib yang kompatibel untuk salah satu jenis toolchain-nya dikesampingkan. Dari platform yang tersisa, platform pertama akan menjadi platform eksekusi target saat ini, dan toolchain terkait (jika ada) menjadi dependensi target.
Platform eksekusi yang dipilih digunakan untuk menjalankan semua tindakan yang dihasilkan target.
Jika target yang sama dapat dibangun dalam beberapa konfigurasi (seperti untuk CPU yang berbeda) dalam build yang sama, prosedur resolusi akan diterapkan secara independen untuk setiap versi target.
Jika aturan menggunakan grup eksekusi, setiap grup eksekusi melakukan resolusi toolchain secara terpisah, dan masing-masing memiliki platform eksekusi dan toolchain sendiri.
Men-debug toolchain
Jika Anda menambahkan dukungan toolchain ke aturan yang ada, gunakan flag --toolchain_resolution_debug=regex
. Selama resolusi toolchain, flag
menyediakan output panjang untuk jenis toolchain atau nama target yang cocok dengan variabel ekspresi reguler. Anda
dapat menggunakan .*
untuk menghasilkan semua informasi. Bazel akan menampilkan nama toolchain yang
diperiksa dan dilewati selama proses resolusi.
Jika Anda ingin melihat dependensi cquery
mana yang berasal dari resolusi toolchain, gunakan flag --transitions
cquery
:
# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)
# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
[toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211