Bu sayfada, kural yazarlarının kural mantıklarını platform tabanlı araç seçiminden ayırmalarını sağlayan araç zinciri çerçevesi açıklanmaktadır. Devam etmeden önce kurallar ve platformlar sayfalarını okumanız önerilir. Bu sayfada, araç zincirlerinin neden gerekli olduğu, nasıl tanımlanıp kullanılacağı ve Bazel'in platform kısıtlamalarına göre nasıl uygun bir araç zinciri seçtiği açıklanmaktadır.
Motivasyon
Öncelikle araç zincirlerinin çözmek üzere tasarlandığı sorunu inceleyelim. "bar" programlama dilini desteklemek için kurallar yazdığınızı varsayalım. bar_binary
kuralınız, çalışma alanınızda başka bir hedef olarak oluşturulmuş barc
derleyiciyi kullanarak *.bar
dosyalarını derler. bar_binary
Hedef yazan kullanıcıların derleyiciye bağımlılığı belirtmesi gerekmediğinden, bu bağımlılığı kural tanımına özel özellik olarak ekleyerek dolaylı bir bağımlılık haline getirirsiniz.
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
hedefi için bir bağımlılık olduğundan herhangi bir bar_binary
hedefinden önce oluşturulacak. Diğer tüm özellikler gibi kuralın uygulama işlevi 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, derleyici etiketinin bar_binary
koduna gömülmesidir. Bununla birlikte farklı hedefler, hangi platform için ve hangi platformda derlendiklerine bağlı olarak farklı derleyicilere ihtiyaç duyabilir. Buna sırasıyla hedef platform ve yürütme platformu denir. Ayrıca, kuralın yazarı mevcut tüm araçları ve platformları bilmeyebilir. Bu nedenle, bunları kuralın tanımına sabit kod olarak eklemek uygun değildir.
İdeal olmayan bir çözüm, _compiler
özelliğini gizli hâle getirerek iş yükünü kullanıcılara aktarmaktır. Ardından, belirli bir platform için oluşturulacak tekil hedefler sabit kodlanabilir.
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",
)
select
kullanarak compiler
'yi platforma göre seçerek 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 çok yorucu ve her bar_binary
kullanıcısından sormanız gereken biraz fazla bir şey var.
Bu stil, çalışma alanında tutarlı bir şekilde kullanılmazsa tek bir platformda sorunsuz çalışan ancak çok platformlu senaryolara genişletildiğinde başarısız olan derlemelere yol açar. Ayrıca, mevcut kuralları veya hedefleri değiştirmeden yeni platformlar ve derleyiciler için destek ekleme sorununu da çözmez.
Araç zinciri çerçevesi, fazladan bir dolaylı yönlendirme ekleyerek bu sorunu çözer. Temel olarak, kuralınızın bir hedef ailesinin bir üyesine (bir araç zinciri türü) soyut bir bağımlılığı olduğunu belirtirsiniz ve Bazel, geçerli platform kısıtlamalarına göre bunu otomatik olarak belirli bir hedefe (bir araç zinciri) çözer. Kural yazarının veya hedef yazarın, mevcut platform ve araç zincirlerinin tamamını bilmesi gerekmez.
Araç zincirlerini kullanan kurallar yazma
Araç zinciri çerçevesi kapsamında, kuralların doğrudan araçlara bağlı olması yerine araç zinciri türlerine bağlıdır. Araç zinciri türü, farklı platformlarda aynı rolü üstlenen bir araç sınıfını temsil eden basit bir hedeftir. Örneğin, bar derleyicisini temsil eden bir tür tanımlayabilirsiniz:
# 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ümdeki kural tanımı, derleyiciyi özellik olarak almak yerine bir //bar_tools:toolchain_type
araç zinciri kullandığını beyan edecek şekilde değiştirilir.
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 anahtar olarak araç zinciri türünü kullanarak bu bağımlılığa ctx.attr
yerine ctx.toolchains
altından erişiyor.
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'in 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ı bulunacak şekilde tanımlanır.
Bazel'in araç zincirlerini hedeflere çözümleme prosedürü aşağıda açıklanmıştır. Aday araç zincirlerinin tamamı değil, yalnızca çözülmüş araç zinciri hedefi bar_binary
hedefinin bağımlılığı haline getirilir.
Zorunlu ve İsteğe Bağlı Araç Zincirleri
Varsayılan olarak, bir kural toolchain türü bağımlılığını çıplak etiket kullanarak ifade ettiğinde (yukarıda gösterildiği gibi) toolchain türü zorunlu olarak kabul edilir. Bazel, zorunlu bir araç zinciri türü için eşleşen bir araç zinciri bulamıyorsa (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ığı belirtebilirsiniz:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
İsteğe bağlı bir araç zinciri türü çözülemediğinde analiz devam eder ve ctx.toolchains["//bar_tools:toolchain_type"]
sonucu None
olur.
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ırıp eşleştirebilirsiniz. Ancak aynı araç zinciri türü birden çok kez listelenirse zorunlu olanın isteğe bağlı olandan daha katı olduğu en katı sürüm kullanılır.
Araç zincirlerini kullanan yazma özellikleri
Yönleri, kurallarla aynı toolchain API'sine erişebilir: Gerekli toolchain türlerini tanımlayabilir, bağlam üzerinden toolchain'lere erişebilir ve toolchain'i kullanarak yeni işlemler oluşturmak için bunları 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ü için bazı araç zincirlerini tanımlamak üzere üç şeye ihtiyacınız vardır:
Aracın veya araç paketinin türünü temsil eden dile özgü bir kural. Kural olarak bu kuralın adının sonunda "_toolchain" bulunur.
- Not:
\_toolchain
kuralı herhangi bir derleme işlemi oluşturamaz. Bunun yerine, diğer kurallardan yapıları toplar ve araç zincirini kullanan kurala yönlendirir. Bu kural, tüm derleme işlemlerini oluşturmaktan sorumludur.
- Not:
Bu kural türünün, farklı platformlar için aracın veya araç paketinin sürümlerini temsil eden çeşitli hedefleri.
Bu tür her hedef için, araç zinciri çerçevesi tarafından kullanılan meta verileri sağlamak üzere genel
toolchain
kuralının ilişkili bir hedefi. Butoolchain
hedefi, bu araç zinciriyle ilişkilitoolchain_type
'ı da ifade eder. Diğer bir deyişle, belirli bir_toolchain
kuralı herhangi birtoolchain_type
ile ve yalnızca bu_toolchain
kuralını kullanantoolchain
örneğinde kural birtoolchain_type
ile ilişkilendirilebilir.
Devam eden örneğimizde, bar_toolchain
kuralının tanımı aşağıda verilmiştir. Örneğimizde yalnızca bir derleyici var ancak bunun altında bir bağlayıcı gibi diğer araçlar da 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, 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 olur. ToolchainInfo
, struct
gibi, rastgele alan-değer çiftleri barındırabilir. ToolchainInfo
alanına tam olarak hangi alanların eklendiğinin spesifikasyonu, araç zinciri türünde açıkça belirtilmelidir. Bu örnekte, yukarıda tanımlanan şemayı yeniden kullanmak için değerler bir BarcInfo
nesnesine sarmalanarak döndürülür. Bu stil, doğrulama ve kod 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ımları oluşturursunuz.
Bu tanımlar, dile özgü hedefleri araç zinciri türüne bağlar ve Bazel'e araç zincirinin belirli bir platform için ne zaman uygun olduğunu 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 ayrı paketlerde olmamasının bir nedeni yoktur.
Gerçek hayattan bir örnek için go_toolchain
bölümüne bakın.
Araç zincirleri ve yapılandırmalar
Kural yazarları için önemli bir soru, bir bar_toolchain
hedefi analiz edildiğinde hangi yapılandırmanın görüldüğü ve bağımlılıklar için hangi geçişlerin kullanılması gerektiğidir. Yukarıdaki örnekte dize özellikleri kullanılıyor ancak Bazel deposundaki diğer hedeflere bağlı olan daha karmaşık bir araç zinciri için ne olur?
bar_toolchain
işlevinin 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.
Araç zinciri çözümü aracılığıyla bir hedeften ("ana" öğe denir) araç zincirine olan bağımlılıkta, "araç zinciri geçişi" adı verilen özel bir yapılandırma geçişi kullanılır. Araç zinciri geçişi, yapılandırmayı aynı tutar ancak yürütme platformunu, araç zinciri ve üst öğe zinciri için aynı olmaya zorlar (aksi takdirde, araç zincirinin araç zinciri çözünürlüğü herhangi bir yürütme platformunu seçebilir ve üst öğe ile aynı olmayabilir). Bu, araç setinin tüm exec
bağımlılıklarının üst öğenin derleme işlemleri için de yürütülebilir olmasına olanak tanır. cfg =
"target"
kullanan (veya varsayılan olarak "target" olduğu için cfg
belirtmeyen) araç zincirinin bağımlılıklarından herhangi biri, üst öğeyle aynı hedef platform için derlenir. 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 eklemesine olanak tanır. Sistem kitaplıkları nihai yapıya bağlanır ve bu nedenle 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ışabilmesi gerekir.
Araç zincirleriyle kayıt yapma ve derleme
Bu noktada tüm yapı taşları birleştirilir ve sadece araç zincirlerini
Bazel'in çözümleme prosedüründe kullanılabilir hale getirmeniz gerekir. Bu işlem, register_toolchains()
kullanılarak MODULE.bazel
dosyasına veya --extra_toolchains
işareti kullanılarak komut satırına toolchain etiketleri geçirilerek yapılır.
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ıldığında, her bir araç zincirinin kaydedilme sırası aşağıdaki kurallara göre belirlenir:
- Bir paketin alt paketinde tanımlanan araç zincirleri, paketin kendisinde tanımlanan araç zincirlerinden önce kaydedilir.
- Bir paket içinde araç zincirleri, adlarının alfabetik sırasına göre kaydedilir.
Artık bir araç zinciri türüne bağlı bir hedef oluşturduğunuzda hedefe 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 çözümler.
Bu işlem sonucunda //bar_tools:barc_linux
oluşturulur ancak //bar_tools:barc_windows
oluşturulmaz.
Araç zinciri çözünürlüğü
Bazel'in araç zinciri çözümleme prosedürü, araç zincirleri kullanan her hedef için hedefin somut araç zinciri bağımlılıklarını belirler. Prosedür bir dizi gerekli araç zinciri türü, hedef platform, mevcut yürütme platformlarının listesi ve kullanılabilir araç zincirlerinin listesini alır. Çıktıları, her araç zinciri türü için seçili bir araç zinciri ve mevcut hedef için seçili bir yürütme platformudur.
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 harici 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.
Barındırma platformu, kullanılabilir bir yürütme platformu olarak otomatik olarak dahil edilir.
Kullanılabilir platformlar ve araç zincirleri, belirlenebilirlik için sıralı listeler olarak izlenir. Bu listelerde, listedeki daha önceki öğelere öncelik verilir.
Öncelik sırasına göre kullanılabilir araç zinciri grubu, --extra_toolchains
ve register_toolchains
'ten oluşturulur:
--extra_toolchains
kullanılarak kaydedilen araç zincirleri önce eklenir. (Bunlar arasında son araç zinciri en yüksek önceliğe sahiptir.)- Geçişli harici bağımlılık grafiğinde
register_toolchains
kullanılarak kaydedilen araç zincirleri, aşağıdaki sırayla gösterilir: (Bunlar içinde, ilk belirtilen araç zinciri en yüksek önceliğe sahiptir.)- Kök modül tarafından kaydedilen araç zincirleri (ör. Workspace kökündeki
MODULE.bazel
); - Kullanıcının
WORKSPACE
dosyasına kayıtlı araç zincirleri (buradan çağrılan tüm makrolar dahil); - Kök olmayan modüller tarafından kaydedilen araç zincirleri (ör. kök modül tarafından belirtilen bağımlılıklar ve bunların bağımlılıkları vb.);
- "WORKSPACE soneki"ne kayıtlı araç zincirleri; bu, yalnızca Bazel yüklemesiyle birlikte sunulan belirli yerel kurallar tarafından kullanılır.
- Kök modül tarafından kaydedilen araç zincirleri (ör. Workspace kökündeki
NOT: :all
, :*
ve /...
gibi sözde hedefler, Bazel'in alfabetik sıralama kullanan paket yükleme mekanizması tarafından sıralanır.
Çözüm adımları aşağıda verilmiştir.
Bir
target_compatible_with
veyaexec_compatible_with
yan tümcesi, listesindeki herconstraint_value
için platformda da buconstraint_value
varsa (açıkça veya varsayılan olarak) platformla eşleşir.Platformda,
constraint_setting
'lardan alınan veconstraint_value
'lere ait olan ancak ifadede referans verilmeyenconstraint_value
'ler varsa buconstraint_value
'ler eşleşmeyi etkilemez.Oluşturulan hedef
exec_compatible_with
özelliğini belirtiyorsa (veya kural tanımıexec_compatible_with
bağımsız değişkenini belirtiyorsa) kullanılabilir yürütme platformlarının listesi, yürütme kısıtlamalarıyla eşleşmeyen tüm platformları kaldırmak için filtrelenir.Mevcut yapılandırmayla eşleşmeyen
target_settings
belirten tüm araç zincirleri, kullanılabilir araç zincirleri listesinden filtrelenerek kaldırılır.Mevcut her yürütme platformu için her araç zinciri türünü, bu yürütme platformu ve hedef platformla uyumlu olan ilk kullanılabilir araç zinciriyle ilişkilendirirsiniz.
Zincirleme araç türü için uyumlu zorunlu bir zincirleme aracı bulamayan tüm 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ıkları olur.
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ı derleme içinde birden fazla yapılandırmada (ör. farklı CPU'lar için) derlenebildiği durumlarda, çözüm işlemi hedefin her sürümüne bağımsız olarak uygulanır.
Kural yürütme grupları kullanıyorsa her yürütme grubu araç zinciri çözümlemesini ayrı olarak gerçekleştirir ve her birinin kendi yürütme platformu ve araç zincirleri vardır.
Hata ayıklama araç zincirleri
Mevcut bir kurala araç zinciri desteği ekliyorsanız --toolchain_resolution_debug=regex
işaretini kullanın. İş akışı çözümü sırasında bu işaret, normal ifade değişkeniyle eşleşen iş akışı türleri veya hedef adlar için ayrıntılı çıkış sağlar. Tüm bilgileri çıkış olarak almak için .*
kullanabilirsiniz. Bazel, çözümleme işlemi sırasında kontrol edip atladığı araç zincirlerinin adlarını gösterir.
Araç zinciri çözümünden hangi cquery
bağımlılıklarının geldiğini görmek istiyorsanız cquery
'ın --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