Halaman ini menjelaskan kerangka kerja toolchain, yang merupakan cara bagi penulis aturan untuk memisahkan logika aturan mereka dari pemilihan alat berbasis platform. Penting disarankan untuk membaca aturan dan platform sebelum melanjutkan. Halaman ini membahas mengapa toolchain diperlukan, cara mendefinisikan dan menggunakannya, dan bagaimana Bazel memilih rantai alat yang sesuai berdasarkan dan batasan platform.
Motivasi
Mari kita cermati masalah yang dirancang untuk dapat diselesaikan oleh toolchain. Misalkan Anda
menulis aturan untuk mendukung "bar" yang berpusat pada
data (data-centric). bar_binary
Anda
akan mengompilasi file *.bar
menggunakan compiler barc
, alat yang
dibangun sebagai target lain
di ruang kerja Anda. Karena pengguna yang menulis bar_binary
target tidak harus menentukan dependensi pada kompiler, Anda membuatnya
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
model tersebut akan dibangun sebelum target bar_binary
. URL ini dapat diakses oleh
sama 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 kompiler
yang berbeda tergantung pada platform yang
dan platform yang digunakannya -- yang disebut
platform target dan platform eksekusi. Selain itu, aturan
penulis belum tentu mengetahui semua
alat dan platform yang tersedia, jadi
tidak mungkin untuk melakukan hardcode pada definisi aturan tersebut.
Solusi yang kurang ideal adalah mengalihkan beban kepada pengguna, dengan membuat
atribut _compiler
non-pribadi. Kemudian target individu bisa
di-hardcode untuk membangun
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 terlalu banyak ditanyakan oleh setiap pengguna bar_binary
.
Jika tidak digunakan secara konsisten di seluruh ruang kerja, gaya ini akan menyebabkan
build yang berfungsi dengan baik pada satu platform tetapi gagal ketika diperluas ke
skenario multi-platform. Contoh ini juga tidak membahas masalah penambahan dukungan
untuk platform dan compiler baru tanpa mengubah aturan atau target yang ada.
Framework toolchain mengatasi masalah ini dengan menambahkan level tambahan tidak langsung. Pada dasarnya, Anda mendeklarasikan bahwa aturan Anda memiliki dependensi abstrak pada beberapa anggota grup target (jenis toolchain), dan Bazel secara otomatis me-resolve ini ke target tertentu (rantai alat) berdasarkan batasan platform yang berlaku. Baik penulis aturan maupun penulis target perlu mengetahui set lengkap platform dan toolchain yang tersedia.
Menulis aturan yang menggunakan toolchain
Dalam framework toolchain, alih-alih memiliki aturan yang bergantung langsung pada alat, tetapi bergantung pada jenis toolchain. Jenis toolchain adalah target sederhana yang merepresentasikan kelas alat dengan peran yang sama untuk di berbagai platform Google. Misalnya, Anda dapat mendeklarasikan jenis yang mewakili batang {i>compiler<i}:
# 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 alih-alih
menggunakan compiler sebagai atribut, ia mendeklarasikan bahwa ia 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 sekarang mengakses dependensi ini di bagian ctx.toolchains
alih-alih ctx.attr
, menggunakan jenis toolchain sebagai kunci.
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"]
akan menampilkan
Penyedia ToolchainInfo
target apa pun yang Bazel menyelesaikan dependensi toolchain. Kolom dari
Objek ToolchainInfo
ditetapkan oleh aturan alat yang mendasarinya; dalam langkah berikutnya
aturan ini ditetapkan sedemikian rupa sehingga ada kolom barcinfo
yang menggabungkan
objek BarcInfo
.
Prosedur Bazel untuk menyelesaikan toolchain ke target dijelaskan
di bawah. Hanya target toolchain yang telah di-resolve yang sebenarnya
membuat dependensi target bar_binary
, bukan seluruh ruang kandidat
toolchain.
Toolchain Wajib dan Opsional
Secara default, saat aturan mengekspresikan 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 toolchain wajib itu adalah {i>error<i} dan analisis berhenti.
Anda dapat mendeklarasikan dependensi jenis toolchain opsional, sebagai berikut ini:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
Ketika tipe toolchain opsional tidak dapat diselesaikan, analisis akan dilanjutkan, dan
hasil ctx.toolchains["//bar_tools:toolchain_type"]
adalah None
.
config_common.toolchain_type
fungsi defaultnya adalah 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 mengombinasikan formulir dalam aturan yang sama. Namun, jika hal yang sama yang terdaftar beberapa kali, itu akan mengambil versi yang paling ketat, di mana hal wajib lebih ketat daripada opsional.
Menulis aspek yang menggunakan toolchain
Aspek memiliki akses ke API toolchain yang sama dengan aturan: Anda dapat menentukan jenis toolchain, mengakses toolchain melalui konteks, dan menggunakannya untuk membuat tindakan ini 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 bagi jenis toolchain tertentu, Anda memerlukan tiga hal:
Aturan spesifik per bahasa yang mewakili jenis alat atau rangkaian alat. Menurut dari konvensi, nama aturan ini diakhiri dengan "_toolchain".
- Catatan: Aturan
\_toolchain
tidak dapat membuat tindakan build apa pun. Sebaliknya, ia mengumpulkan artefak dari aturan lain dan meneruskannya ke yang menggunakan toolchain. Aturan itu bertanggung jawab untuk membuat semua tindakan build.
- Catatan: Aturan
Beberapa target jenis aturan ini, yang mewakili versi alat atau alat Google Cloud untuk platform yang berbeda.
Untuk setiap target tersebut, target terkait dari
toolchain
, untuk menyediakan metadata yang digunakan oleh framework toolchain.toolchain
ini target juga merujuk ketoolchain_type
yang terkait dengan toolchain ini. Artinya, aturan_toolchain
tertentu dapat dikaitkan dengan setiaptoolchain_type
, dan hanya dalam instancetoolchain
yang menggunakan aturan_toolchain
ini bahwa aturan tersebut terkait dengantoolchain_type
.
Untuk contoh yang berjalan, berikut adalah definisi untuk aturan bar_toolchain
. Dengan
hanya memiliki sebuah compiler, tetapi alat lain seperti penaut juga bisa
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 pemakaian menggunakan ctx.toolchains
dan label
jenis toolchain tersebut. ToolchainInfo
, seperti struct
, dapat menyimpan nilai kolom arbitrer
pasangan. Spesifikasi kolom yang ditambahkan ke ToolchainInfo
harus didokumentasikan dengan jelas pada jenis toolchain. Dalam contoh ini, nilai-nilai
nilai yang digabungkan dalam objek BarcInfo
untuk menggunakan kembali skema yang ditentukan di atas; ingin
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 menghubungkan target spesifik bahasa dengan tipe toolchain dan
berikan informasi batasan yang memberi tahu Bazel saat toolchain
yang 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 sintaks jalur relatif di atas menunjukkan bahwa semua definisi ini berada di
paket yang sama, tetapi tidak ada alasan untuk
tipe {i>chain <i}yang spesifik,
target toolchain, dan target definisi toolchain
tidak boleh semuanya berada dalam
paket.
Lihat go_toolchain
untuk contoh di dunia nyata.
Toolchain dan konfigurasi
Pertanyaan penting bagi penulis aturan adalah, saat target bar_toolchain
adalah
dianalisis, konfigurasi apa yang dilihatnya, dan transisi apa
yang harus digunakan untuk dependensi? Contoh di atas menggunakan atribut {i>string<i}, tetapi
apa yang akan terjadi untuk toolchain yang 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 "parent") ke toolchain melalui toolchain
menggunakan transisi konfigurasi khusus yang disebut
transisi". Transisi toolchain membuat konfigurasi tetap sama, kecuali
bahwa platform eksekusi harus sama untuk toolchain
induk (jika tidak, resolusi toolchain untuk toolchain dapat memilih
dan tidak harus sama dengan platform induk). Ini
memungkinkan dependensi exec
toolchain tersebut juga dapat dieksekusi untuk
tindakan build induk. Setiap dependensi toolchain yang menggunakan cfg =
"target"
(atau yang tidak menentukan cfg
, karena "target" adalah default) adalah
dibuat untuk platform target yang sama dengan induknya. Hal ini memungkinkan aturan toolchain untuk
berkontribusi pada library (atribut system_lib
di atas) dan alat (
compiler
) ke aturan build yang memerlukannya. Library sistem
ditautkan ke dalam artefak akhir, jadi perlu dibangun untuk
sementara compiler adalah alat yang dipanggil selama build, dan perlu
dapat dijalankan
di platform eksekusi.
Mendaftar dan membangun dengan toolchain
Pada tahap ini, semua blok bangunan sudah disusun, dan Anda hanya perlu
toolchain yang tersedia untuk prosedur resolusi Bazel. Hal ini dilakukan dengan
mendaftarkan toolchain, baik dalam file MODULE.bazel
menggunakan
register_toolchains()
, atau dengan meneruskan label pada perintah
baris 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/...",
)
Saat menggunakan pola target untuk mendaftarkan toolchain, urutan di mana setiap toolchain yang didaftarkan ditentukan oleh aturan berikut:
- Toolchain yang ditentukan dalam sub-paket dari suatu paket didaftarkan sebelum toolchain yang ditentukan dalam paket itu sendiri.
- Di dalam sebuah paket, toolchain didaftarkan dalam urutan leksikografis nama mereka.
Sekarang, saat Anda membangun target yang bergantung pada tipe toolchain, akan dipilih berdasarkan platform target dan eksekusi.
# 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
sedang dibangun dengan platform yang
memiliki @platforms//os:linux
sehingga me-resolve
//bar_tools:toolchain_type
referensi 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 menentukan dependensi toolchain konkret target. Prosedurnya adalah masukkan 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 eksekusi yang dipilih platform untuk target saat ini.
Platform eksekusi dan toolchain yang tersedia dikumpulkan dari
grafik dependensi eksternal melalui
register_execution_platforms
dan
register_toolchains
panggilan masuk
MODULE.bazel
.
Platform eksekusi dan toolchain tambahan juga dapat ditetapkan di
baris perintah melalui
--extra_execution_platforms
dan
--extra_toolchains
.
Platform host otomatis disertakan sebagai platform eksekusi yang tersedia.
Platform dan toolchain yang tersedia dilacak sebagai daftar berurutan untuk determinisme,
dengan preferensi yang diberikan pada
item-item sebelumnya dalam daftar.
Set toolchain yang tersedia, sesuai 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 eksternal transitif grafik dependensi, dalam urutan berikut: (Di dalam grafik ini, baris pertama toolchain tersebut memiliki prioritas tertinggi.)- Toolchain yang didaftarkan oleh modul root (seperti dalam
MODULE.bazel
di root workspace); - Toolchain yang terdaftar dalam file
WORKSPACE
pengguna, termasuk di makro dipanggil dari sana; - Toolchain yang didaftarkan oleh modul non-root (seperti dalam dependensi yang ditentukan oleh modul root, dan dependensinya, dan seterusnya);
- Toolchain yang terdaftar di "akhiran WORKSPACE"; ini hanya digunakan oleh aturan asli tertentu yang dipaketkan dengan instalasi Bazel.
- Toolchain yang didaftarkan oleh modul root (seperti dalam
CATATAN: Target semu seperti :all
, :*
, dan
/...
dipesan oleh paket Bazel
mekanisme pemuatan, yang menggunakan
pengurutan leksikografis.
Langkah-langkah penyelesaiannya adalah sebagai berikut.
Klausa
target_compatible_with
atauexec_compatible_with
cocok dengan jika, untuk setiapconstraint_value
dalam daftarnya, platform juga memiliki bahwaconstraint_value
(baik secara eksplisit maupun sebagai default).Jika platform memiliki
constraint_value
dariconstraint_setting
yang tidak yang direferensikan oleh klausa, hal ini tidak memengaruhi pencocokan.Jika target yang dibuat menentukan Atribut
exec_compatible_with
(atau definisi aturannya menentukan argumenexec_compatible_with
), daftar platform eksekusi yang tersedia difilter untuk menghapus yang tidak sesuai dengan batasan eksekusi.Daftar toolchain yang tersedia difilter untuk menghapus toolchain menentukan
target_settings
yang tidak cocok dengan konfigurasi saat ini.Untuk setiap platform eksekusi yang tersedia, Anda mengaitkan setiap jenis toolchain dengan toolchain pertama yang tersedia, jika ada, yang kompatibel dengan eksekusi ini platform dan platform target.
Semua platform eksekusi yang gagal menemukan toolchain wajib yang kompatibel untuk salah satu jenis toolchainnya dikesampingkan. Dari platform yang tersisa, yang pertama menjadi platform eksekusi target saat ini, dan objek terkait toolchain (jika ada) menjadi dependensi target.
Platform eksekusi yang dipilih digunakan untuk menjalankan semua tindakan yang yang dihasilkan.
Jika target yang sama dapat dibuat dalam beberapa konfigurasi (seperti untuk CPU yang berbeda) dalam build yang sama, prosedur resolusi diterapkan secara independen ke setiap versi target.
Jika aturan menggunakan grup eksekusi, setiap eksekusi grup melakukan resolusi toolchain secara terpisah, dan masing-masing memiliki eksekusinya sendiri dan toolchain.
Men-debug toolchain
Jika Anda menambahkan dukungan toolchain ke aturan yang sudah ada, gunakan
--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 membuat output semua informasi. Bazel akan mengeluarkan nama
dari {i>toolbar<i} yang
pemeriksaan dan pengabaian selama proses resolusi.
Jika Anda ingin melihat dependensi cquery
mana yang berasal dari toolchain
resolusi, 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