Bu sayfada, kural yazarlarının kural mantıklarını platform tabanlı araç seçiminden ayırmalarına olanak tanıyan araç zinciri çerçevesi açıklanmaktadır. Devam etmeden önce kurallar ve platformlar sayfalarını okumanız önerilir. Bu sayfada, araç zincirlerine neden ihtiyaç duyulduğu, bunların nasıl tanımlanıp kullanılacağı ve Bazel'in platform kısıtlamalarına göre uygun bir araç zincirini nasıl seçtiği ele alınmaktadır.
Motivasyon
İlk olarak, araç zincirlerinin çözmek için tasarlandığı problemlere göz atalım. "bar" programlama dilini destekleyecek kurallar yazdığınızı varsayalım. bar_binary
kuralınız, çalışma alanınızda başka bir hedef olarak oluşturulmuş bir araç olan barc
derleyicisini kullanarak *.bar
dosyalarını derler. bar_binary
hedeflerini yazan kullanıcıların derleyicide bir bağımlılık belirtmek zorunda kalmaması gerektiğinden, derleyiciyi kural tanımına özel özellik olarak ekleyerek bunu örtülü bir bağımlılık haline getirebilirsiniz.
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
artık her bar_binary
hedefinin bağımlılığı olduğundan herhangi bir bar_binary
hedefinden önce oluşturulacak. Kuralın uygulama işlevi, diğer herhangi bir özellik
gibi bu değer tarafından erişilebilir:
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),
)
...
Buradaki sorun, derleyicinin etiketinin kodunun bar_binary
içine gömülmesidir. Ancak, farklı hedeflerin, hangi platform için ve hangi platform üzerinde oluşturulduklarına bağlı olarak farklı derleyicilere ihtiyacı olabilir. Bunlar, sırasıyla hedef platform ve yürütme platformu olarak adlandırılır. Dahası, kuralın yazarı, kullanılabilir tüm araçları ve platformları bile bilmez. Bu nedenle, bunları kuralın tanımına gömmek mümkün değildir.
İdeal olmayan bir çözüm, _compiler
özelliğini gizli olmayacak şekilde ayarlayarak yükü kullanıcılara taşımak olabilir. Sonrasında her bir platform için
hedefler koda gömülebilir.
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",
)
Platforma göre compiler
seçmek için select
kullanarak bu çözümü iyileştirebilirsiniz:
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",
}),
)
Ancak bu yorucu ve her bar_binary
kullanıcısından biraz istemesi gereken bir şey.
Bu stil, çalışma alanı boyunca tutarlı bir şekilde kullanılmazsa tek bir platformda iyi çalışan ancak çok platformlu senaryolara genişletildiğinde başarısız olan derlemeler ortaya çıkar. Ayrıca, mevcut kuralları veya hedefleri değiştirmeden yeni platformlar ve derleyiciler için destek ekleme sorununu da gidermiyor.
Araç zinciri çerçevesi, bu sorunu fazladan bir dolaylı yoldan ekleyerek çözer. Esas olarak, kuralınızın bir hedef ailesinin bazı üyesine soyut bir bağımlılığı olduğunu beyan edersiniz (araç zinciri türü) ve Bazel, geçerli platform kısıtlamalarına göre bunu otomatik olarak belirli bir hedefe (araç zinciri) çözer. Ne kuralı yazanın ne de hedef yazarın, kullanılabilir platform ve araç zincirlerinin tamamını bilmesine gerek yoktur.
Araç zincirlerini kullanan kurallar yazma
Araç zinciri çerçevesi kapsamında, kurallar doğrudan araçlara bağlı olmak yerine araç zinciri türlerine bağımlıdır. Araç zinciri türü, farklı platformlar için aynı rolü sunan bir araç sınıfını temsil eden basit bir hedeftir. Örneğin, çubuk derleyiciyi temsil eden bir tür bildirebilirsiniz:
# 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")
Önceki bölümde yer alan kural tanımı, derleyiciyi özellik olarak almak yerine bir //bar_tools:toolchain_type
araç zincirini kullandığını bildirecek şekilde değiştirmiştir.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
Uygulama işlevi artık bu bağımlılığa ctx.attr
yerine ctx.toolchains
altında erişir ve anahtar olarak araç zinciri türünü kullanır.
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"]
, Bazel'ın araç zinciri bağımlılığını çözdüğü hedefin ToolchainInfo
sağlayıcısını döndürür. ToolchainInfo
nesnesinin alanları, temel aracın kuralı tarafından ayarlanır. Sonraki bölümde bu kural, BarcInfo
nesnesini sarmalayan bir barcinfo
alanı olacak şekilde tanımlanır.
Bazel'ın araç zincirlerini hedeflere dönüştürme prosedürü aşağıda açıklanmıştır. Yalnızca çözümlenen araç zinciri hedefi, aday araç zincirlerinden oluşan alanın tamamı değil, bar_binary
hedefinin bağımlılığı haline getirilir.
Zorunlu ve İsteğe Bağlı Araç Zincirleri
Varsayılan olarak, bir kural çıplak etiket kullanarak (yukarıda gösterildiği gibi) araç zinciri türü bağımlılığı ifade ettiğinde araç zinciri türünün zorunlu olduğu kabul edilir. Bazel, zorunlu bir araç zinciri türü için eşleşen bir araç zinciri bulamazsa (aşağıdaki Araç zinciri çözümü bölümüne bakın) bu bir hatadır ve analiz durdurulur.
Bunun yerine, aşağıdaki gibi isteğe bağlı bir araç zinciri türü bağımlılık beyan etmek mümkündür:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
İsteğe bağlı bir araç zinciri türü çözümlenemezse analiz devam eder ve ctx.toolchains["//bar_tools:toolchain_type"]
sonucunun None
sonucunu verir.
config_common.toolchain_type
işlevi varsayılan olarak zorunludur.
Aşağıdaki formlar kullanılabilir:
- Zorunlu araç zinciri türleri:
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)]
- İsteğe bağlı araç zinciri türleri:
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),
],
)
Ayrıca, aynı kuralda formları karıştırabilir ve eşleştirebilirsiniz. Bununla birlikte, aynı araç zinciri türü birden fazla kez listelenirse en katı olan sürümü alır. Zorunluluk, isteğe bağlı olandan daha katıdır.
Araç zincirlerini kullanan özellikler yazma
Özellikler, kurallarla aynı araç zinciri API'sine erişebilir: Gerekli araç zinciri türlerini tanımlayabilir, araç zincirlerine bağlam üzerinden erişebilir ve bunları, araç zincirini kullanarak yeni eylemler oluşturmak için kullanabilirsiniz.
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 []
Araç zincirlerini tanımlama
Belirli bir araç zinciri türüne yönelik bazı araç zincirlerini tanımlamak için üç şeye ihtiyacınız vardır:
Araç veya araç paketi türünü temsil eden dile özgü bir kural. Kural olarak, bu kuralın adının sonunda "_araç zinciri" bulunur.
- Not:
\_toolchain
kuralı herhangi bir derleme işlemi oluşturamaz. Bunun yerine, diğer kurallardaki yapıları toplayıp araç zincirini kullanan kurala iletir. Bu kural, tüm derleme işlemlerini oluşturmaktan sorumludur.
- Not:
Bu kural türünün, aracın veya araç paketinin farklı platformlar için sürümlerini temsil eden çeşitli hedefleri.
Araç zinciri çerçevesi tarafından kullanılan meta verileri sağlamak amacıyla, bu türden her hedef için genel
toolchain
kuralının ilişkilendirilmiş bir hedefi. Butoolchain
hedefi, bu araç zinciriyle ilişkilendirilentoolchain_type
'yi de ifade eder. Bu, belirli bir_toolchain
kuralının herhangi birtoolchain_type
ile ilişkilendirilebileceği ve yalnızca bu_toolchain
kuralını kullanantoolchain
örneğinde kuralın birtoolchain_type
ile ilişkilendirilebileceği anlamına gelir.
Çalışan örneğimizde bar_toolchain
kuralının tanımı aşağıda verilmiştir. Örneğimizde yalnızca bir derleyici vardır, ancak bağlayıcı gibi diğer araçlar da onun altında gruplandırılabilir.
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(),
},
)
Kural bir ToolchainInfo
sağlayıcısı döndürmelidir. Bu sağlayıcı, tüketen kuralın ctx.toolchains
ve araç zinciri türünün etiketini kullanarak aldığı nesne haline gelir. ToolchainInfo
, struct
gibi rastgele alan-değer çiftleri içerebilir. ToolchainInfo
öğesine tam olarak hangi alanların ekleneceğiyle ilgili spesifikasyon, araç zinciri türünde açıkça belgelenmelidir. Bu örnekte, yukarıda tanımlanan şemayı yeniden kullanmak için bir BarcInfo
nesnesi içine sarmalanmış değerler döndürülür; bu stil, doğrulama ve kodun yeniden kullanımı için yararlı olabilir.
Artık belirli barc
derleyicileri için hedefler tanımlayabilirsiniz.
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",
)
Son olarak, iki bar_toolchain
hedefi için toolchain
tanım oluşturuyorsunuz.
Bu tanımlar, dile özgü hedefleri araç zinciri türüne bağlar ve araç zincirinin belirli bir platform için ne zaman uygun olduğunu Bazel'e bildiren kısıtlama bilgilerini sağlar.
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",
)
Yukarıdaki göreli yol söz diziminin kullanılması, bu tanımların tümünün aynı pakette olduğunu gösterir ancak araç zinciri türünün, dile özgü araç zinciri hedeflerinin ve toolchain
tanım hedeflerinin tümünün ayrı paketlerde olmamasının hiçbir nedeni yoktur.
Gerçek hayattan bir örnek için go_toolchain
sayfasını inceleyin.
Araç zincirleri ve yapılandırmalar
Kural yazarları için en önemli sorulardan biri, bir bar_toolchain
hedefi analiz edildiğinde hangi yapılandırmayı gördüğü ve bağımlılıklar için hangi geçişlerin kullanılması gerektiğidir? Yukarıdaki örnekte dize öznitelikleri kullanılmış ancak Bazel deposundaki diğer hedeflere bağlı olan daha karmaşık bir araç zinciri için ne olur?
bar_toolchain
öğesinin daha karmaşık bir sürümünü inceleyelim:
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(),
},
)
attr.label
kullanımı standart bir kuralla aynıdır ancak cfg
parametresinin anlamı biraz farklıdır.
Bir hedeften ("ana öğe" denir) araç zinciri çözümlemesi aracılığıyla bir araç zincirine olan bağımlılık, "araç zinciri geçişi" adı verilen özel bir yapılandırma geçişi kullanır. Araç zinciri geçişi, yapılandırma platformunu aynı tutar ancak yürütme platformunu araç zinciriyle aynı olmaya zorlar (aksi takdirde, araç zincirinin araç zinciri çözümü herhangi bir yürütme platformunu seçebilir ve üst platformdakiyle aynı olmayabilir). Bu, araç zincirinin tüm exec
bağımlılıklarının, üst öğenin derleme işlemleri için de yürütülebilir olmasına izin verir. Araç zincirinin cfg =
"target"
kullanan (veya "target" varsayılan olduğu için cfg
değerini belirtmeyen) tüm bağımlılıkları üst öğeyle aynı hedef platform için oluşturulur. Bu, araç zinciri kurallarının hem kitaplıkları (yukarıdaki system_lib
özelliği) hem de araçları (compiler
özelliği) bunlara ihtiyaç duyan derleme kurallarına katkıda bulunmasına olanak tanır. Sistem kitaplıkları nihai yapıya bağlıdır. Bu nedenle, bunların aynı platform için derlenmesi gerekir. Derleyici ise derleme sırasında çağrılan bir araçtır ve yürütme platformunda çalışabilmelidir.
Araç zincirleri ile kaydolma ve derleme
Bu noktada tüm yapı taşları derlenir ve tek yapmanız gereken
araç zincirlerini Bazel'ın çözümleme prosedürü için kullanılabilir hale getirmektir. Bu işlem, araç zincirini register_toolchains()
kullanarak bir MODULE.bazel
dosyasına kaydederek veya --extra_toolchains
işaretini kullanarak araç zinciri etiketlerini komut satırına ileterek gerçekleştirilir.
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/...",
)
Araç zincirlerini kaydetmek için hedef kalıpları kullanılırken, her bir araç zincirinin kaydedilme sırası aşağıdaki kurallar tarafından belirlenir:
- Bir paketin alt paketinde tanımlanan araç zincirleri, paketin kendisinde tanımlanan araç zincirlerinden önce kaydedilir.
- Araç zincirleri bir paket içinde, adlarının sözlük sıralamasına göre kaydedilir.
Artık bir araç zinciri türüne bağlı bir hedef oluşturduğunuzda, hedef ve yürütme platformlarına göre uygun bir araç zinciri seçilir.
# 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, //my_pkg:my_bar_binary
öğesinin @platforms//os:linux
içeren bir platformla oluşturulduğunu görür ve bu nedenle //bar_tools:toolchain_type
referansını //bar_tools:barc_linux_toolchain
öğesine çözer.
Bu işlem sonucunda //bar_tools:barc_linux
oluşturulur, ancak //bar_tools:barc_windows
oluşturulmaz.
Araç zinciri çözünürlüğü
Araç zincirlerini kullanan her hedef için Bazel'in araç zinciri çözüm prosedürü, hedefin beton araç zinciri bağımlılıklarını belirler. Prosedür; gerekli araç zinciri türlerini, hedef platformu, mevcut yürütme platformlarının ve kullanılabilir araç zincirlerinin listesini gösterir. Çıkışları, her araç zinciri türü için seçilmiş bir araç zincirinin yanı sıra geçerli hedef için seçili bir yürütme platformu olur.
Kullanılabilir yürütme platformları ve araç zincirleri, MODULE.bazel
dosyalarındaki register_execution_platforms
ve register_toolchains
çağrıları aracılığıyla dış bağımlılık grafiğinden toplanır.
Komut satırında --extra_execution_platforms
ve --extra_toolchains
aracılığıyla ek yürütme platformları ve araç zincirleri de belirtilebilir.
Ana makine platformu, otomatik olarak kullanılabilir bir yürütme platformu olarak eklenir.
Kullanılabilir platformlar ve araç zincirleri, determinizm için sıralı listeler olarak izlenir ve listede önceki öğeler tercih edilir.
Mevcut araç zincirleri grubu, öncelik sırasına göre --extra_toolchains
ve register_toolchains
temel alınarak oluşturulur:
--extra_toolchains
kullanılarak kaydedilen araç zincirleri önce eklenir. (Bunlar içinde, son araç zinciri en yüksek önceliğe sahiptir.)- Geçişli dış bağımlılık grafiğinde
register_toolchains
kullanılarak kaydedilen araç zincirleri, aşağıdaki sırayla gösterilir: (Bunların içinde, ilk araç zinciri en yüksek önceliğe sahiptir.)- Kök modül tarafından kaydedilen araç zincirleri (çalışma alanı kökündeki
MODULE.bazel
olduğu gibi); - Kullanıcının
WORKSPACE
dosyasında çağrılan makrolar dahil olmak üzere dosyaya kayıtlı araç zincirleri; - Kök olmayan modüller tarafından kaydedilen araç zincirleri (kök modül tarafından belirtilen bağımlılıklar ve bunların bağımlılıkları vb.);
- "WORKSPACE son ekine" kayıtlı araç zincirleridir. Bu son ek, yalnızca Bazel kurulumuyla birlikte sunulan belirli yerel kurallar tarafından kullanılır.
- Kök modül tarafından kaydedilen araç zincirleri (çalışma alanı kökündeki
NOT: :all
, :*
ve /...
gibi sözde hedefler Bazel'in sözlük sıralaması kullanan paket yükleme mekanizmasına göre sıralanır.
Çözüm adımları aşağıdaki gibidir.
target_compatible_with
veyaexec_compatible_with
ifadesi, listesindeki herconstraint_value
için platform aynı zamandaconstraint_value
değerine de sahipse (açıkça veya varsayılan olarak) bir platformla eşleşir.Platformda, bu ifadede referans verilmeyen
constraint_setting
kaynaklıconstraint_value
öğeleri varsa bunlar eşleşmeyi etkilemez.Derlenen hedef
exec_compatible_with
özelliğini belirtiyorsa (veya kural tanımıexec_compatible_with
bağımsız değişkenini belirtiyorsa) mevcut yürütme platformlarının listesi, yürütme kısıtlamalarıyla eşleşmeyenleri kaldırmak için filtrelenir.target_settings
belirten ve mevcut yapılandırmayla eşleşmeyen tüm araç zincirlerini kaldırmak için mevcut araç zincirlerinin listesi filtrelenir.Mevcut her yürütme platformu için her bir araç zinciri türünü, bu yürütme platformu ve hedef platformla uyumlu olan mevcut ilk araç zinciriyle (varsa) ilişkilendirirsiniz.
Araç zinciri türlerinden biri için uyumlu bir zorunlu araç zinciri bulamayan yürütme platformları dikkate alınmaz. Kalan platformlardan ilki, mevcut hedefin yürütme platformu olur ve ilişkili araç zincirleri (varsa) hedefin bağımlılığı haline gelir.
Seçilen yürütme platformu, hedefin oluşturduğu tüm işlemleri çalıştırmak için kullanılır.
Aynı hedefin aynı yapı içinde birden fazla yapılandırmada (farklı CPU'lar gibi) oluşturulabildiği durumlarda çözümleme prosedürü, hedefin her sürümüne bağımsız olarak uygulanır.
Kural yürütme gruplarını kullanıyorsa her yürütme grubu araç zinciri çözünürlüğünü ayrı olarak gerçekleştirir ve her birinin kendi yürütme platformu ve araç zincirleri olur.
Hata ayıklama araç zincirleri
Mevcut bir kurala araç zinciri desteği ekliyorsanız --toolchain_resolution_debug=regex
işaretini kullanın. Araç zinciri çözümlemesi sırasında işaret, normal ifade değişkeniyle eşleşen araç zinciri türleri veya hedef adları için ayrıntılı çıktı sağlar. Tüm bilgilerin çıkışını .*
ile kullanabilirsiniz. Bazel, çözüm sürecinde kontrol ettiği ve atladığı
araç zincirlerinin adlarını çıkartır.
Hangi cquery
bağımlılıklarının araç zinciri çözümünden geldiğini görmek istiyorsanız cquery
--transitions
işaretini kullanın:
# 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