Alet Zincirleri

Sorun bildirme Kaynağı görüntüleme Nightly · 7.4 .

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_binaryKuralınız, *.bar dosyalarını barc derleyiciyi kullanarak derleyecektir. Bu derleyici, çalışma alanınızda başka bir hedef olarak oluşturulmuş bir araçtır. bar_binary hedeflerini yazan kullanıcıların derleyiciye bağımlılık belirtmesi gerekmeyeceğinden, bunu kural tanımına gizli bir özellik olarak ekleyerek bunu örtülü 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 hedefinin bağımlısıdır. Bu nedenle, herhangi bir bar_binary hedefinden önce oluşturulur. 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, derleyicinin etiketinin bar_binary içine kodlanmış olmasıdır. Ancak farklı hedeflerin, hangi platform için ve hangi platformda derlendiklerine bağlı olarak farklı derleyicilere ihtiyacı olabilir. Bunlara 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.

_compiler özelliğini herkese açık hale getirerek yükü kullanıcılara aktarmak ideal bir çözüm değildir. Daha sonra tek tek hedefler, bir platform veya başka bir platform için derlemek üzere 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, her bar_binary kullanıcısından istenecek sıkıcı ve biraz fazla bir işlemdir. 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, ek bir yön düzeyi 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. Ne kural yazarının ne de hedef yazarın kullanılabilir platformların 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 belirlenir. Sonraki bölümde bu kural, bir BarcInfo nesnesini sarmalayan bir barcinfo alanı olacak ş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çlar 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. Bununla birlikte, aynı araç zinciri türü birden çok kez listelenirse en katı olan sürümü alır. Burada, zorunlu olan sürüm isteğe bağlıdan daha katıdı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 araç zinciri tanımlamak üzere üç şeye ihtiyacınız vardır:

  1. Araç veya araç paketi türünü temsil eden dile özgü bir kural. Kural olarak bu kuralın adının sonunda "_toolchain" bulunur.

    1. Not: \_toolchain kuralı herhangi bir derleme işlemi oluşturamaz. Bunun yerine, diğer kurallardan yapı taşlarını toplar ve bunları araç zincirini kullanan kurala iletir. Bu kural, tüm derleme işlemlerini oluşturmaktan sorumludur.
  2. Bu kural türünün, farklı platformlar için aracın veya araç paketinin sürümlerini temsil eden çeşitli hedefleri.

  3. 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. Bu toolchain hedefi, bu araç zinciriyle ilişkili toolchain_type'ı da ifade eder. Bu, belirli bir _toolchain kuralının herhangi bir toolchain_type ile ilişkilendirilebileceği ve kuralın yalnızca bu _toolchain kuralını kullanan bir toolchain örneğinde toolchain_type ile ilişkilendirileceği anlamına gelir.

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 şemanın yeniden kullanılması için bir BarcInfo nesnesine 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ımları oluşturursunuz. Bu tanımlar, dile özgü hedefleri araç zinciri türüne bağlar ve Bazel'a 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çlar zincirleri ve yapılandırmalar

Kural yazarları için önemli bir soru da şu, 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 ö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.

Bir hedeften ("üst öğe" olarak adlandırılır) araç zincirine olan bağımlılık, araç zinciri çözümü aracılığıyla "araç zinciri geçişi" olarak adlandırılan özel bir yapılandırma geçişini kullanır. Araç zinciri geçişi, yürütme platformunu araç zinciri için üst öğeyle aynı olmaya zorlaması dışında yapılandırmayı aynı tutar (aksi takdirde, araç zinciri için araç zinciri çözünürlüğü herhangi bir yürütme platformu seçebilir ve üst öğeyle 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. Araç zincirinin cfg = "target" kullanan (veya "target" varsayılan olduğundan cfg belirtmeyen) bağımlılıklarından herhangi biri, üst öğeyle aynı hedef platform için oluşturulur. Bu sayede araç zinciri kuralları, ihtiyaç duyulan kitaplıkları (yukarıdaki system_lib özelliği) ve araçları (compiler özelliği) derleme kurallarına katkıda bulunabilir. 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 kaydolma ve derleme

Bu noktada tüm yapı taşları birleştirilmiştir. Artık araç zincirlerini Bazel'in çözümleme işlemine sunmanız yeterlidir. Bu işlem, register_toolchains() kullanılarak WORKSPACE 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.
  • Paket içinde araç zincirleri, adlarının sözlüksel 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'ün @platforms//os:linux içeren bir platformla derlendiğini görür ve bu nedenle //bar_tools:toolchain_type referansını //bar_tools:barc_linux_toolchain olarak çözer. Bu işlem sonucunda //bar_tools:barc_linux oluşturulur ancak //bar_tools:barc_windows oluşturulmaz.

Araç zinciri çözünürlüğü

Araç zincirleri kullanan her hedef için Bazel'in araç zinciri çözümleme prosedürü, hedefin somut araç zinciri bağımlılıklarını belirler. Prosedür, gerekli araç zinciri türleri grubunu, hedef platformu, kullanılabilir yürütme platformlarının listesini ve kullanılabilir araç zincirlerinin listesini giriş olarak 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, register_execution_platforms ve register_toolchains aracılığıyla WORKSPACE dosyasından toplanır. --extra_execution_platforms ve --extra_toolchains aracılığıyla komut satırında 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, deterministik için sıralı listeler olarak izlenir ve listede önceki öğelere öncelik verilir.

Kullanılabilir araç zincirleri, öncelik sırasına göre --extra_toolchains ve register_toolchains öğelerinden oluşturulur:

  1. --extra_toolchains kullanılarak kaydedilen araç zincirleri önce eklenir.
    1. Bunlar arasında son araç zinciri en yüksek önceliğe sahiptir.
  2. register_toolchains kullanılarak kaydedilen araç zincirleri
    1. Bunlar arasında en yüksek önceliğe sahip olan, bahsedilen ilk araç zinciridir.

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 açıklanmıştır.

  1. target_compatible_with veya exec_compatible_with ifadesi, listedeki her constraint_value için platformun constraint_value (açık veya varsayılan olarak) içermesi durumunda bir platformla eşleşir.

    Platformda, constraint_setting'lardan alınan ve constraint_value'lere ait olan ancak ifadede referans verilmeyen constraint_value'ler varsa bu constraint_value'ler eşleşmeyi etkilemez.

  2. 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.

  3. 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 (varsa) ilişkilendirirsiniz.

  4. Zincirleme araç türü için uyumlu zorunlu bir zincirleme aracı bulamayan tüm yürütme platformları dikkate alınmaz. Diğer platformlardan ilki, mevcut hedefin yürütme platformu haline gelir ve onunla ilişkili araç zincirleri (varsa) hedefin bağımlılıkları 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ı 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ı ayrı gerçekleştirir ve her birinin kendi yürütme platformu ve araç zincirleri vardır.

Araç zincirlerinde hata ayıklama

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ı çıkış sağlar. Tüm bilgilerin çıkışını sağlamak için .* kullanabilirsiniz. Bazel, çözümleme işlemi sırasında kontrol edip atladığı araç zincirlerinin adlarını gösterir.

Hangi cquery bağımlılıklarının araç zinciri çözünürlüğü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