Kurallar

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

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

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

Herhangi bir kuralı oluşturmadan veya değiştirmeden önce Bazel'ın oluşturma aşamaları hakkında bilgi sahibi olduğunuzdan emin olun. Derlemenin üç aşamasını (yükleme, analiz ve yürütme) anlamak ö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ğiticisi'ni inceleyin. Ardından, bu sayfayı referans olarak kullanın.

Bazel'in kendisinde birkaç kural vardır. cc_library ve java_binary gibi bu yerel kurallar, belirli diller için bir miktar temel destek sağlar. Kendi kurallarınızı tanımlayarak Bazel'ın yerel olarak desteklemediği diller ve araçlar için benzer bir destek ekleyebilirsiniz.

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

Kendi kuralınızı tanımlarken, hangi özellikleri desteklediğine ve çıktıları nasıl ürettiğine karar vermeniz gerekir.

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, gerektiğinde kuralın çıkışlarını oluşturmak için yürütme aşamasında daha sonra kullanılacak işlemleri kaydeder.

Kural oluşturma

Bir .bzl dosyasında yeni bir kural tanımlamak için rule işlevini kullanın ve sonucu genel bir değişkende depolayı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.

Ayrıca, rule çağrısı, kuralın yürütülebilir bir çıkış mı (executable=True ile) yoksa özellikle yürütülebilir bir test mi (test=True ile) oluşturup oluşturmadığını belirtmelidir. İkincisi, kural bir test kuralıdır ve kuralın adı _test ile bitmelidir.

Hedef örneklendirme

Kurallar BUILD dosyalarına yüklenebilir ve ç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 yan etkiye sahiptir. Bu işleme, kuralı satma adı verilir. Bu, yeni hedef için bir ad ve hedefin özellikleri için değerleri belirtir.

Kurallar, Starlark işlevlerinden de çağrılabilir ve .bzl dosyalarına yüklenebilir. Kuralları çağıran Starlark işlevleri Starlark makroları olarak adlandırılır. Starlark makrolarının sonuçta BUILD dosyalarından çağrılması gerekir ve yalnızca yükleme aşamasında, BUILD dosyaları hedefleri örneklendirmek için değerlendirildiğinde çağrılabilir.

Özellikler

Özellik, bir kural bağımsız değişkenidir. Özellikler, bir hedefin uygulamasına belirli değerler sağlayabilir veya diğer 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şturulan) bir haritanın rule öğesinin attrs parametresine geçirilmesiyle tanımlanır. name ve visibility gibi yaygın özellikler, tüm kurallara dolaylı olarak eklenir. Ek özellikler, özellikle yürütülebilir ve test kurallarına dolaylı olarak eklenir. Dolaylı olarak bir kurala eklenen özellikler attrs hizmetine aktarılan sözlüğe dahil edilemez.

Bağımlılık özellikleri

Kaynak kodunu işleyen kurallar, genellikle çeşitli bağımlılık türlerini işlemek 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 dosyaları içeren dillere ilişkin kurallar, bir hedef ve tüketicileri tarafından işlenen başlıklar için genellikle 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ı sağlaması gerektiğini belirtmelidir. (Örneğin, cc_library, CcInfo değerini sağlar.)
  • data, çalışma zamanında bir hedefe bağlı olarak yürütülebilir herhangi bir dosya için kullanılabilir hale getirilecek 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. Giriş etiketi belirten tüm özellikler (attr.label_list, attr.label veya attr.label_keyed_string_dict ile tanımlananlar), hedef ile hedef tanımlandığında etiketleri (veya karşılık gelen Label nesneleri) o özellikte listelenen hedefler arasındaki belirli bir tür bağımlılıklarını belirtir. Bu etiketlere ilişkin kod 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 için bir bağımlılık olduğundan önce other_target analiz edilir. Hedeflerin bağımlılık grafiğinde bir döngü varsa bu bir hatadır.

Gizli özellikler ve örtülü 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 bir BUILD dosyasında belirtmediği hedef grafiğin bir parçası olduğu için örtülüdür. Kullanıcı çoğu zaman kuralın hangi aracı kullandığını belirtmekle ilgilenmediğinden, örtülü bağımlılıklar, bir kural ve araç arasındaki ilişkiyi (derleyici gibi bir derleme zamanı bağımlılığı) 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 dolaylı bir bağımlılık sağlamak isterseniz, özelliğe alt çizgiyle (_) başlayan bir ad vererek özelliği gizli yapabilirsiniz. Gizli özelliklerin varsayılan değerleri olmalıdır. Yalnızca örtülü bağımlılıklar için gizli özelliklerin kullanılması genellikle 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 hedefin //tools:example_compiler derleyicisine dolaylı bir bağımlılığı vardır. Bu şekilde, kullanıcı etiketini giriş olarak iletmemiş olsa bile example_library uygulama işlevinin, derleyiciyi çağıran işlemler oluşturmasına olanak tanır. _compiler gizli bir özellik olduğundan, bu kural türünün tüm hedeflerinde ctx.attr._compiler her zaman //tools:example_compiler öğesine işaret eder. Alternatif olarak, compiler özelliğini alt çizgi olmadan adlandırabilir ve varsayılan değeri koruyabilirsiniz. Bu, kullanıcıların gerekirse farklı bir derleyiciyi yerine koymasına olanak tanır, ancak derleyici etiketinin farkında olunmasını gerektirmez.

Örtülü bağımlılıklar genellikle kuralın uygulanmasıyla aynı depoda bulunan araçlar için kullanılır. Araç, yürütme platformundan veya farklı bir depodan geliyorsa kural, bu aracı bir araç zincirinden edinmelidir.

Çıkış özellikleri

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

  • Başka yerde tanımlanan hedeflere başvurmak yerine çıkış dosyası hedeflerini tanımlarlar.
  • Çıkış dosyası hedefleri, örnek oluşturulan kural hedefine bağlıdır, bunun tersi de geçerlidir.

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

Çıkış özellikleri, önceden tanımlanmış çıkışlar oluşturmak için tercih edilen yöntemdir. Bu çıkışlara özellikle bağlı olabilir veya komut satırından istenebilir.

Uygulama işlevi

Her kural için bir implementation işlevi gerekir. Bu işlevler sıkı bir şekilde analiz aşamasında yürütülür ve yükleme aşamasında oluşturulan hedeflerin 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 dosyaları okuyamaz veya yazamaz.

Kural uygulama işlevleri genellikle gizlidir (başta bir alt çizgiyle adlandırılır). Geleneksel olarak, bu kurallar kurallarıyla aynı şekilde adlandırılır ancak sonlarında _impl bulunur.

Uygulama işlevleri tam olarak bir parametre alır: yani geleneksel olarak ctx adlı bir kural bağlamı alır. Bunlar, bir sağlayıcı listesi döndürür.

Hedefler

Bağımlılıklar analiz sırası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 adına karşılık gelen alanlara sahiptir. Bu alanlar, bu özellik aracılığıyla her doğrudan bağımlılığı temsil eden Target nesnelerini içerir. Bu, label_list özellikleri için Targets listesidir. label özellikleri için bu tek bir Target veya None olur.

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

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

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

Örneğin, bir kural başlık dosyalarını hdrs özelliği aracılığıyla 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]

Bir struct öğesinin, sağlayıcı nesneleri listesi yerine bir hedef uygulama işlevinden 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 stil kesinlikle önerilmez ve kurallar bu stilden taşınmalıdır.

Files

Dosyalar File nesneleriyle temsil edilir. Bazel analiz aşamasında dosya G/Ç'si gerçekleştirmediğinden bu nesneler dosya içeriğini doğrudan okumak veya yazmak için kullanılamaz. Bunun yerine, eylem grafiğinin parçalarını oluşturmak için işlem yayınlayan işlevlere (bkz. ctx.actions) aktarılırlar.

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

Her bağımlılık özelliği için ctx.files öğesinin ilgili alanı, bu özellik üzerinden 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 ayarlanmış bağımlılık özellikleri için tek bir File veya None içerir. ctx.executable, ctx.file ile aynı şekilde davranır ancak yalnızca özellikleri executable=True olarak ayarlanan bağımlılık özellikleri için alanlar içerir.

Çıkışları bildirme

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

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

Bunun yerine, çıkış özellikleri için oluşturulanlar gibi önceden tanımlanmış çıkışlarda File nesneleri, karşılık gelen ctx.outputs alanlarından alınabilir.

İşlemler

Bir işlem, bir giriş kümesinden nasıl çıkış kümesi oluşturulacağını açıklar (örneğin, "hello.c'de gcc'yi çalıştırın ve hello.o alın"). Bir işlem oluşturulduğunda Bazel komutu hemen çalıştırmaz. Bir eylem başka bir eylemin sonucuna bağlı olabileceğinden, değeri bir 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'da tanımlanmıştır:

ctx.actions.args, işlemlerle ilgili bağımsız değişkenleri verimli bir şekilde toplamak için kullanılabilir. Yürütme zamanına kadar ayrıntılandırmaların azalmasını ö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 bir listesini veya kümesini kaldırır ve çıkış dosyalarının (boş olmayan) bir listesini oluşturur. Giriş ve çıkış dosyaları grubu, analiz aşamasında bilinmelidir. Bağımlılıkların sağlayıcıları dahil olmak üzere özelliklerin değerine bağlı olabilir ancak yürütme işleminin sonucuna bağlı olamaz. Örneğin, işleminiz "zip açma" komutunu çalıştırıyorsa hangi dosyaların şişirilmesini beklediğinizi (sıkıştırmayı açma işleminden önce) belirtmeniz gerekir. Dahili olarak değişken sayıda dosya oluşturan işlemler, bu dosyaları tek bir dosyada (zip, tar veya başka bir arşiv biçimi gibi) sarmalayabilir.

İşlemlerde tüm girişler listelenmelidir. Kullanılmayan girişlerin listelenmesine izin verilir ancak verimsizdir.

İşlemler tüm çıktılarını oluşturmalıdır. Başka dosyalar yazabilirler, ancak çıkışlarda olmayan hiçbir şey tüketiciler tarafından kullanılamaz. Beyan edilen tüm çıkışlar bir işlem aracılığıyla yazılmalıdır.

İşlemler, saf işlevlere benzerdir: Yalnızca sağlanan girişlere bağlı olmalı ve bilgisayar bilgilerine, kullanıcı adı, saat, ağ veya G/Ç cihazlarına (girişleri okuma ve yazma çıkışları hariç) erişmekten kaçınmalıdır. Çıkış önbelleğe alınıp yeniden kullanılacağından bu önemli bir husustur.

Bağımlılıklar, hangi işlemlerin yürütüleceğine karar veren Bazel tarafından çözümlenir. Bağımlılık grafiğinde bir döngü varsa bu bir hatadır. Bir işlem oluşturmak 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ına aktarılacak parametreler veya hedef tüketicilerinin bilmesi gereken başka her şey bulunabilir.

Kuralın uygulama işlevi, sağlayıcıları yalnızca desteklenen hedefin anlık bağımlılıklarından okuyabildiğinden kuralların, hedefin tüketicileri tarafından bilinmesi gereken hedef bağımlılıklarındaki tüm bilgileri, genellikle bunları bir depset içinde toplayarak yönlendirmesi gerekir.

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

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

Varsayılan çıkışlar

Hedeflerin 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 hedefinin //pkg:foo varsayılan çıkışı foo.jar olur, bu nedenle bu değer bazel build //pkg:foo komutuyla oluşturulur.

Varsayılan çıkışlar DefaultInfo files parametresiyle 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 tanımlanmış çıkışlara (genellikle çıkış özellikleri tarafından oluşturulanlar) ayarlanır.

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

Çalıştırma dosyaları

Çalıştırma dosyaları, çalışma zamanında (derleme zamanı yerine) hedef tarafından kullanılan bir dosya kümesidir. 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, ikili programın ortamını aşamalayarak çalışma sırasında çalıştırma dosyalarına erişebilmesini sağlar.

Çalıştırma dosyaları, kural oluşturma sırasında manuel olarak eklenebilir. runfiles nesneleri, ctx.runfiles kural bağlamında runfiles yöntemiyle oluşturulabilir ve DefaultInfo üzerindeki runfiles parametresine aktarılabilir. Yürütülebilir kuralların yürütülebilir çıkışı, çalıştırma dosyalarına dolaylı olarak eklenir.

Bazı kurallar, çıkışları bir hedeflerin çalıştırma dosyalarına eklenen özellikleri (genellikle data) belirtir. Çalıştırma dosyaları hem data hem de nihai yürütme için kod sağlayabilecek tüm özelliklerden (genellikle srcs (ilişkili data ile filegroup hedefleri içerebilir) ve deps) 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şlevini kullanarak 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.",
    })

Daha sonra kural uygulama işlevleri 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ı özel başlatma

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

Bu, provider işlevine bir init geri çağırması geçirilerek yapılır. Bu geri çağırma sağlanırsa provider() özelliğinin dönüş türü, iki değerden oluşan bir unsur olacak şekilde değişir: init kullanılmadığında sıradan döndürülen değer olan sağlayıcı simgesi ve "ham yapıcı".

Bu durumda, sağlayıcı simgesi çağrıldığında, doğrudan yeni bir örnek döndürmek yerine bağımsız değişkenleri init geri çağırmasına yönlendirir. Geri çağırmanın döndürdüğü değer, değerler ile dikte eşleme alan adları (dizeler) olmalıdır. Bu değer, yeni örneğin alanlarını başlatmak için kullanılır. Geri çağırmanın herhangi bir imzası olabileceğini ve bağımsız değişkenler imzayla eşleşmiyorsa geri çağırma doğrudan çağrılmış gibi bir hata bildirileceğini unutmayın.

Buna karşın ham oluşturucu, init geri çağırmasını atlar.

Aşağıdaki örnekte, bağımsız değişkenlerini önceden işlemek ve doğrulamak için init kullanılmaktadı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

Bir kural uygulaması, sağlayıcıyı aşağıdaki şekilde örneklendirebilir:

    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ığıyla çalışmayan alternatif genel 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)

Genellikle ham oluşturucu, adı alt çizgiyle (yukarıda _new_exampleinfo) başlayan bir değişkene bağlıdır. Bu nedenle, kullanıcı kodu bu değişkeni yükleyemez ve rastgele sağlayıcı örnekleri oluşturamaz.

init işlevinin bir başka kullanımı 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ılabilecek hedefleri tanımlar. Test kuralları, hedefleri bir bazel test komutuyla da çağırılabilen özel bir yürütülebilir kural türüdür. Yürütülebilir kurallar ve test kuralları, rule çağrısında ilgili executable veya test bağımsız değişkeni True değerine 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 kurala göre _test ile biter, ancak bu zorunlu değildir.) Test dışı kurallarda bu son ek bulunmamalıdır.

Her iki kural türü de run veya test komutları ile çağrılacak yürütülebilir bir çıkış dosyası (önceden tanımlanmış veya olmayabilir) üretmelidir. Bazel'a bir kural çıkışlarının hangisinin bu yürütülebilir dosya olarak kullanılacağını bildirmek için bunu, döndürülen bir 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 öğesine iletmeniz gerekmez). Ayrıca dolaylı olarak runfiles'a da eklenir:

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

Bu dosyayı oluşturan işlem, dosyadaki yürütülebilir biti 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 geçin.

Eski davranış nedeniyle yürütülebilir kurallar, önceden tanımlanmış özel bir ctx.outputs.executable çıkışına sahiptir. Bu dosya, DefaultInfo kullanarak bir dosya belirtmezseniz varsayılan yürütülebilir dosya olarak işlev görür. Başka şekilde kullanılmamalıdır. Bu çıkış mekanizması, analiz sırasında yürütülebilir dosyanın adının özelleştirilmesini desteklemediğinden 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ının tüm kurallar için eklenenlere ek olarak dolaylı olarak tanımlanmış ek özellikleri de vardır. Dolaylı olarak eklenen özelliklerin varsayılanları değiştirilemez, ancak bir özel kuralı bir Starlark makrosu içinde sarmalayarak bu durum düzeltilebilir. Bu makro, varsayılan değeri değiştirir:

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 çalıştırma dosyaları dizininin kökü, yürütülebilir dosyanın hemen yanında olur. 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 program, runfiles dizininin kök dizininin bitişiğindedir. Bununla birlikte, runfiles ile çağrılan ikili programlar aynı varsayımı yapamaz. Bu sorunu gidermek için her ikili program, ortam veya komut satırı bağımsız değişkeni/bayrak kullanarak parametre olarak çalıştırma dosyaları kökünü kabul edecek bir yöntem sağlamalıdır. Bu, ikili programların doğru standart çalıştırma dosyaları kökünü çağırdığı ikili programlara geçirmesini sağlar. Politika ayarlanmazsa ikili program, çağrılan ilk ikili programın çağrılduğunu tahmin edebilir ve bitişik bir runfiles dizinini arayabilir.

İleri düzey konular

Çıkış dosyaları isteme

Tek bir hedefin birkaç çı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 bunların doğrudan veya dolaylı olarak bağımlı oldukları dosyaları oluşturur. (Eylem grafiğine bakacak olursak, Bazel yalnızca istenen dosyaların geçişli bağımlılıkları olarak erişilebilir olan işlemleri yürütür.)

Varsayılan çıkışlara ek olarak, önceden tanımlanmış tüm çıkışlar komut satırında açıkça istenebilir. Kurallar, çıkış özellikleri aracılığıyla önceden tanımlanmış çıkışları belirtebilir. Bu durumda kullanıcı, kuralı örnek verirken çıkışlar için etiketleri açık bir şekilde seçer. Çıkış özellikleri için File nesneleri elde etmek üzere karşılık gelen ctx.outputs özelliğini kullanın. Kurallar, hedef adına göre önceden tanımlanmış çıkışları da dolaylı olarak tanımlayabilir, ancak bu özellik kullanımdan kaldırılmıştır.

Varsayılan çıkışlara ek olarak, birlikte istenebilecek çıkış dosyası koleksiyonları olan çıkış grupları da vardır. Bunlar --output_groups ile istenebilir. Örneğin, 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. Ön bildirilmemiş çıkışların etiketleri olmadığından, bu çıktılar yalnızca varsayılan çıkışlarda veya bir çıkış grubunda görünerek istenebilir.

Çıkış grupları OutputGroupInfo sağlayıcısı ile belirtilebilir. Birçok yerleşik sağlayıcının aksine OutputGroupInfo işlevinin, şu ada sahip çıkış grupları tanımlamak için rastgele adlara sahip parametreleri 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 OutputGroupInfo, aynı çıkış gruplarını tanımlamadıkları sürece hem bir boyut hem de bu özelliğin uygulandığı kural hedefi tarafından döndürülebilir. Bu durumda, elde edilen sağlayıcılar birleştirilir.

OutputGroupInfo işlevinin genellikle bir hedeften, tüketicilerin eylemlerine belirli türdeki dosyaları aktarmak için kullanılmaması gerektiğini unutmayın. Bunun yerine bunun için kurala özel sağlayıcılar tanımlayın.

Yapılandırmalar

Farklı bir mimari için bir C++ ikili programı 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 programların, yürütme platformunda (ana makineniz veya uzak yürütücü olabilir) çalışması gerekir. Nihai çıkış gibi bazı ikili programların hedef mimari için oluşturulması gerekir.

Bu nedenle, Bazel "yapılandırmalar" ve geçişler kavramına sahiptir. En üstteki hedefler (komut satırında istenenler) "hedef" yapılandırmasında, yürütme platformunda çalışması 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 aktarılan CPU mimarisini değiştirmek için). Bazı durumlarda, farklı yapılandırmalar için aynı kitaplık gerekebilir. Böyle bir durumda, mülk analiz edilir ve potansiyel olarak birkaç kez oluşturulur.

Varsayılan olarak Bazel, hedefin bağımlılıklarını hedefin kendisiyle aynı yapılandırmada, diğer bir deyişle geçiş olmadan oluşturur. Bağımlılık, hedefin oluşturulmasına yardımcı olmak için ihtiyaç duyulan bir araç olduğunda, ilgili özellik exec yapılandırmasına geçişi belirtmelidir. Bu, aracın ve tüm bağımlılıklarının yürütme platformu için derlemesine neden olur.

Her bir bağımlılık özelliğinde, bağımlılıkların aynı yapılandırmada derlenmesi veya exec yapılandırmasına geçiş yapması gerekip gerekmediğine karar vermek için cfg kullanabilirsiniz. Bir bağımlılık özelliği executable=True işaretine sahipse cfg açıkça ayarlanmalıdır. Bu, bir aracın yanlış yapılandırmaya yönelik olarak yanlışlıkla oluşturulmasını önlemek içindir. Örneği inceleyin

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

Derlemenin bir parçası olarak yürütülen araçlar (derleyiciler veya kod oluşturucu gibi) bir yönetici 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 yürütülebilir dosyalar (ör. bir testin parçası) 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 niyetlerini açık bir şekilde ifade etmelerine yardımcı olmak açısından tümüyle kolaylık sağlar. executable=False (cfg) isteğe bağlı olduğunda, bunu yalnızca okunabilirliğe gerçekten yardımcı olduğunda ayarlayın.

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

Not: Daha önce, Bazel'da yürütme platformu kavramı yoktu. Tüm derleme işlemlerinin ana makinede çalışacağı kabul ediliyordu. Bu nedenle, ana makine yapılandırmasında bir bağımlılık oluşturmak için kullanılabilecek tek bir "ana makine" yapılandırması ve bir "ana makine" geçişi vardır. Birçok kural, araçları için "ana makine" geçişini kullanmaya devam etmektedir ancak bu geçiş, şu anda kullanımdan kaldırılmıştır ve mümkün olduğunda "exec" geçişlerini kullanmak üzere taşınmaktadır.

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

  • "host" terminaldir, "exec" değildir: "Ana makine" yapılandırmasında bir bağımlılık olduğunda, daha fazla geçişe izin verilmez. "Yürütme" yapılandırması aldığınızda, daha fazla yapılandırma geçişi yapmaya devam edebilirsiniz.
  • "host" monolitiktir, "exec" değildir: Yalnızca bir "ana makine" yapılandırması vardır ancak her yürütme platformu için farklı bir "exec" yapılandırması olabilir.
  • "ana makine", araçları Bazel ile aynı makinede veya çok benzer bir makinede çalıştırdığınızı varsayar. Bu artık geçerli değil: Derleme işlemlerini yerel makinenizde veya uzak yürütücüde çalıştırabilirsiniz. Uzaktan yürütücünün, yerel makinenizle aynı CPU ve işletim sistemi olacağının garantisi yoktur.

Hem "exec" hem de "host" yapılandırmaları aynı seçenek değişikliklerini uygular (örneğin, --compilation_mode değerini --host_compilation_mode, --host_cpu değerini --cpu olarak ayarlama vb.). Aralarındaki fark, "host" yapılandırmasının diğer tüm işaretlerin default değerleriyle başlaması, "exec" 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. Bununla birlikte, erişim hatalarını önlemek için gerekli tüm parçalar bildirilmelidir:

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 işlevini kullanın.

Normalde bir dosyanın çalıştırma dosyaları ağacındaki göreli yolu, kaynak ağaçtaki veya oluşturulan çıkış ağacındaki ilgili dosyanın yoluyla aynıdır. Herhangi bir nedenle bunların farklı olması gerekirse root_symlinks veya symlinks bağımsız değişkenlerini belirtebilirsiniz. root_symlinks, dosyalara yönlendiren bir sözlük eşlemedir. Yollar, runfiles dizininin köküyle görelidir. symlinks sözlüğü aynıdır ancak yollara dolaylı olarak çalışma alanının adı 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ı runfiles ağacında aynı yola eşlememeye dikkat edin. Bu durumda derleme, çakışmayı açıklayan bir hata mesajı vererek başarısız olur. Sorunu düzeltmek için ctx.runfiles bağımsız değişkenlerinizi değiştirerek çakışmayı kaldırmanız gerekir. Bu kontrol, kuralınızı kullanan tüm hedeflerin yanı sıra bu hedeflere bağlı herhangi bir türdeki hedefler için yapılır. Bu durum, özellikle aracınızın başka bir araç tarafından geçişli olarak kullanılması ihtimali olduğunda risklidir. Sembolik bağlantı adları, aracın çalıştırma 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 araçları eklemesi gerekebilir. Derleme aynı zamanda, izleme uygulanmış kaynak dosyaların listesini de toplar. Dikkate alınan hedef alt grubu, işaret --instrumentation_filter tarafından kontrol edilir. --instrument_test_targets belirtilmediği sürece test hedefleri hariç tutulur.

Bir kural uygulaması, derleme zamanında kapsam araçları eklerse bunu uygulama işlevinde hesaba katması gerekir. ctx.coverage_instrumented, hedef kaynaklarının izlenmesi 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 (hedef kaynaklarının özel olarak kullanılıp kullanılmadığı fark etmeksizin) ctx.configuration.coverage_enabled değerine koşullandırılabilir.

Kural, derlemeden önce bağımlılıklarından gelen kaynakları (başlık dosyaları gibi) doğrudan içeriyorsa, bağımlılıkların kaynaklarının ayarlanması gerekiyorsa derleme zamanı enstrümantasyonunun da etkinleştirilmesi 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, InstrumentedFilesInfo sağlayıcısının kapsamıyla ilgili olan ve coverage_common.instrumented_files_info kullanılarak oluşturulan özellikler hakkında da 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ı 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ülmezsedependency_attributes içindeki cfg özelliği "host" veya "exec" olarak ayarlanmamış her araç dışı bağımlılık özelliğiyle bir varsayılan özellik oluşturulur. (Bu, srcs gibi özellikleri source_attributes yerine dependency_attributes içine yerleştirdiği için ideal bir davranış değildir ancak bağımlılık zincirindeki tüm kurallar için açık kapsam yapılandırması ihtiyacını ortadan kaldırır.)

Doğrulama İşlemleri

Bazen derlemeyle ilgili bir şeyi doğrulamanız gerekir ve bu doğrulama için gereken bilgiler yalnızca yapılarda (kaynak dosyalar veya oluşturulan dosyalar) bulunur. Bu bilgiler yapılarda bulunduğundan, kurallar dosyaları okuyamadığı için kurallar analiz sırasında bu doğrulamayı yapamaz. Bunun yerine, bu doğrulamayı yürütme sırasında işlemler yapmalıdır. Doğrulama başarısız olduğunda işlem 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.

Ayrıca doğrulama işlemleri, yapıları derlemek için gerekli olmayan işlemlerin parçalarını ayrı işlemlere taşıyarak derleme performansının iyileştirilmesine de yardımcı olabilir. Örneğin, derleme ve hata ayıklama yapan tek bir işlem, bir derleme işlemi ve bir hata ayıklama işlemine ayrılabiliyorsa bu işlem, doğrulama işlemi olarak çalıştırılabilir ve diğer işlemlere paralel olarak çalıştırılabilir.

Bu "doğrulama işlemleri", yalnızca girdileri hakkında bir şeyler kanıtlamaları gerektiğinden genellikle derlemenin başka bir yerinde kullanılan bir şey üretmez. Ancak bu durum bir soruna neden olur: Doğrulama işlemi, derlemenin başka bir yerinde kullanılan bir şey üretmezse kural, işlemin çalıştırılmasını nasıl sağlar? Geçmişte yaklaşım, doğrulama işleminin boş bir dosya çıktısı vermesi ve bu çıktıyı derlemedeki başka bir önemli işlemin girişlerine yapay olarak eklemekti:

Bu işe yarar çünkü Bazel, derleme işlemi her çalıştırıldığında doğrulama işlemini her zaman çalıştırır. Ancak bunun önemli dezavantajları vardır:

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

  2. Derlemedeki derleme işlemi yerine başka işlemler çalıştırılabiliyorsa bu işlemlere, doğrulama işlemlerinin boş çıkışlarının da (örneğin, java_library kaynak jar çıkışı) eklenmesi gerekir. Bu durum, daha sonra derleme işlemi yerine çalıştırılabilecek yeni işlemlerin eklenmesi ve boş doğrulama çıkışının yanlışlıkla kaybedilmesi durumunda da bir sorun teşkil eder.

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

Doğrulamalar Çıktı Grubu

Doğrulama Çıktısı Grubu, doğrulama işlemlerinin başka şekilde kullanılmayan çıkışlarını, diğer işlemlerin girişlerine yapay olarak eklenmelerini gerektirmeyecek şekilde tutmak için tasarlanmış bir çıkış grubudur.

Bu grup, --output_groups işaretinin değerinden bağımsız olarak ve hedefin nasıl bağlı olduğundan bağımsız olarak (örneğin, komut satırında, bağımlılık olarak veya hedefin örtülü çıkışları aracılığıyla) çıktılarının her zaman istenmesi açısından özeldir. Normal önbelleğe alma ve artımlılığın hâlâ geçerli olduğunu 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ış grubunu kullanmak için doğrulama işlemlerinin, boş bir dosya da dahil olmak üzere bazı dosyalar üretmesi gerekir. Bu, bir dosyanın oluşturulması için normalde çıkış oluşturmayan bazı araçların sarmalanmasını gerektirebilir.

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

  • Hedefin bir araç olarak kullanılması
  • Hedef, dolaylı bir bağımlılık olarak (ör. "_" ile başlayan bir özellik) bağımlı olduğunda
  • Hedef, ana makinede veya yönetici yapılandırmasında oluşturulduğunda.

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

Doğrulama Çıktı Grubunu Kullanma

Doğrulama Çıkış Grubu _validation olarak adlandırılmıştır ve diğer herhangi bir çıkış grubu 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 öğesine veya girişlerin başka herhangi bir işleme eklenmediğine dikkat edin. Etikete bağlı bir hedef varsa veya hedefin örtülü çıkışlarından herhangi birine doğrudan ya da dolaylı olarak bağımlıysa bu kural türündeki bir hedef için doğrulama işlemi yine de çalışır.

Doğrulama işlemlerinin çıkışlarının yalnızca doğrulama çıkış grubuna girmesi ve diğer işlemlerin girişlerine eklenmez çünkü bu, paralellik kazançlarını ortadan kaldırabileceğinden genellikle önemlidir. Ancak, şu anda Bazel'ın bunu uygulamak için herhangi bir özel denetimi yoktur. Bu nedenle, Starlark kuralları testlerindeki hiçbir işlemin girişlerine doğrulama işlemi çıkışlarının 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 İşlemi İşareti

Çalıştırılan doğrulama işlemleri --run_validations komut satırı işareti tarafından kontrol edilir. Bu işaret varsayılan olarak true değerini alır.

Desteği sonlandırılan özellikler

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

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

  • rule öğesinin outputs parametresi, önceden tanımlanmış çıkış etiketleri oluşturmak için çıkış özelliği adları ile dize şablonları arasındaki eşlemeyi belirtir. Önceden bildirilmemiş çıkışları kullanmayı ve çıkışları açıkça DefaultInfo.files öğesine eklemeyi tercih edin. Önceden tanımlanmış bir çıkışın etiketi yerine, çıktıyı tüketen 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ış yürütülebilir bir çıktıyı ifade eder. Çıkışı, örneğin ctx.actions.declare_file(ctx.label.name) gibi açıkça belirtmeyi tercih edin ve yürütülebilir dosyayı oluşturan komutun, izinleri yürütülebilir şekilde ayarladığından emin olun. Yürütülebilir çıkışı açık bir şekilde DefaultInfo öğesinin executable parametresine iletin.

Kaçınılması gereken Runfiles özellikleri

ctx.runfiles ve runfiles türü, çoğu eski nedenlerden dolayı saklanan 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ı kullanmayın. Bu modlar, çalıştırma dosyalarını belirli kod gömülü bağımlılık kenarlarında örtülü biçimde ve kafa karıştırıcı şekillerde toplar. Bunun yerine, ctx.runfiles öğesinin files veya transitive_files parametrelerini kullanarak veya bağımlılıklardaki çalıştırma dosyalarını runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) ile birleştirerek dosya ekleyin.

  • DefaultInfo oluşturucunun data_runfiles ve default_runfiles öğelerini kullanmaktan kaçının. Bunun yerine DefaultInfo(runfiles = ...) değerini belirtin. "Varsayılan" ve "veri" çalıştırma dosyaları arasındaki fark, eski nedenlerden dolayı korunmaktadır. Örneğin, bazı kurallar varsayılan çıkışlarını data_runfiles içine yerleştirir, ancak default_runfiles öğesine yerleştirmez. Kurallar, data_runfiles kullanmak yerine her ikisi de varsayılan çıkışları içermeli ve runfiles (genellikle data) sağlayan özelliklerden default_runfiles ile birleştirilmelidir.

  • DefaultInfo öğesinden runfiles alırken (genellikle yalnızca mevcut kural ile bağımlılıkları arasında çalıştırma dosyalarını birleştirmek için) DefaultInfo.default_runfiles kullanın, kullanmayın DefaultInfo.data_runfiles kullanmayın.

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

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

Bu stil kullanımdan kaldırıldı 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, sağlayıcı örneğine erişen herhangi bir kodun 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, hem eski hem de modern sağlayıcıları aşağıdaki gibi 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, ...])

dep, bu kuralın bir örneğinde sonuç olarak ortaya çıkan Target nesnesiyse sağlayıcılar ve bunların içerikleri dep.legacy_info.x ve dep[MyInfo].y olarak alınabilir.

providers'a ek olarak, döndürülen struct'ın özel anlamı olan başka alanları da alabilir (dolayısıyla karşılık gelen bir eski sağlayıcı oluşturmaz):

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

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

provides kural bildirimlerinde ve providers bağımlılık özellikleri bildirimlerinde eski sağlayıcılar dize olarak aktarılırken, modern sağlayıcılara *Info sembolü eklenir. Taşıma sırasında dizelerden simgelere geçtiğinizden emin olun. Tüm kuralları anatomik olarak güncellemenin zor olduğu karmaşık veya büyük kural grupları için aşağıdaki adımları uygulayarak daha kolay işlem yapabilirsiniz:

  1. Eski sağlayıcıyı üreten kuralları, yukarıdaki söz dizimini kullanarak 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 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ı, bunun yerine modern sağlayıcıyı kullanacak şekilde değiştirin. Herhangi bir özellik bildirimleri için eski sağlayıcı gerekiyorsa bunları da modern sağlayıcıyı gerektirecek şekilde güncelleyin. İsteğe bağlı olarak, tüketicilerin şu sağlayıcıyı kabul etmesini/zorunlu kılmasını sağlayarak bu çalışmayı 1. adımla ekleyebilirsiniz: hasattr(target, 'foo') kullanarak eski sağlayıcının varlığını test etme veya FooInfo in target ifadesini kullanarak yeni sağlayıcının varlığını test etme.

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