Kurallar

Sorun bildirme Kaynağı görüntüleme Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Kural, kuralın uygulama işlevi tarafından döndürülen sağlayıcılarda referans verilen bir dizi çıkış oluşturmak için Bazel'in girişlerde gerçekleştirdiği bir dizi işlemi tanımlar. Örneğin, bir C++ ikili kuralı şu şekilde olabilir:

  1. Bir dizi .cpp kaynak dosyası (giriş) alın.
  2. Kaynak dosyalarda g++ komutunu çalıştırın (işlem).
  3. Yürütülebilir çıkışı ve çalışma zamanında kullanılabilmesi için diğer dosyaları içeren DefaultInfo sağlayıcıyı döndürün.
  4. Hedeften ve bağımlılıklarına ait toplanan C++'ya özgü bilgileri içeren CcInfo sağlayıcısını döndürme

Bazel'in bakış açısından g++ ve standart C++ kitaplıkları da bu kuralın girişleridir. Kural yazarı olarak, yalnızca bir kurala kullanıcı tarafından sağlanan girişleri değil, aynı zamanda işlemleri yürütmek için gereken tüm araçları ve kitaplıkları da dikkate almanız gerekir.

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

Bazel'in kendisinde birkaç kural oluşturulmuştur. cc_library ve java_binary gibi bu yerel kurallar, belirli diller için temel düzeyde destek sağlar. Kendi kurallarınızı tanımlayarak Bazel'in doğal olarak desteklemediği diller ve araçlar için benzer 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 çıkışlarını nasıl oluşturacağına siz karar verirsiniz.

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

Kural oluşturma

.bzl dosyasında yeni bir kural tanımlamak için rule işlevini kullanın ve sonucu bir genel değişkende depolayın. rule çağrısı, özellikleri ve 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 yürütülebilir bir çıkış (executable=True ile) veya özellikle bir test yürütülebilir dosyası (test=True ile) oluşturup oluşturmadığını da belirtmelidir. İkinci durumda kural bir test kuralı olur ve kuralın adı _test ile bitmelidir.

Hedef örneği oluşturma

Kurallar BUILD dosyalarında yüklenebilir ve çağrılabilir:

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

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

Bir oluşturma kuralına yapılan her çağrı değer döndürmez ancak bir hedef tanımlama yan etkisine sahiptir. Buna kuralın örneklenmesi denir. Bu, yeni hedef için bir ad ve hedefin özelliklerinin değerlerini belirtir.

Kurallar, Starlark işlevlerinden ç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 örneklemek için değerlendirildiği yükleme aşamasında çağrılabilir.

Özellikler

Özellik, bir kural bağımsız değişkenidir. Özellikler, bir hedefin uygulamasına belirli değerler sağlayabilir veya başka hedeflere referans vererek bir bağımlılık grafiği oluşturabilir.

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

Bağımlılık özellikleri

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

  • srcs, bir hedefin işlemleri tarafından işlenen kaynak dosyaları belirtir. Özellik şeması genellikle, kuralın işlediği kaynak dosya türü için hangi dosya uzantılarının beklendiğini belirtir. Başlık dosyası içeren diller için kurallar genellikle bir hedef ve tüketicileri tarafından işlenen üstbilgiler için ayrı bir hdrs özelliği belirtir.
  • deps, bir hedefin kod bağımlılıklarını belirtir. Özellik şeması, bu bağımlılıkların hangi sağlayıcılar tarafından sağlanacağını belirtmelidir. (Örneğin, cc_library, CcInfo sağlar.)
  • data, bir hedefe bağlı olan tüm yürütülebilir dosyalara çalışma zamanında kullanıma sunulacak dosyaları belirtir. Bu sayede, istediğiniz dosyaları belirtebilirsiniz.
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 örnektir. Giriş etiketi belirten tüm özellikler (attr.label_list, attr.label veya attr.label_keyed_string_dict ile tanımlananlar), hedef tanımlandığında bir hedef ile etiketleri (veya karşılık gelen Label nesneleri) listelenen hedefler arasındaki belirli bir türdeki bağımlılıkları belirtir. Bu etiketlerin depolama alanı ve muhtemelen yolu, tanımlanan hedefe göre çözülür.

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

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

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

Gizli özellikler ve gizli bağımlılıklar

Varsayılan değere sahip bir bağımlılık özelliği dolaylı bağımlılık oluşturur. Kullanıcının BUILD dosyasında belirtmediği hedef grafiğin bir parçası olduğu için gizlidir. Kullanıcılar çoğu zaman kuralın kullandığı aracı belirtmek istemediğinden, kural ile araç (derleme zamanı bağımlılığı, derleyici gibi) arasındaki ilişkiyi sabit kodlamak için örtülü bağımlılıklar kullanışlıdır. Kuralın uygulama işlevi içinde 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ği, alt çizgiyle (_) başlayan bir adla gizli yapabilirsiniz. Gizli özelliklerde varsayılan değerler olmalıdır. Özel özelliklerin yalnızca örtülü bağımlılıklar için kullanılması genellikle anlamlı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 //tools:example_compiler derleyicisine dolaylı bir bağımlılığa sahiptir. Bu, kullanıcı etiketini giriş olarak iletmemiş olsa bile example_library'ün uygulama işlevinin derleyiciyi çağıran işlemler oluşturmasına olanak tanır. _compiler gizli bir özellik olduğundan, ctx.attr._compiler bu kural türünün tüm hedeflerinde her zaman //tools:example_compiler öğesini gösterir. Alternatif olarak, özelliği alt çizgi olmadan compiler olarak adlandırabilir ve varsayılan değeri koruyabilirsiniz. Bu, kullanıcıların gerekirse farklı bir derleyici kullanmalarına olanak tanır ancak derleyicinin etiketi hakkında bilgi sahibi olmalarını gerektirmez.

Dolaylı bağımlılıklar, genellikle kural uygulamasıyla aynı depoda bulunan araçlar için kullanılır. Araç çalışma platformundan veya farklı bir depodan geliyorsa kural, bu aracı bir araç zincirinden almalıdır.

Çıkış özellikleri

attr.output ve attr.output_list gibi çıkış özellikleri, hedefin oluşturduğu bir çıkış dosyasını belirtir. Bu özellikler, bağımlılık özelliklerinden iki şekilde farklıdır:

  • Başka bir yerde tanımlanan hedeflere başvurmak yerine çıkış dosyası hedeflerini tanımlarlar.
  • Çıkış dosyası hedefleri, örneklenen kural hedefine bağlıdır. Bunun tersi olmaz.

Çıkış özellikleri genellikle yalnızca bir kuralın, hedef ada dayandırılamayan kullanıcı tanımlı adlarla çıkış oluşturması gerektiğinde kullanılır. Bir kuralın bir çıkış özelliği varsa kural genellikle out veya outs olarak adlandırılır.

Çıkış özellikleri, önceden bildirilmiş çıkışlar oluşturmanın tercih edilen yoludur. Bu çıkışlara özel olarak bağımlı olunabilir veya komut satırında istenebilir.

Uygulama işlevi

Her kural için bir implementation işlevi gerekir. Bu işlevler yalnızca 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şlemler grafiğine dönüştürür. Dolayısıyla, uygulama işlevleri aslında dosyaları okuyamaz veya yazamaz.

Kural uygulama işlevleri genellikle gizlidir (başta alt çizgiyle belirtilir). Kurallarla aynı şekilde adlandırılırlar ancak sonlarına _impl eklenir.

Uygulama işlevleri için tam olarak tek bir parametre kullanılır: Geleneksel olarak ctx olarak adlandırılan kural bağlamı. Sağlayıcıların listesini döndürürler.

Hedefler

Bağımlılıklar analiz zamanında Target nesneleri olarak temsil edilir. 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. Bu alanlar, ilgili özellik aracılığıyla her doğrudan bağımlılığı temsil eden Target nesneleri içerir. label_list özellikleri için bu, Targets öğelerinin bir listesidir. label özellikleri için bu tek bir Target veya None olmalıdır.

Bir hedefin uygulama işlevi, sağlayıcı nesnelerinin bir listesini döndürü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 başlık dosyalarını hdrs özelliği aracılığıyla alıyor ve bunları hedef ile tüketicilerinin derleme işlemlerine sağlıyorsa bunları şu şekilde toplayabilir:

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

Hedefin uygulama işlevinden sağlayıcı nesneleri listesi yerine struct döndürüldüğü eski stil için:

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

Sağlayıcılar, Target nesnesinin ilgili alanından alınabilir:

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

Bu stilin kullanılması önerilmez ve kurallar bu stilde taşınmamalıdır.

Dosyalar

Dosyalar File nesneleriyle temsil edilir. 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 yayınlayan işlevlere (ctx.actions bölümüne bakın) iletilir.

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

Her bağımlılık özelliği için ctx.files'un ilgili alanı, bu özellik aracılığıyla tüm bağımlılıkların varsayılan çıkışlarının bir listesini içerir:

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

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

Çıktıları beyan etme

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ışlarda etiket yoktur. Çıkışlar için File nesneleri ctx.actions.declare_file ve ctx.actions.declare_directory kullanılarak oluşturulabilir. Çıktı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 ctx.outputs öğesinin karşılık gelen alanlarından alınabilir.

İşlemler

İşlem, bir giriş grubundan nasıl bir çıkış grubu oluşturulacağını açıklar. Örneğin, "hello.c üzerinde gcc'yi çalıştır ve hello.o dosyasını al". Bazel, bir işlem oluşturulduğunda komutu hemen çalıştırmaz. Bir işlem başka bir işlemin çıktısına bağlı olabileceğinden, işlemi bağımlılık grafiğine kaydeder. Ö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şlemlere ait bağımsız değişkenleri verimli bir şekilde toplamak için kullanılabilir. Yürütme zamanına kadar depsetlerin 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ı listesini veya depset'ini alır ve çıkış dosyaları listesini (boş olmayan) oluşturur. Giriş ve çıkış dosyaları grubu analiz aşamasında bilinmelidir. Bağımlılıklardan gelen sağlayıcılar da dahil olmak üzere özelliklerin değerine bağlı olabilir ancak yürütmenin sonucuna bağlı olamaz. Örneğin, işleminizde unzip komutu çalıştırılıyorsa hangi dosyaların şifresinin çözülmesini 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 dosyaya (ö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 verimsizdir.

İşlemler, tüm çıkışlarını oluşturmalıdır. Diğer dosyaları yazabilirler ancak çıktılarda olmayan hiçbir şey tüketicilere sunulmaz. Tanımlanan tüm çıkışlar bir işlem tarafından yazılmalıdır.

İşlemler, saf işlevlerle benzerdir: Yalnızca sağlanan girişlere bağlı olmalı ve bilgisayar bilgileri, kullanıcı adı, saat, ağ veya G/Ç cihazlarına (giriş okuma ve çıkış yazma hariç) erişmekten kaçınmalıdır. Çıkış önbelleğe alınacağı ve yeniden kullanılacağı için bu önemlidir.

Bağımlılıklar Bazel tarafından çözülür. Bazel, hangi işlemlerin yürütüleceğine karar verir. Bağımlılık grafiğinde döngü varsa bu bir hatadır. Bir işlemin oluşturulması, işlemin yürütüleceğini garanti etmez. Bu, işlemin çıktı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ı diğer kurallara gösterdiği 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üketicilerinin bilmesi gereken diğer her şey yer alabilir.

Bir kuralın uygulama işlevi, sağlayıcıları yalnızca belirlenen hedefin anlık bağımlılıklarından okuyabildiğinden, kuralların bir hedefin tüketicileri tarafından bilinmesi gereken bir hedef bağımlılıklarından gelen tüm bilgileri, genellikle bir depset olarak toplayarak iletmesi gerekir.

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

Eski uygulama işlevleri, uygulama işlevinin sağlayıcı nesneleri listesi yerine struct döndürdüğü eski bir tarzda da yazılabilir. Bu stilin kullanılması önerilmez ve kurallar bu stilde taşınmamalıdır.

Varsayılan çıkışlar

Bir hedefin varsayılan çıkışları, komut satırında derleme için hedef istendiğinde varsayılan olarak istenen çıkışlardır. Örneğin, java_library hedefi //pkg:foo varsayılan çıkış olarak foo.jar değerine sahiptir. Bu nedenle, bazel build //pkg:foo komutu tarafından oluşturulur.

Varsayılan çıkışlar, DefaultInfo öğesinin files parametresi tarafından belirtilir:

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

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

İş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 olmayan işlemler kısaltılır. Bir çıkış yalnızca bir hedefin tüketicileri tarafından kullanılıyorsa hedef tek başına oluşturulduğunda bu işlemler gerçekleştirilmez. Yalnızca başarısız hedefi yeniden oluşturmak hatayı yeniden oluşturmayacağından bu durum, hata ayıklamayı daha zor hale getirir.

Çalışma dosyaları

Çalışma dosyaları, bir hedefin çalışma zamanında (derleme zamanı yerine) kullandığı bir dosya grubudur. Yürütme aşamasında, Bazel çalıştırma dosyalarını işaret eden sembolik bağlantıları içeren bir dizin ağacı oluşturur. Bu işlem, çalışma zamanında çalışma dosyalarına erişebilmesi için ortamda ikili dosyayı hazırlar.

Çalıştırma dosyaları, kural oluşturulurken manuel olarak eklenebilir. runfiles nesneleri, kural bağlamında ctx.runfiles üzerinde runfiles yöntemi ile oluşturulabilir ve DefaultInfo tarihinde runfiles parametresine iletilebilir. Yürütülebilir kuralların yürütülebilir çıkışı, çalışma dosyalarına dolaylı olarak eklenir.

Bazı kurallar, genellikle data olarak adlandırılan ve çıkışları hedeflerin çalışma dosyalarına eklenen özellikleri belirtir. Ayrıca çalıştırma dosyaları, data 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 özelliklerinden 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ı örneklerini 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ı

Özel ön işleme ve doğrulama mantığıyla bir sağlayıcının oluşturulmasını korumak mümkündür. Bu, tüm sağlayıcı örneklerinin belirli değişmezlik kurallarına uymasını sağlamak veya kullanıcılara örnek elde etmek için daha temiz bir API sunmak için kullanılabilir.

Bu işlem, provider işlevine bir init geri çağırma işlevi geçirilerek yapılır. Bu geri çağırma işlevi sağlanırsa provider() işlevinin döndürdüğü tür, iki değerden oluşan bir tuple olarak değişir: init kullanılmadığında normal döndürülen değer olan sağlayıcı simgesi ve "ham kurucu".

Bu durumda, sağlayıcı sembolü çağrıldığında doğrudan yeni bir örnek döndürmek yerine bağımsız değişkenleri init geri çağırma işlevine iletir. Geri çağırma işlevinin döndürdüğü değer, alan adlarını (dizeleri) değerlerle eşleyen bir sözlük olmalıdır. Bu değer, yeni örneğin alanlarını başlatmak için kullanılır. Geri çağırma işlevinin herhangi bir imzaya sahip olabileceğini ve bağımsız değişkenler imzayla eşleşmezse geri çağırma işlevi doğrudan çağrılmış gibi bir hata bildirildiğ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şkenlerini ön işleme almak ve doğrulamak için init kullanılır:

# //pkg:exampleinfo.bzl

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

# It's possible to define an init accepting positional arguments, but
# 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(
    ...
    init = _exampleinfo_init)

export ExampleInfo

Daha sonra bir kural uygulaması, sağlayıcıyı şu şekilde örneklendirebilir:

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

Ham kurucu, init mantığından geçmeyen alternatif herkese açık fabrika işlevleri tanımlamak için kullanılabilir. Örneğin, exampleinfo.bzl dosyasında şunları tanımlayabiliriz:

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)

Ham kurucu genellikle adı alt çizgiyle başlayan bir değişkene (yukarıdaki _new_exampleinfo) bağlanır. Böylece kullanıcı kodu, kurucuyu yükleyemez ve rastgele sağlayıcı örnekleri oluşturamaz.

init'ün başka bir kullanımı, kullanıcının sağlayıcı simgesini çağırmasını tamamen engellemek ve bunun yerine kullanıcıyı 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 komutu tarafından çağrılabilecek hedefleri tanımlar. Test kuralları, hedefleri bazel test komutuyla da çağrılabilecek özel bir tür çalıştırılabilir kuraldır. Yürütülebilir ve test kuralları, rule çağrısında ilgili executable veya test bağımsız değişkenini True olarak ayarlayarak 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 kurallara göre _test ile biter, ancak bu zorunlu değildir.) Test dışı kurallarda bu son ek olmamalıdır.

Her iki kural türü de run veya test komutları tarafından çağrılacak, yürütülebilir bir çıkış dosyası (önceden bildirilmiş olabilir veya bildirilmemiş olabilir) oluşturmalıdır. Bazel'e, bir kuralın çıktılarından hangisinin bu yürütülebilir dosya olarak kullanılacağını bildirmek için döndürülen bir DefaultInfo sağlayıcının executable bağımsız değişkeni olarak iletin. Bu executable, kuralın varsayılan çıkışlarına eklenir (bu nedenle bunu hem executable hem de files'a iletmeniz gerekmez). Ayrıca çalışma dosyalarına da dolaylı 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 bitini ayarlamalıdır. ctx.actions.run veya ctx.actions.run_shell işlemleri 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ın önceden beyan edilmiş özel bir ctx.outputs.executable çıkışı vardır. DefaultInfo kullanarak bir dosya belirtmezseniz bu dosya varsayılan yürütülebilir dosya olarak kullanılır. Aksi takdirde kullanılmamalıdır. Bu çıkış mekanizması, yürütülebilir dosyanın adının analiz sırasında ö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, dolaylı olarak tanımlanmış ek özelliklere sahiptir. Anlamlı olarak eklenen özelliklerin varsayılan değerleri değiştirilemez. Ancak bu durum, özel bir kuralı varsayılan değeri değiştiren bir Starlark makrosuna sarmalayarak atlatılabilir:

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

_example_test = rule(
 ...
)

Çalışma dosyası konumu

Bir yürütülebilir hedef bazel run (veya test) ile çalıştırıldığında, runfiles dizininin kökü yürütülebilir dosyanın yanındadır. Yollar aşağıdaki gibi 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)

Runfiles dizinindeki File yolu File.short_path değerine karşılık gelir.

Doğrudan bazel tarafından yürütülen ikili dosya, runfiles dizininin köküne bitişiktir. Ancak çalışma dosyalarından from adlı ikili dosyalar aynı varsayımı yapamaz. Bu sorunu azaltmak için her ikili, bir ortam veya komut satırı bağımsız değişkeni/işareti kullanarak çalıştırma dosyası kökünü parametre olarak kabul etmenin bir yolunu sağlamalıdır. Bu, ikili programların doğru standart çalıştırma dosyası kökünü çağırdığı ikili programlara iletmesini sağlar. Bu ayar belirtilmezse ikili program, çağrılan ilk ikili program olduğunu tahmin edip bitişik bir runfiles dizinini arayabilir.

İleri seviye konular

Çıkış dosyaları isteğinde bulunma

Tek bir hedefin birden fazla çıkış dosyası olabilir. Bir bazel build komutu çalıştırıldığında, komuta verilen hedeflerin bazı çıkışlarının istendiği kabul edilir. Bazel yalnızca istenen bu dosyaları ve doğrudan veya dolaylı olarak bu dosyalara bağlı olan dosyaları derleyebilir. (İş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 açıkça istenebilecek herhangi bir önceden bildirilmiş çıkış vardır. Kurallar, çıktı özellikleri aracılığıyla önceden tanımlanmış çıkışları belirtebilir. Bu durumda kullanıcı, kuralı örneklendirirken çıkışlar için etiketleri açıkça seçer. Çıkış özellikleri için File nesnelerini almak amacıyla ilgili ctx.outputs özelliğini kullanın. Kurallar, hedef ada göre de önceden bildirilmiş çıkışları dolaylı olarak tanımlayabilir ancak bu özelliğin desteği sonlandırılmıştır.

Varsayılan çıkışlara ek olarak, birlikte istenebilecek çıkış dosyası koleksiyonları olan çıkış grupları da vardır. Bu bilgiler --output_groups ile istenebilir. Örneğin, bir hedef //pkg:mytarget, debug_files çıkış grubuna sahip bir kural türüne sahipse bu dosyalar bazel build //pkg:mytarget --output_groups=debug_files çalıştırılarak oluşturulabilir. Önceden bildirilmemiş çıkışların etiketleri bulunmadığından 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ıdan farklı olarak OutputGroupInfo, rastgele adlara sahip parametreleri alarak bu ada sahip çıkış gruplarını tanımlar:

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 OutputGroupInfo, aynı çıkış gruplarını tanımlamadıkları sürece hem bir yön hem de bu yönün uygulandığı kural hedefi tarafından döndürülebilir. Bu durumda, ortaya çıkan sağlayıcılar birleştirilir.

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

Yapılandırmalar

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

Bu nedenle Bazel'de "yapılandırmalar" ve geçişler kavramı vardır. En üstteki hedefler ("target" yapılandırmasında oluşturulan, komut satırında istenen hedefler) "target" yapılandırmasında, yürütme platformunda çalıştırılması gereken araçlar ise "exec" yapılandırmasında oluşturulur. Kurallar, yapılandırmaya bağlı olarak farklı işlemler oluşturabilir (ör. derleyiciye iletilen CPU mimarisini değiştirmek için). Bazı durumlarda farklı yapılandırmalar için aynı kitaplık gerekli olabilir. Bu durumda, analiz edilir ve muhtemelen birden çok kez oluşturulur.

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

Bağımlılıkların aynı yapılandırmada mı yoksa bir yürütme yapılandırmasına mı oluşturulacağına karar vermek için her bağımlılık özelliğinde cfg kullanabilirsiniz. Bir bağımlılık özelliği executable=True işaretine sahipse cfg açıkça ayarlanmalıdır. Bunun amacı, yanlış yapılandırmaya yönelik bir aracın yanlışlıkla oluşturulmasını önlemektir. Ö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 işleminin bir parçası olarak çalıştırılan araçlar (ör. derleyiciler veya kod oluşturucular) bir exec yapılandırması için derlenmelidir. Bu durumda, özellikte cfg="exec" değerini belirtin.

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

cfg="target" aslında hiçbir şey yapmaz. Kural tasarımcılarının amaçları konusunda açık olmalarına yardımcı olan bir kolaylık değeridir. cfg değerinin isteğe bağlı olduğu executable=False durumunda, yalnızca okunabilirliğe gerçekten yardımcı olduğu durumlarda bunu ayarlayın.

cfg=my_transition'yi kullanıcı tanımlı geçişleri kullanmak için de kullanabilirsiniz. Bu geçişler, derleme grafiğini daha geniş ve daha az anlaşılır hale getirme dezavantajı sayesinde kural yazarlarına yapılandırma değiştirme konusunda büyük esneklik sağlar.

Not: Bazel'de geçmişte yürütme platformu kavramı yoktu ve bunun yerine tüm derleme işlemlerinin ana makinede çalıştığı kabul ediliyordu. Bu nedenle, tek bir "ana makine" yapılandırması ve ana makine yapılandırmasında bağımlılık oluşturmak için kullanılabilecek bir "ana makine" geçişi vardır. Birçok kural, araçları için hâlâ "host" geçişini kullanıyor ancak bu geçiş şu anda desteği sonlandırılmış durumda ve mümkün olduğunda "exec" geçişlerinin kullanılması için taşınıyor.

"host" (Ana makine) ve "exec" yapılandırmaları arasında birçok fark vardır:

  • "host" terminaldir, "exec" değildir: "host" yapılandırmasında bir bağımlılık oluşturulduktan sonra başka geçişe izin verilmez. "exec" yapılandırmasına başladıktan sonra başka yapılandırma geçişleri yapmaya devam edebilirsiniz.
  • "host" monolitik, "exec" ise değildir: Yalnızca bir "ana makine" yapılandırması vardır ancak her yürütme platformu için farklı bir "yürütme" yapılandırması olabilir.
  • "ana makine", araçları Bazel ile aynı makinede veya önemli ölçüde benzer bir makinede çalıştırdığınızı varsayar. Bu durum artık geçerli değil: Derleme işlemlerini yerel makinenizde veya uzak bir yürütücüde çalıştırabilirsiniz. Uzaktan yürütücünün, yerel makinenizle aynı CPU ve işletim sistemine sahip olacağı garanti edilmez.

Hem "exec" hem de "host" yapılandırmaları aynı seçenek değişikliklerini uygular (örneğin, --host_compilation_mode yerine --compilation_mode ayarlayın, --host_cpu yerine --cpu ayarlayın vb.). Aradaki fark, "ana makine" yapılandırmasının diğer tüm işaretlerin varsayılan değerleriyle başlaması, "yürütme" yapılandırmasının ise hedef yapılandırmaya bağlı olarak işaretlerin mevcut değerleriyle başlamasıdır.

Yapılandırma parçaları

Kurallar cpp, java ve jvm 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
    host_fragments = ["java"], # Required fragments of the host configuration
    ...
)

ctx.fragments yalnızca hedef yapılandırma için yapılandırma parçaları sağlar. Ana makine yapılandırması için parçalara erişmek istiyorsanız bunun yerine ctx.host_fragments kullanın.

Normalde, bir dosyanın runfiles ağacındaki göreli yolu, söz konusu dosyanın kaynak ağaç veya oluşturulan çıkış ağacındaki göreli yolu ile aynıdır. Bu değerlerin bir nedenle farklı olması gerekiyorsa root_symlinks veya symlinks bağımsız değişkenlerini belirtebilirsiniz. root_symlinks, yolları dosyalarla eşleyen bir sözlüktür. Bu yolların referans noktası, runfiles dizininin köküdür. symlinks sözlüğü aynıdır ancak yolların önüne çalışma alanının adı gizli olarak 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ı dosyayı, çalıştırma dosyaları ağacında aynı yola eşlememeye dikkat edin. Bu, derlemenin çakışmayı açıklayan bir hatayla başarısız olmasına neden olur. Sorunu düzeltmek için çakışma olmaması amacıyla ctx.runfiles bağımsız değişkenlerinizi değiştirmeniz gerekir. Bu kontrol, kuralınızı kullanan tüm hedefler ve bu hedeflere bağlı olan her tür hedef için yapılır. Bu durum, aracınızın başka bir araç tarafından aktarmalı olarak kullanılması muhtemelse özellikle risklidir. Simge bağlantısı adları, bir aracın çalışma dosyalarında ve tüm bağımlılıklarında benzersiz olmalıdır.

Kod kapsamı

coverage komutu çalıştırıldığında derlemenin belirli hedefler için kapsam enstrümasyonu eklemesi gerekebilir. Derleme, gerekli kılınan kaynak dosyaların listesini de toplar. Hedeflerin dikkate alınan alt kümesi, --instrumentation_filter işaretçisi tarafından kontrol edilir. --instrument_test_targets belirtilmediği sürece test hedefleri hariç tutulur.

Bir kural uygulaması derleme sırasında kapsam enstrümasyonu ekliyorsa bunu uygulama işlevinde hesaba katması gerekir. Bir hedefin kaynaklarının enstrümasyona tabi tutulması gerekiyorsa ctx.coverage_instrumented 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 (hedef kaynaklarının özellikle gerekli olup olmadığına bakılmaksızın), ctx.configuration.coverage_enabled

Kural, derlemeden önce bağımlılıklarından gelen kaynakları doğrudan içeriyorsa (başlık dosyaları gibi) ve bağımlılıkların kaynaklarının izlenmesi gerektiğinde 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.configuration.coverage_enabled and
    (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ıyla kapsam için hangi özelliklerin alakalı olduğu hakkında bilgi sağlamalıdır. instrumented_files_info öğesinin dependency_attributes parametresi, deps gibi kod bağımlılıkları ve data gibi veri bağımlılıkları da dahil olmak üzere tüm çalışma zamanı bağımlılık özelliklerini listelemelidir. Kapsam araçları 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 dependency_attributes içinde, cfg özelliği "host" veya "exec" olarak ayarlanmayan her araç dışı bağımlılık özelliği için varsayılan bir özellik oluşturulur. (srcs gibi özellikleri source_attributes yerine dependency_attributes içine yerleştirdiği için bu ideal bir davranış değildir ancak bağımlılık zincirindeki tüm kurallar için açık kapsam yapılandırması gerekmesini önler.)

Doğrulama İşlemleri

Bazen derlemeyle ilgili bir şeyi doğrulamanız gerekir ve bu doğrulamayı yapmak için gereken bilgiler yalnızca yapı taşlarında (kaynak dosyalar veya oluşturulan dosyalar) bulunur. Bu bilgiler yapılarda olduğu için kurallar dosyaları okuyamadığından analiz sırasında bu doğrulamayı yapamaz. Bunun yerine, işlemlerin bu doğrulamayı yürütme sırasında yapması gerekir. Doğrulama başarısız olduğunda işlem de başarısız olur ve dolayısıyla derleme de başarısız olur.

Çalıştırılabilecek doğrulamalara örnek olarak statik analiz, hata analizi, bağımlılık ve tutarlılık kontrolleri ve stil kontrolleri verilebilir.

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

Bu "doğrulama işlemleri" genellikle yalnızca girişleriyle ilgili iddialarda bulunmaları gerektiğinden derlemede başka bir yerde kullanılan bir şey üretmez. Ancak bu durum bir sorun teşkil eder: Bir doğrulama işlemi, derlemede başka bir yerde kullanılan bir şey üretmiyorsa kural, işlemi nasıl çalıştırır? Geçmişte, doğrulama işleminin boş bir dosya oluşturması ve bu çıktıyı derlemedeki diğer önemli işlemlerin girişlerine yapay olarak eklemesi yaklaşımı benimseniyordu:

Bazel, derleme işlemi çalıştırıldığında doğrulama işlemini her zaman çalıştıracağı için bu yöntem işe yarar. Ancak bu yöntemin önemli dezavantajları vardır:

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

  2. Derleme işlemi yerine derlemedeki diğer işlemler çalışabilirse doğrulama işlemlerinin boş çıkışlarının bu işlemlere de eklenmesi gerekir (örneğin, java_library kaynak jar çıkışı). Derleme işlemi yerine çalışabilecek yeni işlemler daha sonra eklenirse ve boş doğrulama çıkışı yanlışlıkla atlanırsa da bu bir sorundur.

Bu sorunların çözümü, Doğrulamalar Çıkış Grubu'nu kullanmaktır.

Doğrulamalar Çıkış Grubu

Doğrulama Çıktı Grubu, doğrulama işlemlerinin aksi takdirde kullanılmayan çı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 bağımlı olduğundan (örneğin, komut satırında, bağımlılık olarak veya hedefin gizli çıkışları aracılığıyla) bağımsız olarak çıkışlarının her zaman istenmesi açısından özeldir. Normal önbelleğe alma ve artımlılığın geçerliliğini koruduğunu unutmayın: Doğrulama işleminin girişleri değişmediyse ve doğrulama işlemi daha önce başarılı olduysa doğrulama işlemi çalıştırılmaz.

Bu çıkış grubunu kullanmak için doğrulama işlemlerinin boş olsa bile bir dosya oluşturması gerekir. Bu işlem, bir dosyanın oluşturulması için normalde çıkış oluşturmayan bazı araçların sarmalanmasını gerektirebilir.

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

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

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

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

Doğrulama Çıkış Grubu _validation olarak adlandırılır ve diğer tüm çı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'e veya başka bir işlemin girişlerine eklenmediğini unutmayın. Hedefin etikete bağlı olduğu veya hedefin örtülü çıkışlarından herhangi birine doğrudan ya da dolaylı olarak bağımlıysa bu kural türündeki bir hedefle ilgili doğrulama işlemi çalışmaya devam eder.

Paralellik kazançlarının ortadan kalkabileceği için doğrulama işlemlerinin çıktılarının genellikle yalnızca doğrulama çıkış grubuna gitmesi ve diğer işlemlerin girişlerine eklenmemesi önemlidir. Ancak, Bazel'in şu anda bunu uygulamak için özel bir denetimi olmadığını unutmayın. Bu nedenle, doğrulama işlemi çıkışlarının Starlark kurallarıyla ilgili testlerdeki işlemlerin 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ğerini alan --run_validations komut satırı işaretiyle kontrol edilir.

Kullanımdan kaldırılan özellikler

Desteği sonlandırılan önceden beyan edilmiş çıkışlar

Önceden tanımlanmış çıkışları kullanmanın iki desteği sonlandırılmış yolu vardır:

  • rule'ın outputs parametresi, önceden tanımlanmış çıkış etiketleri oluşturmak için çıkış özellik adları ile dize şablonları arasında bir eşleme belirtir. Önceden bildirilmemiş çıkışları kullanmayı ve çıkışları açıkça DefaultInfo.files öğesine eklemeyi tercih edin. Önceden tanımlanmış bir çıktının etiketi yerine, çıktıyı kullanan kurallar için kural hedefinin etiketini giriş olarak kullanın.

  • Yürütülebilir kurallar için ctx.outputs.executable, kural hedefiyle aynı ada sahip önceden tanımlanmış bir yürütülebilir çıkışa başvurur. Çıktıyı açıkça belirtmeyi tercih edin (ör. ctx.actions.declare_file(ctx.label.name) ile) ve yürütülebilir dosyayı oluşturan komutun izinlerini yürütmeye izin verecek şekilde ayarladığından emin olun. Yürütülebilir çıkışı DefaultInfo işlevinin executable parametresine açıkça iletin.

Kullanılmaması gereken çalışma dosyası özellikleri

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

  • ctx.runfiles collect_data ve collect_default modlarını kullanmaktan kaçının. Bu modlar, belirli kodlanmış bağımlılık kenarlarında çalışma dosyalarını dolaylı olarak kafa karıştırıcı şekillerde toplar. Bunun yerine, ctx.runfiles dosyasının files veya transitive_files parametrelerini kullanarak ya da bağımlılıklardaki çalışma dosyalarını runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) ile birleştirerek dosya ekleyin.

  • DefaultInfo kurucusunun data_runfiles ve default_runfiles özelliklerini kullanmaktan kaçının. Bunun yerine DefaultInfo(runfiles = ...) değerini belirtin. "Varsayılan" ve "veri" çalışma dosyaları arasındaki ayrım, eski nedenlerden dolayı korunur. Örneğin, bazı kurallar varsayılan çıkışlarını data_runfiles'e, ancak default_runfiles'e koymaz. Kurallar, data_runfiles yerine hem varsayılan çıkışları içermeli hem de çalıştırma dosyaları sağlayan özelliklerden (genellikle data) default_runfiles özelliğini birleştirmelidir.

  • runfilesDefaultInfo'dan alırken (genellikle yalnızca mevcut kural ile bağımlılıkları arasındaki çalıştırma dosyalarını birleştirmek için) DefaultInfo.data_runfiles'i değil DefaultInfo.default_runfiles kullanın.

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

Geçmişte Bazel sağlayıcıları Target nesnesindeki basit alanlardı. Nokta operatörü kullanılarak erişilen bu alanlar, alanın kuralın uygulama işlevi tarafından döndürülen bir yapıya yerleştirilmesiyle oluşturulmuştur.

Bu stilin desteği sonlandırılmıştır ve yeni kodlarda kullanılmamalıdır; taşıma işleminde size yardımcı olabilecek bilgiler için aşağıya bakın. Yeni sağlayıcı mekanizması, ad çakışmalarını önler. Ayrıca, sağlayıcı örneğine erişen kodların sağlayıcı sembolünü kullanarak almasını zorunlu kılarak veri gizlemeyi de destekler.

Şu an için, eski sağlayıcılar desteklenmektedir. Bir kural, aşağıdaki gibi hem eski hem de modern sağlayıcıları 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 elde edilen Target nesnesi dep ise sağlayıcılar ve içerikleri dep.legacy_info.x ve dep[MyInfo].y olarak alınabilir.

Döndürülen yapı, providers'e ek olarak özel anlamı olan (ve dolayısıyla ilgili eski sağlayıcıyı oluşturmayan) başka alanlar da alabilir:

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

  • output_groups alanı bir yapı değeri alır ve bir OutputGroupInfo değerine karşılık gelir.

Kuralların provides bildirimlerinde ve bağımlılık özelliklerinin providers bildirimlerinde eski sağlayıcılar dize olarak, modern sağlayıcılar ise *Info sembolleriyle iletilir. Taşıma işlemi sırasında dizelerden sembollere geçtiğinizden emin olun. Tüm kuralları atomik olarak güncellemenin zor olduğu karmaşık veya büyük kural kümeleri için aşağıdaki adımları uygulayarak daha kolay bir deneyim elde edebilirsiniz:

  1. Yukarıdaki söz dizimini kullanarak eski sağlayıcıyı oluşturan kuralları hem eski hem de modern sağlayıcıları oluşturacak şekilde değiştirin. Eski sağlayıcıyı döndürdüğünü belirten kurallarda, 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. Herhangi bir özellik beyanı eski sağlayıcıyı gerektiriyorsa bunları modern sağlayıcıyı gerektirecek şekilde de güncelleyin. İsteğe bağlı olarak, tüketicilerin hasattr(target, 'foo') kullanarak eski sağlayıcının varlığını veya FooInfo in target kullanarak yeni sağlayıcının varlığını test etmesini/sağlamasını zorunlu kılarak bu işlemi 1. adımla devam ettirebilirsiniz.

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