Kurallar

Sorun bildir Kaynağı görüntüle Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Kural, Bazel'in girişler üzerinde gerçekleştirdiği bir dizi işlemi tanımlar. Bu işlemler, kuralın uygulama işlevi tarafından döndürülen sağlayıcılarda referans verilen bir dizi çıkış üretir. Örneğin, bir C++ ikili kuralı şunları yapabilir:

  1. Bir dizi .cpp kaynak dosya (giriş) alın.
  2. Kaynak dosyalarda g++'yı çalıştırın (işlem).
  3. Çalışma zamanında kullanılabilir hale getirmek için yürütülebilir çıkış ve diğer dosyalarla birlikte DefaultInfo sağlayıcısını döndürün.
  4. Hedef ve bağımlılıklarından toplanan C++'a özgü bilgilerle CcInfo sağlayıcısını döndürür.

Bazel açısından g++ ve standart C++ kitaplıkları da bu kuralın girişleridir. Kural yazan kişi olarak, yalnızca kullanıcı tarafından sağlanan kural girişlerini değil, işlemleri yürütmek için gereken tüm araçları ve kitaplıkları da göz önünde bulundurmanız gerekir.

Herhangi bir kural oluşturmadan veya değiştirmeden önce Bazel'in derleme aşamaları hakkında bilgi sahibi olduğunuzdan emin olun. Derlemenin üç aşamasını (yükleme, analiz ve yürütme) anlamak önemlidir. Kurallar ve makrolar arasındaki farkı anlamak için makrolar hakkında bilgi edinmek de faydalıdır. Başlamak için öncelikle Kurallar Eğitimi'ni inceleyin. Ardından, bu sayfayı referans olarak kullanın.

Bazel'in kendisinde birkaç kural yerleşiktir. genrule ve filegroup gibi yerel kurallar, temel destek sağlar. Kendi kurallarınızı tanımlayarak Bazel'in yerel olarak desteklemediği diller ve araçlar için destek ekleyebilirsiniz.

Bazel, Starlark dilini kullanarak kurallar yazmak için bir genişletilebilirlik modeli sağlar. Bu kurallar, doğrudan BUILD dosyalarından yüklenebilen .bzl dosyalarına yazılır.

Kendi kuralınızı tanımlarken hangi özellikleri destekleyeceğine ve çıktılarını nasıl oluşturacağına karar verebilirsiniz.

Kuralın implementation işlevi, analiz aşamasındaki tam davranışını tanımlar. Bu işlev herhangi bir harici komut çalıştırmaz. Bunun yerine, gerekirse kuralın çıktılarını oluşturmak için yürütme aşamasında kullanılacak işlemleri kaydeder.

Kural oluşturma

.bzl dosyasında yeni bir kural tanımlamak için rule işlevini kullanın ve sonucu genel bir değişkende saklayın. rule çağrısı, özellikleri ve bir uygulama işlevini belirtir:

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

Bu, example_library adlı bir kural türünü tanımlar.

rule çağrısı, kuralın executable = True ile yürütülebilir bir çıktı mı yoksa test = True ile özel olarak bir test yürütülebilir dosyası mı oluşturduğunu da belirtmelidir. İkincisi geçerliyse kural bir test kuralıdır ve kuralın adı _test ile bitmelidir.

Hedef örneği oluşturma

Kurallar BUILD dosyalarına yüklenebilir ve bu dosyalarda çağrılabilir:

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

Derleme kuralına yapılan her çağrı değer döndürmez ancak hedef tanımlama gibi bir yan etkisi vardır. Buna kuralı örnekleme denir. Bu, yeni hedef için bir ad ve hedefin özellikleri için değerler belirtir.

Kurallar, Starlark işlevlerinden de çağrılabilir ve .bzl dosyalarına yüklenebilir. Kuralları çağıran Starlark işlevlerine Starlark makroları denir. Starlark makroları nihayetinde BUILD dosyalarından çağrılmalıdır ve yalnızca BUILD dosyalarının hedefleri oluşturmak için değerlendirildiği yükleme aşamasında çağrılabilir.

Özellikler

Özellik, kural bağımsız değişkenidir. Özellikler, bir hedefin uygulanmasına özel değerler sağlayabilir veya başka hedeflere başvurarak bağımlılık grafiği oluşturabilir.

srcs veya deps gibi kurala özgü özellikler, rule işlevinin attrs parametresine özellik adlarından şemalara (attr modülü kullanılarak oluşturulur) bir harita iletilerek tanımlanır. name ve visibility gibi ortak özellikler tüm kurallara örtülü olarak eklenir. Ek özellikler, özellikle yürütülebilir ve test kurallarına örtülü olarak eklenir. Bir kurala örtülü olarak eklenen özellikler, attrs'ya iletilen sözlüğe dahil edilemez.

Bağımlılık özellikleri

Kaynak kodu işleyen kurallar, çeşitli bağımlılık türlerini işlemek için genellikle aşağıdaki özellikleri tanımlar:

  • srcs, bir hedefin işlemleri tarafından işlenen kaynak dosyaları belirtir. Genellikle, özellik şeması, kuralın işlediği kaynak dosya türü için hangi dosya uzantılarının beklendiğini belirtir. Başlık dosyaları içeren dillerle ilgili kurallar, genellikle bir hedef ve tüketicileri tarafından işlenen başlıklar için ayrı bir hdrs özelliği belirtir.
  • deps, bir hedef için kod bağımlılıklarını belirtir. Özellik şeması, bu bağımlılıkların hangi sağlayıcılar tarafından sağlanması gerektiğini belirtmelidir. (Örneğin, cc_library, CcInfo hizmetini sunar.)
  • data, çalışma zamanında bir hedefe bağlı olan tüm yürütülebilir dosyalara sunulacak dosyaları belirtir. Bu, rastgele dosyaların belirtilmesine olanak tanır.
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

Bunlar bağımlılık özelliklerine örneklerdir. Bir giriş etiketini belirten tüm özellikler (attr.label_list, attr.label veya attr.label_keyed_string_dict ile tanımlananlar), hedef tanımlandığında hedef ile etiketleri (veya karşılık gelen Label nesneleri) bu özellikte listelenen hedefler arasında belirli bir türde bağımlılıklar belirtir. Bu etiketlerin deposu ve muhtemelen yolu, tanımlanan hedefe göre çözümlenir.

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

Bu örnekte, other_target, my_target öğesinin bağımlısıdır ve bu nedenle other_target önce analiz edilir. Hedeflerin bağımlılık grafiğinde döngü varsa hata oluşur.

Gizli özellikler ve örtülü bağımlılıklar

Varsayılan değeri olan bir bağımlılık özelliği, örtülü bağımlılık oluşturur. Kullanıcı, BUILD dosyasında belirtmediği için örtüktür. Kullanıcılar çoğu zaman kuralın hangi aracı kullandığını belirtmekle ilgilenmediğinden, örtülü bağımlılıklar bir kural ile bir araç (derleyici gibi derleme zamanı bağımlılığı) arasındaki ilişkiyi sabit kodlamak için kullanışlıdır. Kuralın uygulama işlevinde bu, diğer bağımlılıklarla aynı şekilde ele alınır.

Kullanıcının bu değeri geçersiz kılmasına izin vermeden örtülü bir bağımlılık sağlamak istiyorsanız özelliğe alt çizgiyle (_) başlayan bir ad vererek özel yapabilirsiniz. Özel özelliklerin varsayılan değerleri olmalıdır. Genellikle yalnızca örtülü bağımlılıklar için özel özellikler kullanmak mantıklıdır.

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

Bu örnekte, example_library türündeki her hedef, derleyici //tools:example_compiler üzerinde örtülü bir bağımlılığa sahiptir. Bu, kullanıcının etiketini giriş olarak iletmemiş olmasına rağmen example_library'nın uygulama işlevinin derleyiciyi çağıran işlemler oluşturmasına olanak tanır. _compiler özel bir özellik olduğundan ctx.attr._compiler, bu kural türünün tüm hedeflerinde her zaman //tools:example_compiler'ı işaret eder. Alternatif olarak, özelliği compiler olarak adlandırabilir ve varsayılan değeri koruyabilirsiniz. Bu sayede kullanıcılar gerekirse farklı bir derleyici kullanabilir ancak derleyicinin etiketini bilmeleri gerekmez.

Örtülü bağımlılıklar genellikle kural uygulamasıyla aynı depoda bulunan araçlar için kullanılır. Araç, execution platform'dan veya farklı bir depodan geliyorsa kural, bu aracı bir toolchain'den almalıdır.

Çıkış özellikleri

Çıkış özellikleri (ör. attr.output ve attr.output_list), hedef tarafından oluşturulan bir çıkış dosyasını bildirir. Bunlar, bağımlılık özelliklerinden iki şekilde farklıdır:

  • Başka bir yerde tanımlanan hedeflere başvurmak yerine çıkış dosyası hedeflerini tanımlar.
  • Çıkış dosyası hedefleri, diğer şekilde değil, oluşturulan kural hedefine bağlıdır.

Genellikle, çıkış özellikleri yalnızca bir kuralın hedef ada dayalı olamayan, kullanıcı tanımlı adlara sahip çıkışlar oluşturması gerektiğinde kullanılır. Bir kuralın tek bir çıkış özelliği varsa genellikle out veya outs olarak adlandırılır.

Çıkış özellikleri, özellikle bağlı olabileceğiniz veya komut satırında istenebilecek önceden bildirilmiş çıkışlar oluşturmanın tercih edilen yoludur.

Uygulama işlevi

Her kural için bir implementation işlevi gerekir. Bu işlevler kesinlikle analiz aşamasında yürütülür ve yükleme aşamasında oluşturulan hedef grafiğini, yürütme aşamasında gerçekleştirilecek işlemlerin grafiğine dönüştürür. Bu nedenle, uygulama işlevleri dosyaları okuyamaz veya yazamaz.

Kural uygulama işlevleri genellikle özeldir (başında alt çizgi bulunur). Genellikle kurallarıyla aynı ada sahiptirler ancak sonlarına _impl eklenir.

Uygulama işlevleri tam olarak bir parametre alır: geleneksel olarak ctx olarak adlandırılan bir kural bağlamı. Sağlayıcıların listesini döndürürler.

Hedefler

Bağımlılıklar, analiz sırasında Target nesneleri olarak gösterilir. Bu nesneler, hedefin uygulama işlevi yürütüldüğünde oluşturulan sağlayıcıları içerir.

ctx.attr, her bağımlılık özelliğinin adlarına karşılık gelen alanlara sahiptir ve bu özelliği kullanan her doğrudan bağımlılığı temsil eden Target nesnelerini içerir. label_list özellikleri için bu, Targets listesidir. label özellikleri için bu, tek bir Target veya None'dir.

Hedefin uygulama işlevi tarafından bir sağlayıcı nesneleri listesi döndürülür:

return [ExampleInfo(headers = depset(...))]

Bunlara, sağlayıcı türü anahtar olarak kullanılarak dizin gösterimi ([]) ile erişilebilir. Bunlar, Starlark'ta tanımlanan özel sağlayıcılar veya Starlark genel değişkenleri olarak kullanılabilen yerel kurallar için sağlayıcılar olabilir.

Örneğin, bir kural hdrs özelliğini kullanarak üstbilgi dosyalarını alıp hedef ve tüketicilerinin derleme işlemlerine sağlarsa bunları şu şekilde toplayabilir:

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

Eski bir yapı stili vardır. Bu stilin kullanılması kesinlikle önerilmez ve kurallar bu stilden taşınmalıdır.

Dosyalar

Dosyalar File nesneleriyle gösterilir. Bazel, analiz aşamasında dosya G/Ç işlemi gerçekleştirmediğinden bu nesneler, dosya içeriğini doğrudan okumak veya yazmak için kullanılamaz. Bunun yerine, işlem grafiğinin parçalarını oluşturmak için işlem yayan işlevlere (ctx.actions bölümüne bakın) iletilirler.

File, kaynak dosya veya oluşturulmuş dosya olabilir. Oluşturulan her dosya, yalnızca bir işlemin sonucu olmalıdır. Kaynak dosyalar herhangi bir işlemin çıkışı olamaz.

Her bağımlılık özelliği için ctx.files karşılık gelen alanında, bu özelliği kullanan tüm bağımlılıkların varsayılan çıkışlarının listesi yer alır:

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file, spesifikasyonları allow_single_file = True olarak ayarlanan bağımlılık özellikleriyle ilgili tek bir File veya None içerir. ctx.executable, ctx.file ile aynı şekilde çalışır ancak yalnızca özellikleri executable = True olarak ayarlanan bağımlılık özelliklerinin alanlarını içerir.

Çıkışları bildirme

Analiz aşamasında, bir kuralın uygulama işlevi çıkışlar oluşturabilir. Yükleme aşamasında tüm etiketlerin bilinmesi gerektiğinden bu ek çıkışların etiketi yoktur. Çıkışlar için File nesneleri ctx.actions.declare_file ve ctx.actions.declare_directory kullanılarak oluşturulabilir. Çıkışların adları genellikle hedefin adına göre belirlenir. ctx.label.name:

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

Çıkış özellikleri için oluşturulanlar gibi önceden bildirilmiş çıkışlar için File nesneleri bunun yerine ctx.outputs öğesinin ilgili alanlarından alınabilir.

İşlemler

Bir işlem, bir dizi girişten nasıl bir dizi çıkış oluşturulacağını açıklar. Örneğin, "hello.c üzerinde gcc'yi çalıştır ve hello.o'yu al". Bir işlem oluşturulduğunda Bazel, komutu hemen çalıştırmaz. Bir işlem, başka bir işlemin çıkışına bağlı olabileceğinden bağımlılık grafiğine kaydedilir. Örneğin, C'de bağlayıcı, derleyiciden sonra çağrılmalıdır.

İşlem oluşturan genel amaçlı işlevler ctx.actions içinde tanımlanır:

ctx.actions.args, işlemler için bağımsız değişkenleri verimli bir şekilde biriktirmek amacıyla kullanılabilir. Çalışma zamanına kadar bağımlılık kümelerinin düzleştirilmesini önler:

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive = [headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with = ",")
    args.add_joined("-s", srcs, join_with = ",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

İşlemler, giriş dosyalarının listesini veya depset'ini alır ve çıkış dosyalarının (boş olmayan) bir listesini oluşturur. Giriş ve çıkış dosyaları kümesi, analiz aşamasında bilinmelidir. Bu durum, bağımlılıklardaki sağlayıcılar da dahil olmak üzere özelliklerin değerine bağlı olabilir ancak yürütme sonucuna bağlı olamaz. Örneğin, işleminiz unzip komutunu çalıştırıyorsa hangi dosyaların açılmasını beklediğinizi belirtmeniz gerekir (unzip komutunu çalıştırmadan önce). Dahili olarak değişken sayıda dosya oluşturan işlemler, bu dosyaları tek bir dosyada (ör. zip, tar veya başka bir arşiv biçimi) sarmalayabilir.

İşlemler, tüm girişlerini listelemelidir. Kullanılmayan girişlerin listelenmesine izin verilir ancak bu durum verimli değildir.

İşlemler, tüm çıkışlarını oluşturmalıdır. Diğer dosyaları yazabilirler ancak çıkışlarda olmayan hiçbir şey tüketiciler tarafından kullanılamaz. Bildirilen tüm çıkışlar bir işlem tarafından yazılmalıdır.

Eylemler, saf işlevlere benzer: Yalnızca sağlanan girişlere bağlı olmalı ve bilgisayar bilgilerine, kullanıcı adına, saate, ağa veya G/Ç cihazlarına (girişleri okuma ve çıkışları yazma hariç) erişmekten kaçınmalıdır. Bu önemlidir çünkü çıkış önbelleğe alınır ve yeniden kullanılır.

Bağımlılıklar, hangi işlemlerin yürütüleceğine karar veren Bazel tarafından çözülür. Bağımlılık grafiğinde döngü varsa bu bir hatadır. Bir işlem oluşturmak, bu işlemin yürütüleceğini garanti etmez. Bu, çıkışlarının derleme için gerekli olup olmadığına bağlıdır.

Sağlayıcılar

Sağlayıcılar, bir kuralın kendisine bağlı olan diğer kurallara sunduğu bilgi parçalarıdır. Bu veriler arasında çıkış dosyaları, kitaplıklar, bir aracın komut satırında iletilecek parametreler veya hedef tüketicilerin bilmesi gereken diğer her şey yer alabilir.

Bir kuralın uygulama işlevi yalnızca oluşturulan hedefin doğrudan bağımlılarından sağlayıcıları okuyabildiğinden kuralların, bir hedefin tüketicileri tarafından bilinmesi gereken hedef bağımlılarındaki tüm bilgileri iletmesi gerekir. Bu genellikle bilgileri bir depset içinde biriktirerek yapılır.

Bir hedefin sağlayıcıları, uygulama işlevi tarafından döndürülen sağlayıcı nesnelerinin listesiyle belirtilir.

Eski uygulama işlevleri, sağlayıcı nesneleri listesi yerine struct döndüren eski bir stilde de yazılabilir. Bu stil kesinlikle önerilmez ve kurallar bu stilden taşınmalıdır.

Varsayılan çıkışlar

Bir hedefin varsayılan çıkışları, hedef komut satırında oluşturulmak üzere istendiğinde varsayılan olarak istenen çıkışlardır. Örneğin, bir java_library hedefinin //pkg:foo varsayılan çıkışı foo.jar olduğundan bu hedef, bazel build //pkg:foo komutuyla oluşturulur.

Varsayılan çıkışlar, DefaultInfo öğesinin files parametresiyle belirtilir:

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

DefaultInfo bir kural uygulaması tarafından döndürülmezse veya files parametresi belirtilmezse DefaultInfo.files, varsayılan olarak tüm önceden bildirilmiş çıkışlar (genellikle çıkış özellikleri tarafından oluşturulanlar) olur.

İşlem gerçekleştiren kurallar, doğrudan kullanılması beklenmese bile varsayılan çıkışlar sağlamalıdır. İstenen çıkışların grafiğinde yer almayan işlemler budanır. Bir çıktı yalnızca hedefin tüketicileri tarafından kullanılıyorsa hedef bağımsız olarak oluşturulduğunda bu işlemler gerçekleştirilmez. Bu durum, yalnızca başarısız olan hedefi yeniden oluşturmak hatayı yeniden üretmeyeceğinden hata ayıklamayı zorlaştırır.

Çalıştırma dosyaları

Çalıştırma dosyaları, bir hedef tarafından derleme zamanında değil, çalışma zamanında kullanılan bir dosya grubudur. Bazel, yürütme aşamasında, runfile'lara işaret eden sembolik bağlantıları içeren bir dizin ağacı oluşturur. Bu, çalışma zamanında çalıştırılabilir dosyaların erişebilmesi için ikili dosyanın ortamını hazırlar.

Çalıştırma dosyaları, kural oluşturma sırasında manuel olarak eklenebilir. runfiles nesneleri, kural bağlamında runfiles yöntemiyle oluşturulabilir ctx.runfiles ve DefaultInfo üzerinde runfiles parametresine iletilebilir. Yürütülebilir kuralların yürütülebilir çıkışı, runfiles'a örtülü olarak eklenir.

Bazı kurallar, genellikle data olarak adlandırılan ve çıkışları hedeflerin çalışma dosyalarına eklenen özellikleri belirtir. Çalıştırma dosyaları, data'dan ve nihai yürütme için kod sağlayabilecek tüm özelliklerden (genellikle srcs (ilişkili data ile filegroup hedefleri içerebilir) ve deps) de birleştirilmelidir.

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

Özel sağlayıcılar

Sağlayıcılar, kurala özel bilgileri aktarmak için provider işlevi kullanılarak tanımlanabilir:

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields = {
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    },
)

Kural uygulama işlevleri daha sonra sağlayıcı örnekleri oluşturup döndürebilir:

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
Sağlayıcıların özel olarak başlatılması

Sağlayıcıların özel ön işleme ve doğrulama mantığıyla oluşturulmasını koruyabilirsiniz. Bu, tüm sağlayıcı örneklerinin belirli değişmezleri karşılamasını sağlamak veya kullanıcılara örnek almak için daha temiz bir API sunmak amacıyla kullanılabilir.

Bu işlem, provider işlevine bir init geri çağırması iletilerek yapılır. Bu geri çağırma işlevi verilirse provider() işlevinin dönüş türü, iki değerden oluşan bir demet olacak şekilde değişir: init işlevi kullanılmadığında normal dönüş değeri olan sağlayıcı sembolü ve "raw constructor".

Bu durumda, sağlayıcı sembolü çağrıldığında yeni bir örnek doğrudan döndürülmek yerine bağımsız değişkenler init geri çağırma işlevine iletilir. Geri çağırmanın dönüş değeri, alan adlarını (dizeler) değerlerle eşleyen bir sözlük olmalıdır. Bu, yeni örneğin alanlarını başlatmak için kullanılır. Geri çağırma işleminin herhangi bir imzası olabileceğini ve bağımsız değişkenler imzayla eşleşmezse geri çağırma işlemi doğrudan çağrılmış gibi bir hata bildirileceğini unutmayın.

Buna karşılık, ham oluşturucu init geri çağırma işlevini atlar.

Aşağıdaki örnekte, bağımsız değişkenleri önceden işlemek ve doğrulamak için init kullanılır:

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# Keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {"files_to_link": files_to_link, "headers": all_headers}

ExampleInfo, _new_exampleinfo = provider(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

Bir kural uygulaması daha sonra sağlayıcıyı aşağıdaki gibi başlatabilir:

ExampleInfo(
    files_to_link = my_files_to_link,  # may not be empty
    headers = my_headers,  # will automatically include the core headers
)

Ham oluşturucu, init mantığından geçmeyen alternatif genel fabrika işlevlerini tanımlamak için kullanılabilir. Örneğin, exampleinfo.bzl aşağıdaki tanımları içerebilir:

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

Genellikle, kullanıcı kodu tarafından yüklenememesi ve rastgele sağlayıcı örnekleri oluşturamaması için ham oluşturucu, adı alt çizgiyle (_new_exampleinfo yukarıda) başlayan bir değişkene bağlanır.

init için başka bir kullanım alanı da kullanıcının sağlayıcı sembolünü tamamen çağırmasını engellemek ve bunun yerine fabrika işlevini kullanmaya zorlamaktır:

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

Yürütülebilir kurallar ve test kuralları

Yürütülebilir kurallar, bazel run komutuyla çağrılabilen hedefleri tanımlar. Test kuralları, hedefleri bazel test komutuyla da çağrılabilen özel bir yürütülebilir kural türüdür. Yürütülebilir ve test kuralları, rule çağrısında ilgili executable veya test bağımsız değişkeni True olarak ayarlanarak oluşturulur:

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

Test kurallarının adları _test ile bitmelidir. (Test hedef adları da genellikle _test ile biter ancak bu zorunlu değildir.) Test dışı kurallar bu sonekleri içermemelidir.

Her iki kural türü de run veya test komutlarıyla çağrılacak, yürütülebilir bir çıkış dosyası (önceden bildirilmiş olabilir veya olmayabilir) oluşturmalıdır. Bazel'e bir kuralın hangi çıkışlarının bu yürütülebilir dosya olarak kullanılacağını söylemek için döndürülen DefaultInfo sağlayıcısının executable bağımsız değişkeni olarak iletin. Bu executable, kuralın varsayılan çıkışlarına eklenir (böylece bunu hem executable hem de files için iletmeniz gerekmez). Ayrıca runfiles'a da örtülü olarak eklenir:

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

Bu dosyayı oluşturan işlem, dosyada yürütülebilir bit'i ayarlamalıdır. ctx.actions.run veya ctx.actions.run_shell işlemi için bu işlem, işlem tarafından çağrılan temel araç tarafından yapılmalıdır. ctx.actions.write işlemi için is_executable = True değerini iletin.

Eski davranış olarak, yürütülebilir kurallar özel bir ctx.outputs.executable önceden bildirilmiş çıkışa sahiptir. Bu dosya, DefaultInfo kullanarak belirtmediğiniz durumlarda varsayılan yürütülebilir dosya olarak kullanılır. Aksi takdirde kullanılmamalıdır. Bu çıkış mekanizması, analiz sırasında yürütülebilir dosyanın adının özelleştirilmesini desteklemediği için kullanımdan kaldırılmıştır.

Yürütülebilir kural ve test kuralı örneklerini inceleyin.

Yürütülebilir kurallar ve test kuralları, tüm kurallar için eklenenlere ek olarak, örtülü olarak tanımlanmış ek özelliklere sahiptir. Varsayılan olarak eklenen özelliklerin varsayılan değerleri değiştirilemez. Ancak bu durum, varsayılan değeri değiştiren bir Starlark makrosuna özel bir kural yerleştirilerek çözülebilir:

def example_test(size = "small", **kwargs):
  _example_test(size = size, **kwargs)

_example_test = rule(
 ...
)

Runfiles konumu

Yürütülebilir bir hedef bazel run (veya test) ile çalıştırıldığında, runfiles dizininin kökü yürütülebilir dosyanın yanında yer alır. Yollar şu şekilde ilişkilidir:

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

Çalıştırma dosyaları dizinindeki bir File öğesinin yolu, File.short_path ile eşleşir.

bazel tarafından doğrudan yürütülen ikili, runfiles dizininin kökünün bitişiğindedir. Ancak, runfiles'tan from adlı ikili dosyalar aynı varsayımı yapamaz. Bunu azaltmak için her ikili, ortamı veya komut satırı bağımsız değişkenini ya da işaretini kullanarak çalışma dosyaları kökünü parametre olarak kabul etmenin bir yolunu sağlamalıdır. Bu, ikili dosyaların çağırdığı ikili dosyalara doğru kanonik runfiles kökünü iletmesine olanak tanır. Bu ayarlanmamışsa ikili dosya, bunun çağrılan ilk ikili dosya olduğunu tahmin edebilir ve bitişik bir runfiles dizini arayabilir.

Gelişmiş konular

Çıkış dosyaları isteme

Tek bir hedefin birden fazla çıkış dosyası olabilir. Bir bazel build komutu çalıştırıldığında, komuta verilen hedeflerin bazı çıkışları istenmiş olarak kabul edilir. Bazel yalnızca bu istenen dosyaları ve doğrudan veya dolaylı olarak bağlı oldukları dosyaları oluşturur. (İşlem grafiği açısından Bazel yalnızca istenen dosyaların geçişli bağımlılıkları olarak erişilebilen işlemleri yürütür.)

Varsayılan çıkışlara ek olarak, komut satırında herhangi bir önceden bildirilmiş çıkış açıkça istenebilir. Kurallar, çıkış özellikleri kullanılarak önceden bildirilmiş çıkışları belirtebilir. Bu durumda, kullanıcı kuralı oluştururken çıkışlar için etiketleri açıkça seçer. Çıkış özellikleri için File nesneleri elde etmek üzere ctx.outputs öğesinin ilgili özelliğini kullanın. Kurallar, hedef adına göre önceden bildirilmiş çıkışları örtülü olarak da tanımlayabilir ancak bu özellik kullanımdan kaldırılmıştır.

Varsayılan çıkışlara ek olarak, birlikte istenebilecek çıkış dosyaları koleksiyonları olan çıkış grupları da vardır. Bunlar --output_groups ile istenebilir. Örneğin, bir hedef //pkg:mytarget, debug_files çıkış grubuna sahip bir kural türündeyse bu dosyalar bazel build //pkg:mytarget --output_groups=debug_files çalıştırılarak oluşturulabilir. Önceden bildirilmemiş çıkışların etiketi olmadığından, bu çıkışlar yalnızca varsayılan çıkışlarda veya bir çıkış grubunda görünerek istenebilir.

Çıkış grupları, OutputGroupInfo sağlayıcısıyla belirtilebilir. Birçok yerleşik sağlayıcının aksine, OutputGroupInfo'nın bu ada sahip çıkış gruplarını tanımlamak için rastgele adlara sahip parametreler alabileceğini unutmayın:

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

Ayrıca, çoğu sağlayıcının aksine, aynı çıktı gruplarını tanımlamadıkları sürece OutputGroupInfo hem bir aspect hem de bu aspect'in uygulandığı kural hedefi tarafından döndürülebilir. Bu durumda, sonuçta elde edilen sağlayıcılar birleştirilir.

OutputGroupInfo genellikle bir hedeften tüketicilerinin işlemlerine belirli dosya türlerini aktarmak için kullanılmaması gerektiğini unutmayın. Bunun yerine kurala özel sağlayıcılar tanımlayın.

Yapılandırmalar

Farklı bir mimari için C++ ikili dosyası oluşturmak istediğinizi düşünün. Derleme karmaşık olabilir ve birden fazla adım içerebilir. Derleyiciler ve kod oluşturucular gibi bazı ara ikili dosyaların yürütme platformunda (ana makineniz veya uzak bir yürütücü olabilir) çalışması gerekir. Nihai çıktı gibi bazı ikili dosyalar hedef mimari için oluşturulmalıdır.

Bu nedenle Bazel'de "yapılandırmalar" ve geçişler kavramı vardır. En üstteki hedefler (komut satırında istenenler) "target" yapılandırmasında yerleşik olarak bulunur. Yürütme platformunda çalışması gereken araçlar ise "exec" yapılandırmasında yerleşik olarak bulunur. Kurallar, yapılandırmaya bağlı olarak farklı işlemler oluşturabilir. Örneğin, derleyiciye iletilen CPU mimarisini değiştirebilir. Bazı durumlarda, farklı yapılandırmalar için aynı kitaplık gerekebilir. Bu durumda, analiz edilecek ve potansiyel olarak birden çok kez oluşturulacaktır.

Bazel, varsayılan olarak bir hedefin bağımlılıklarını hedefle aynı yapılandırmada, yani geçişler olmadan oluşturur. Bağımlılık, hedefi oluşturmaya yardımcı olmak için gereken bir araç olduğunda, ilgili özellik bir yürütme yapılandırmasına geçişi belirtmelidir. Bu işlem, aracın ve tüm bağımlılıklarının yürütme platformu için oluşturulmasına neden olur.

Her bağımlılık özelliği için, bağımlılıkların aynı yapılandırmada oluşturulup oluşturulmayacağına veya bir yürütme yapılandırmasına geçip geçmeyeceğine karar vermek üzere cfg simgesini kullanabilirsiniz. Bir bağımlılık özelliğinde executable = True işareti varsa cfg açıkça ayarlanmalıdır. Bu, yanlış yapılandırma için yanlışlıkla araç oluşturulmasını önlemek amacıyla yapılır. Örneğe bakın

Genel olarak, çalışma zamanında ihtiyaç duyulacak kaynaklar, bağımlı kitaplıklar ve yürütülebilir dosyalar aynı yapılandırmayı kullanabilir.

Derleme kapsamında yürütülen araçlar (ör. derleyiciler veya kod oluşturucular) bir yürütme yapılandırması için oluşturulmalıdır. Bu durumda, özellikte cfg = "exec" değerini belirtin.

Aksi takdirde, çalışma zamanında kullanılan (ör. testin bir parçası olarak) yürütülebilir dosyalar hedef yapılandırma için oluşturulmalıdır. Bu durumda, özellikte cfg = "target" değerini belirtin.

cfg = "target" aslında hiçbir şey yapmaz: Kural tasarımcılarının amaçlarını açıkça belirtmelerine yardımcı olmak için tamamen kolaylık sağlayan bir değerdir. executable = False olduğunda (yani cfg isteğe bağlı olduğunda) bu özelliği yalnızca okunabilirliği gerçekten artırdığı durumlarda ayarlayın.

Ayrıca, cfg = my_transition kullanarak kullanıcı tanımlı geçişler de kullanabilirsiniz. Bu geçişler, kural yazarlarına yapılandırmaları değiştirme konusunda büyük bir esneklik sağlar ancak derleme grafiğini daha büyük ve daha az anlaşılır hale getirir.

Not: Geçmişte Bazel'de yürütme platformları kavramı yoktu ve bunun yerine tüm derleme işlemlerinin ana makinede çalıştığı kabul ediliyordu. 6.0'dan önceki Bazel sürümleri bunu temsil etmek için ayrı bir "ana makine" yapılandırması oluşturuyordu. Kodda veya eski dokümanlarda "host" referansları görürseniz bu, bahsettiğimiz şeydir. Bu ek kavramsal ek yükten kaçınmak için Bazel 6.0 veya daha yeni bir sürümü kullanmanızı öneririz.

Yapılandırma parçaları

Kurallar, cpp ve java gibi yapılandırma parçalarına erişebilir. Ancak erişim hatalarını önlemek için gerekli tüm parçaların bildirilmesi gerekir:

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    ...
)

Normalde, runfiles ağacındaki bir dosyanın göreli yolu, kaynak ağacındaki veya oluşturulan çıktı ağacındaki göreli yoluyla aynıdır. Bunların bir nedenle farklı olması gerekiyorsa root_symlinks veya symlinks bağımsız değişkenlerini belirtebilirsiniz. root_symlinks, yolları dosyalara eşleyen bir sözlüktür. Yollar, runfiles dizininin köküne göre belirlenir. symlinks sözlüğü aynıdır ancak yollara, ana çalışma alanının adı (mevcut hedefi içeren deponun adı değil) ile örtülü olarak önek eklenir.

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

symlinks veya root_symlinks kullanılıyorsa iki farklı dosyanın runfiles ağacında aynı yola eşlenmemesine dikkat edin. Bu durum, derlemenin çakışmayı açıklayan bir hatayla başarısız olmasına neden olur. Düzeltmek için çakışmayı kaldırmak üzere ctx.runfiles bağımsız değişkenlerinizi değiştirmeniz gerekir. Bu kontrol, kuralınızı kullanan tüm hedeflerin yanı sıra bu hedeflere bağlı olan her türlü hedef için yapılır. Bu durum, aracınızın başka bir araç tarafından geçişli olarak kullanılması muhtemel olduğunda özellikle risklidir. Sembolik bağlantı adları, bir aracın çalışma dosyaları ve tüm bağımlılıkları arasında benzersiz olmalıdır.

Kod kapsamı

coverage komutu çalıştırıldığında, derlemenin belirli hedefler için kapsam ölçümü eklemesi gerekebilir. Derleme, enstrümantasyon uygulanan kaynak dosyaların listesini de toplar. Göz önünde bulundurulan hedef alt kümesi, --instrumentation_filter işaretiyle kontrol edilir. --instrument_test_targets belirtilmediği sürece test hedefleri hariç tutulur.

Bir kural uygulaması derleme zamanında kapsam araçları ekliyorsa uygulama işlevinde bunu hesaba katması gerekir. ctx.coverage_instrumented, bir hedefin kaynaklarına araç eklenmesi gerekiyorsa kapsam modunda True değerini döndürür:

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

Kapsam modunda her zaman açık olması gereken mantık (bir hedefin kaynakları özellikle izleniyor olsun veya olmasın) ctx.configuration.coverage_enabled koşuluna bağlı olabilir.

Kural, derlemeden önce bağımlılıklarındaki kaynakları doğrudan içeriyorsa (ör. üstbilgi dosyaları) bağımlılıkların kaynaklarının enstrümantasyonlu olması durumunda derleme zamanı enstrümantasyonunu da etkinleştirmesi gerekebilir:

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if ctx.coverage_instrumented() or any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]):
    # Do something to turn on coverage for this compile action

Kurallar ayrıca coverage_common.instrumented_files_info kullanılarak oluşturulan InstrumentedFilesInfo sağlayıcısı kapsamı için hangi özelliklerin alakalı olduğu hakkında bilgi vermelidir. instrumented_files_info öğesinin dependency_attributes parametresi, deps gibi kod bağımlılıkları ve data gibi veri bağımlılıkları dahil olmak üzere tüm çalışma zamanı bağımlılığı özelliklerini listelemelidir. Kapsam enstrümantasyonu eklenebilecekse source_attributes parametresi, kuralın kaynak dosyaları özelliklerini listelemelidir:

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

InstrumentedFilesInfo döndürülmezse her bir araç dışı bağımlılık özelliği ile varsayılan bir özellik oluşturulur. Bu özellik, özellik şemasında cfg değerini "exec" olarak ayarlamaz. dependency_attributes içinde. (Bu, srcs gibi özellikleri source_attributes yerine dependency_attributes içine yerleştirdiğinden ideal bir davranış olmasa da bağımlılık zincirindeki tüm kurallar için açık kapsam yapılandırması yapılmasına gerek kalmaz.)

Kuralları test etme

Test kuralları, kapsam raporları oluşturmak için ek kurulum gerektirir. Kuralın kendisi aşağıdaki örtülü özellikleri eklemelidir:

my_test = rule(
    ...,
    attrs = {
        ...,
        # Implicit dependencies used by Bazel to generate coverage reports.
        "_lcov_merger": attr.label(
            default = configuration_field(fragment = "coverage", name = "output_generator"),
            executable = True,
            cfg = config.exec(exec_group = "test"),
        ),
        "_collect_cc_coverage": attr.label(
            default = "@bazel_tools//tools/test:collect_cc_coverage",
            executable = True,
            cfg = config.exec(exec_group = "test"),
        )
    },
    test = True,
)

configuration_field kullanıldığında, kapsam istenmediği sürece Java LCOV birleştirme aracına olan bağımlılık önlenebilir.

Test çalıştırıldığında, COVERAGE_DIR ortam değişkeniyle belirtilen dizine benzersiz adlara sahip bir veya daha fazla LCOV dosyası biçiminde kapsam bilgileri vermelidir. Ardından Bazel, bu dosyaları _lcov_merger aracını kullanarak tek bir LCOV dosyasında birleştirir. Mevcutsa _collect_cc_coverage aracını kullanarak C/C++ kapsamını da toplar.

Temel kapsam

Kapsam yalnızca bir testin bağımlılık ağacında yer alan kod için toplandığından, kapsam raporları yanıltıcı olabilir. Bu raporlar, --instrumentation_filter işaretiyle eşleşen tüm kodları kapsamayabilir.

Bu nedenle Bazel, kuralların ctx.instrumented_files_info öğesinin baseline_coverage_files özelliğini kullanarak temel kapsam dosyalarını belirtmesine olanak tanır. Bu dosyalar, kullanıcı tanımlı bir işlem tarafından LCOV biçiminde oluşturulmalı ve hedef kaynak dosyalarındaki tüm satırları, dalları, işlevleri ve/veya blokları (sources_attributes ve extensions parametrelerine göre) listelemelidir. Kapsam için izlenen hedeflerdeki kaynak dosyalar için Bazel, temel kapsamlarını --combined_report ile oluşturulan birleştirilmiş kapsam raporunda birleştirir ve böylece test edilmemiş dosyaların yine kapsam dışı olarak gösterilmesini sağlar.

Bir kural herhangi bir temel kapsam dosyası sağlamıyorsa Bazel, yalnızca kaynak dosya yollarından bahseden ancak içerikleri hakkında herhangi bir bilgi içermeyen sentetik kapsam bilgileri oluşturur.

Doğrulama İşlemleri

Bazen derlemeyle ilgili bir şeyi doğrulamanız gerekir ve bu doğrulama için gereken bilgiler yalnızca yapıtlar (kaynak dosyalar veya oluşturulan dosyalar) içinde bulunur. Bu bilgiler yapay nesnelerde yer aldığından kurallar, dosyaları okuyamadığı için analiz sırasında bu doğrulamayı yapamaz. Bunun yerine, işlemler bu doğrulamayı yürütme sırasında yapmalıdır. Doğrulama başarısız olduğunda işlem ve dolayısıyla derleme de başarısız olur.

Çalıştırılabilecek doğrulama örnekleri arasında statik analiz, linting, bağımlılık ve tutarlılık kontrolleri ile stil kontrolleri yer alır.

Doğrulama işlemleri, yapı performansını artırmak için yapı artefaktları oluşturmak üzere gerekli olmayan işlem bölümlerini ayrı işlemlere taşıyarak da yardımcı olabilir. Örneğin, derleme ve linting işlemlerini yapan tek bir işlem, derleme işlemi ve linting işlemi olarak ayrılabilirse linting işlemi doğrulama işlemi olarak çalıştırılabilir ve diğer işlemlerle paralel olarak yürütülebilir.

Bu "doğrulama işlemleri", yalnızca girişleriyle ilgili iddialarda bulunmaları gerektiğinden genellikle derlemenin başka bir yerinde kullanılan bir şey üretmez. Ancak bu durum bir sorun yaratır: Bir doğrulama işlemi, derlemenin başka bir yerinde kullanılan bir şey üretmiyorsa kural, işlemin çalışmasını nasıl sağlar? Geçmişte, doğrulama işleminin boş bir dosya oluşturması ve bu çıktının, derlemedeki diğer bazı önemli işlemlerin girişlerine yapay olarak eklenmesi yaklaşımı kullanılıyordu:

Bu yöntem, derleme işlemi çalıştırıldığında Bazel her zaman doğrulama işlemini çalıştırdığı için işe yarar ancak önemli dezavantajları vardır:

  1. Doğrulama işlemi, derlemenin kritik yolundadır. Bazel, derleme işleminin çalıştırılması için boş çıkışın gerekli olduğunu düşündüğünden, derleme işlemi girişi yoksayacak olsa bile önce doğrulama işlemini çalıştırır. Bu durum paralelliği azaltır ve derlemeleri yavaşlatır.

  2. Derleme işlemi yerine derlemedeki başka işlemler çalıştırılabiliyorsa doğrulama işlemlerinin boş çıkışlarının bu işlemlere de eklenmesi gerekir (ör. java_library'nın kaynak jar çıkışı). Derleme işlemi yerine çalışabilecek yeni işlemler daha sonra eklenirse ve boş doğrulama çıkışı yanlışlıkla kaldırılırsa da bu sorun yaşanır.

Bu sorunların çözümü, doğrulama çıkış grubunu kullanmaktır.

Doğrulama Çıkış Grubu

Doğrulama Çıkış Grubu, doğrulama işlemlerinin aksi takdirde kullanılmayacak çıkışlarını tutmak için tasarlanmış bir çıkış grubudur. Böylece, diğer işlemlerin girişlerine yapay olarak eklenmeleri gerekmez.

Bu grup, --output_groups işaretinin değerinden ve hedefin nasıl kullanıldığından (ör. komut satırında, bağımlılık olarak veya hedefin örtülü çıkışları aracılığıyla) bağımsız olarak çıkışları her zaman istendiği için özeldir. Normal önbelleğe alma ve artımlılık işlemlerinin geçerli olmaya devam ettiğini unutmayın: Doğrulama işlemine yönelik girişler değişmediyse ve doğrulama işlemi daha önce başarılı olduysa doğrulama işlemi çalıştırılmaz.

Bu çıkış grubunun kullanılması, doğrulama işlemlerinin boş olsa bile bir dosya çıkışı vermesini gerektirir. Bu işlem, normalde çıktı oluşturmayan bazı araçların dosya oluşturacak şekilde sarmalanmasını gerektirebilir.

Bir hedefin doğrulama işlemleri üç durumda çalıştırılmaz:

  • Hedef, araç olarak kullanıldığında
  • Hedef, örtülü bir bağımlılık olarak kullanıldığında (örneğin, "_" ile başlayan bir özellik)
  • Hedef, yürütme yapılandırmasında oluşturulduğunda.

Bu hedeflerin, doğrulama hatalarını ortaya çıkaracak kendi ayrı derlemeleri ve testleri olduğu varsayılır.

Doğrulama Çıkış Grubu'nu kullanma

Doğrulama Çıkış Grubu, _validation olarak adlandırılır ve diğer çıkış grupları gibi kullanılır:

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")
  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
    outputs = [validation_output],
    executable = ctx.executable._validation_tool,
    arguments = [validation_output.path],
  )

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"
    ),
  }
)

Doğrulama çıkış dosyasının DefaultInfo veya başka bir işlemin girişlerine eklenmediğini unutmayın. Bu kural türünün hedefi için doğrulama işlemi, hedef etikete bağlıysa veya hedefin örtülü çıkışlarından herhangi biri doğrudan ya da dolaylı olarak bağlıysa yine de çalışır.

Doğrulama işlemlerinin çıkışlarının genellikle yalnızca doğrulama çıkış grubuna gitmesi ve diğer işlemlerin girişlerine eklenmemesi önemlidir. Aksi takdirde paralellikten elde edilen kazanımlar kaybedilebilir. Ancak Bazel'in bunu zorunlu kılmak için özel bir kontrolü olmadığını unutmayın. Bu nedenle, doğrulama işlemi çıkışlarının Starlark kurallarıyla ilgili testlerdeki herhangi bir işlemin girişlerine eklenmediğini test etmeniz gerekir. Örneğin:

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

Doğrulama İşlemleri İşareti

Doğrulama işlemlerinin çalıştırılması, varsayılan olarak doğru değerine ayarlanmış --run_validations komut satırı işaretiyle kontrol edilir.

Kullanımdan kaldırılan özellikler

Kullanımdan kaldırılan önceden bildirilmiş çıkışlar

Önceden bildirilmiş çıkışları kullanmanın iki kullanımdan kaldırılmış yolu vardır:

  • rule öğesinin outputs parametresi, önceden bildirilmiş çıkış etiketleri oluşturmak için çıkış özelliği adları ve dize şablonları arasındaki eşlemeyi belirtir. Önceden bildirilmemiş çıkışları kullanmayı ve çıkışları DefaultInfo.files'ya açıkça eklemeyi tercih edin. Çıkışı kullanan kurallar için önceden bildirilmiş bir çıkışın etiketi yerine kural hedefinin etiketini giriş olarak kullanın.

  • Çalıştırılabilir kurallar için ctx.outputs.executable, kural hedefiyle aynı ada sahip önceden bildirilmiş bir çalıştırılabilir çıkışı ifade eder. Çıkışı açıkça bildirmeyi tercih edin (ör. ctx.actions.declare_file(ctx.label.name) ile) ve yürütülebilir dosyayı oluşturan komutun, yürütmeye izin verecek şekilde izinlerini ayarladığından emin olun. Yürütülebilir çıkışı, DefaultInfo öğesinin executable parametresine açıkça aktarın.

Kaçınılması gereken runfiles özellikleri

ctx.runfiles ve runfiles türünde, çoğu eski nedenlerle korunmuş karmaşık bir özellik seti bulunur. Aşağıdaki öneriler, karmaşıklığı azaltmaya yardımcı olur:

  • ctx.runfiles'ın collect_data ve collect_default modlarının kullanımından kaçının. Bu modlar, belirli sabit kodlanmış bağımlılık kenarlarında runfile'ları kafa karıştırıcı şekillerde örtülü olarak toplar. Bunun yerine, ctx.runfiles öğesinin files veya transitive_files parametrelerini kullanarak ya da runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) ile bağımlılıklardan gelen runfile'ları birleştirerek dosya ekleyin.

  • DefaultInfo oluşturucusunun data_runfiles ve default_runfiles parametrelerini kullanmaktan kaçının. Bunun yerine DefaultInfo(runfiles = ...) değerini belirtin. "Varsayılan" ve "veri" çalıştırma dosyaları arasındaki ayrım, eski sistemlerle uyumluluk nedeniyle korunur. Örneğin, bazı kurallar varsayılan çıkışlarını data_runfiles'ya yerleştirir ancak default_runfiles'ya yerleştirmez. data_runfiles kullanmak yerine kurallar hem varsayılan çıkışları içermeli hem de çalıştırılabilir dosyalar sağlayan özelliklerden (genellikle data) default_runfiles öğesini birleştirmelidir.

  • runfiles, DefaultInfo konumundan alınırken (genellikle yalnızca geçerli kural ile bağımlılıkları arasındaki runfile'ları birleştirme için) DefaultInfo.data_runfiles değil DefaultInfo.default_runfiles kullanılmalıdır.

Eski sağlayıcılardan veri taşıma

Geçmişte Bazel sağlayıcıları, Target nesnesindeki basit alanlardı. Nokta operatörü kullanılarak erişildi ve alan, sağlayıcı nesneleri listesi yerine kuralın uygulama işlevi tarafından döndürülen bir struct içine yerleştirilerek oluşturuldu:

return struct(example_info = struct(headers = depset(...)))

Bu tür sağlayıcılar, Target nesnesinin ilgili alanından alınabilir:

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

Bu stil artık kullanılmıyor ve yeni kodda kullanılmamalıdır. Geçiş yapmanıza yardımcı olabilecek bilgiler için aşağıya bakın. Yeni sağlayıcı mekanizması, ad çakışmalarını önler. Ayrıca, bir sağlayıcı örneğine erişen herhangi bir kodun sağlayıcı sembolünü kullanarak bu örneği almasını zorunlu kılarak veri gizlemeyi de destekler.

Eski sağlayıcılar şu anda hâlâ desteklenmektedir. Bir kural, hem eski hem de modern sağlayıcıları aşağıdaki şekilde döndürebilir:

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x = "foo", ...)
  modern_data = MyInfo(y = "bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

Bu kuralın bir örneği için sonuçta elde edilen dep Target nesnesi ise sağlayıcılar ve içerikleri dep.legacy_info.x ve dep[MyInfo].y olarak alınabilir.

providers dışında, döndürülen yapı özel anlamı olan (ve bu nedenle karşılık gelen eski sağlayıcı oluşturmayan) birkaç alan daha alabilir:

  • files, runfiles, data_runfiles, default_runfiles ve executable alanları, DefaultInfo alanlarının aynı adlı alanlarına karşılık gelir. DefaultInfo sağlayıcısı döndürülürken bu alanlardan herhangi birinin belirtilmesine izin verilmez.

  • output_groups alanı bir yapı değeri alır ve OutputGroupInfo ile eşleşir.

provides kural bildirimlerinde ve providers bağımlılık bildirimi özelliklerinde eski sağlayıcılar dize olarak, modern sağlayıcılar ise Info sembolleriyle iletilir. Taşıma işlemi sırasında dizeleri sembollere değiştirdiğinizden emin olun. Tüm kuralların atomik olarak güncellenmesinin zor olduğu karmaşık veya büyük kural kümelerinde aşağıdaki adımları izleyerek daha kolay bir şekilde güncelleme yapabilirsiniz:

  1. Önceki söz dizimini kullanarak hem eski hem de modern sağlayıcıları oluşturmak için eski sağlayıcıyı oluşturan kuralları değiştirin. Eski sağlayıcıyı döndürdüğünü belirten kurallar için bu beyanı hem eski hem de modern sağlayıcıları içerecek şekilde güncelleyin.

  2. Eski sağlayıcıyı kullanan kuralları, modern sağlayıcıyı kullanacak şekilde değiştirin. Eski sağlayıcıyı gerektiren özellik bildirimleri varsa bunları modern sağlayıcıyı gerektirecek şekilde güncelleyin. İsteğe bağlı olarak, tüketicilerin sağlayıcıyı kabul etmesini veya zorunlu kılmasını sağlayarak bu çalışmayı 1. adımla birlikte yapabilirsiniz: hasattr(target, 'foo') kullanarak eski sağlayıcının veya FooInfo in target kullanarak yeni sağlayıcının varlığını test edin.

  3. Eski sağlayıcıyı tüm kurallardan tamamen kaldırın.