Alet Zincirleri

Sorun bildirin Kaynağı göster

Bu sayfada, kural yazarlarının kural mantığını platform tabanlı araç seçiminden ayırmalarının bir yolu olan araç zinciri çerçevesi açıklanmaktadır. Devam etmeden önce kurallar ve platformlar sayfalarının okunması önerilir. Bu sayfada araç zincirlerinin neden gerekli olduğu, bunların nasıl tanımlanıp kullanılacağı ve Bazel'in platform kısıtlamalarına dayanarak uygun araç zincirini nasıl seçtiği açıklanmaktadır.

Motivasyon

Öncelikle araç zincirlerinin çözmek için tasarlandığı soruna bakalı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ş barc derleyiciyi kullanarak *.bar dosyalarını derler. 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 hedefi için bir bağımlılık olduğundan herhangi bir bar_binary hedefinden önce oluşturulacak. Kuralın uygulama işleviyle, tıpkı diğer özellikler gibi 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. Dahası, kural yazarı mevcut tüm araçları ve platformları bile bilemez. Bu nedenle, kuralların tanımına bu araçların kod olarak gömülmesi mümkün değildir.

İdeal olmayan bir çözüm, _compiler özelliğini gizli hâle getirerek iş yükünü kullanıcılara aktarmaktır. 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 compiler 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ı boyunca tutarlı bir şekilde kullanılmazsa tek bir platformda sorunsuz çalışan ancak çoklu platform senaryolarına uygulandığında 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 ele almaz.

Araç zinciri çerçevesi, fazladan bir dolaylı yönlendirme ekleyerek bu sorunu çözer. Esasen, kuralınızın hedef ailesinin bir üyesine (araç zinciri türü) soyut bağımlılığı olduğunu beyan edersiniz. Bazel, geçerli platform kısıtlamalarına göre bunu belirli bir hedefe (araç zinciri) otomatik olarak çözer. Ne kural yazarının ne de hedef yazarın kullanılabilir platformların ve araç zincirlerinin tamamını bilmesi gerekmez.

Araç zincirleri 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ü yapan bir araç sınıfını temsil eden basit bir hedeftir. Örneğin, çubuk derleyiciyi 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ç zincirini kullandığını bildirecek şekilde değiştirilmiş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şiyor ve anahtar olarak araç zinciri türünü kullanıyor.

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'ın araç zincirlerini hedeflere çözümleme prosedürü aşağıda açıklanmıştır. Aslında aday araç zincirlerinin tüm alanının değil, yalnızca çözümlenen araç zinciri hedefi aslında bar_binary hedefinin bağımlılığı haline gelir.

Zorunlu ve İsteğe Bağlı Araç Zincirleri

Varsayılan olarak, bir kural sade bir etiket kullanarak (yukarıda gösterildiği gibi) araç zinciri türü bağımlılığını 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ünürlüğü'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ığı 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"] 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 özellikleri yazma

Yönler, kurallarla aynı araç zinciri API'sine erişebilir: Gerekli araç zinciri türlerini tanımlayabilir, bağlam üzerinden araç zincirlerine erişebilir ve araç zincirini 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. 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.

    1. Not: \_toolchain kuralı 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.
  2. Aracın veya araç paketinin farklı platformlara yönelik sürümlerini temsil eden, bu kural türündeki birkaç hedef.

  3. Araç zinciri çerçevesi tarafından kullanılan meta verileri sağlamak için bu tür her bir hedef için genel toolchain kuralının ilişkili hedefi. Bu toolchain hedefi, bu araç zinciriyle ilişkili toolchain_type anlamına da gelir. Diğer bir deyişle, belirli bir _toolchain kuralı herhangi bir toolchain_type ile ve yalnızca bu _toolchain kuralını kullanan toolchain örneğinde kural bir toolchain_type ile ilişkilendirilebilir.

Çalışan örneğimizde, bar_toolchain kuralının tanımını aşağıda bulabilirsiniz. Ö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, 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ğeri çiftleri içerebilir. ToolchainInfo öğesine tam olarak hangi alanların ekleneceğine dair spesifikasyon, araç zinciri türünde açıkça belgelenmelidir. 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ım 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 bkz. go_toolchain.

Araç 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ılmaktadır, ancak Bazel deposundaki diğer hedeflere bağlı daha karmaşık bir araç zincirine 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.

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 için üst öğe ile 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ç zincirinin tüm exec bağımlılıklarının aynı zamanda üst öğe derleme işlemleri için yürütülmesine izin verir. 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, 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ı son yapıya bağlıdır. Bu nedenle, derleme sırasında çağrılan bir araç olan derleyicinin de aynı platform için oluşturulması gerekir. Yürütme platformunda çalışabilmesi gerekir.

Araç zincirleriyle kaydolma 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, araç zincirini register_toolchains() kullanarak bir WORKSPACE dosyasına kaydederek veya --extra_toolchains işaretini kullanarak komut satırında araç zincirlerinin etiketlerini ileterek 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ırken 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 araç zinciri türüne bağlı bir hedef derlediğinizde, hedef ve yürütme platformlarına göre uygun bir araç zinciri seçilecektir.

# 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üğü

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; 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. Çıkışları, her araç zinciri türü için seçilmiş bir araç zinciri ve geçerli hedef için seçilmiş bir yürütme platformudur.

Mevcut yürütme platformları ve araç zincirleri, register_execution_platforms ve register_toolchains aracılığıyla WORKSPACE dosyasından 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 da otomatik olarak kullanılabilir bir yürütme platformu arasına eklenir. 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 ilk olarak bahsedilen araç zinciri en yüksek önceliğe sahiptir.

NOT: :all, :* ve /... gibi sözde hedefler, Bazel'in sözlüksel sıralamayı kullanan paket yükleme mekanizmasına göre 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, ifadede referans verilmeyen constraint_setting öğelerinden constraint_value öğeleri varsa bunlar 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şmeyenleri kaldıracak şekilde 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. Araç zinciri türlerinden biri için uyumlu bir zorunlu araç zinciri bulamayan yürütme platformları elenir. 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) oluşturulabildiği durumlarda, çözüm prosedürü 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. 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üm sürecinde kontrol ettiği ve atladığı araç zincirlerinin adlarını üretir.

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