Kurallar Eğiticisi

Sorun bildirin Kaynağı göster

Starlark, ilk olarak Bazel'de kullanılmak üzere geliştirilen ve diğer araçlar tarafından benimsenen Python benzeri bir yapılandırma dilidir. Bazel'in BUILD ve .bzl dosyaları Starlark'ın "Derleme Dili" diyalektinde yazılmıştır. Ancak bu dil, özellikle bir özelliğin Derleme Dili'nde ifade edildiğini belirtirken özellikle Bazel'in yerleşik veya "yerel" bir parçası olmak üzere sadece "Starlark" olarak anılır. Bazel, temel dili glob, genrule, java_binary gibi derlemeyle ilgili çok sayıda işlevle zenginleştirir.

Daha fazla bilgi için Bazel ve Starlark belgelerini, yeni kural kümeleri için başlangıç noktası olarak Kurallar SIG şablonunu inceleyin.

Boş kural

İlk kuralınızı oluşturmak için foo.bzl dosyasını oluşturun:

def _foo_binary_impl(ctx):
    pass

foo_binary = rule(
    implementation = _foo_binary_impl,
)

rule işlevini çağırırken bir geri çağırma işlevi tanımlamanız gerekir. Mantık burada görünecektir, ancak fonksiyonu şimdilik boş bırakabilirsiniz. ctx bağımsız değişkeni, hedef hakkında bilgi sağlar.

Kuralı yükleyip bir BUILD dosyasından kullanabilirsiniz.

Aynı dizinde bir BUILD dosyası oluşturun:

load(":foo.bzl", "foo_binary")

foo_binary(name = "bin")

Artık hedef oluşturulabilir:

$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)

Kural hiçbir şey yapmasa da zaten diğer kurallar gibi davranır: Zorunlu bir adı vardır, visibility, testonly ve tags gibi ortak özellikleri destekler.

Değerlendirme modeli

Devam etmeden önce kodun nasıl değerlendirildiğini anlamanız önemlidir.

foo.bzl öğesini bazı basılı ifadelerle güncelleyin:

def _foo_binary_impl(ctx):
    print("analyzing", ctx.label)

foo_binary = rule(
    implementation = _foo_binary_impl,
)

print("bzl file evaluation")

ve BUILD:

load(":foo.bzl", "foo_binary")

print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")

ctx.label, analiz edilen hedefin etiketine karşılık gelir. ctx nesnesinde birçok yararlı alan ve yöntem bulunur. Kapsamlı bir listeyi API referansında bulabilirsiniz.

Kodu sorgulayın:

$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1

Birkaç gözlem yapın:

  • Önce "bzl dosya değerlendirmesi" yazdırılır. BUILD dosyasını değerlendirmeden önce, Bazel yüklediği tüm dosyaları değerlendirir. Birden fazla BUILD dosyası foo.bzl dosyası yükleniyorsa "bzl dosya değerlendirmesi" ibaresi yalnızca bir kez gösterilir. Bunun nedeni, Bazel'in değerlendirme sonucunu önbelleğe almasıdır.
  • _foo_binary_impl geri çağırma işlevi çağrılmadı. Bazel sorgusu, BUILD dosyalarını yükler ancak hedefleri analiz etmez.

Hedefleri analiz etmek için cquery ("yapılandırılmış sorgu") veya build komutunu kullanın:

$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...

Gördüğünüz gibi _foo_binary_impl artık her hedef için bir kez olmak üzere iki kez çağrılıyor.

foo.bzl değerlendirmesi, bazel query çağrısından sonra önbelleğe alındığından ne "bzl dosya değerlendirmesi" ne de "BUILD file" yeniden yazdırılmaz. Bazel, print ifadelerini yalnızca yürütüldüklerinde yayınlar.

Dosya oluşturma

Kuralınızı daha kullanışlı hale getirmek için, dosya oluşturacak şekilde güncelleyin. İlk olarak, dosyayı tanımlayın ve bir ad verin. Bu örnekte, hedefle aynı ada sahip bir dosya oluşturun:

ctx.actions.declare_file(ctx.label.name)

bazel build :all öğesini şimdi çalıştırırsanız bir hata alırsınız:

The following files have no generating action:
bin2

Her dosya bildirdiğinizde, bir işlem oluşturarak Bazel'a dosyayı nasıl oluşturacağını bildirmeniz gerekir. Belirtilen içeriğe sahip bir dosya oluşturmak için ctx.actions.write işlevini kullanın.

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello\n",
    )

Kod geçerli olsa da hiçbir işlevi olmaz:

$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)

ctx.actions.write işlevi, Bazel'a dosyanın nasıl oluşturulacağını öğreten bir işlem kaydetti. Ancak, Bazel dosya gerçekten talep edilene kadar dosya oluşturmaz. Dolayısıyla yapılacak son şey, Bazel'a dosyanın kural uygulamasında kullanılan geçici bir dosya değil, kuralın bir çıktısı olduğunu söylemektir.

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello!\n",
    )
    return [DefaultInfo(files = depset([out]))]

DefaultInfo ve depset işlevlerine daha sonra göz atın. Şimdilik son satırın bir kuralın çıktılarını seçme yolu olduğunu varsayalım.

Şimdi, Bazel'i çalıştırın:

$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
  bazel-bin/bin1

$ cat bazel-bin/bin1
Hello!

Dosyayı başarıyla oluşturdunuz.

Özellikler

Kuralı daha kullanışlı hale getirmek için attr modülünü kullanarak yeni özellikler ekleyin ve kural tanımını güncelleyin.

username adlı bir dize özelliği ekleyin:

foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "username": attr.string(),
    },
)

Daha sonra, BUILD dosyasında ayarlayın:

foo_binary(
    name = "bin",
    username = "Alice",
)

Geri çağırma işlevindeki değere erişmek için ctx.attr.username işlevini kullanın. Örneğin:

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello {}!\n".format(ctx.attr.username),
    )
    return [DefaultInfo(files = depset([out]))]

Özelliği zorunlu hale getirebileceğinizi veya varsayılan bir değer ayarlayabileceğinizi unutmayın. attr.string belgelerine bakın. boolean veya tam sayı listesi gibi başka özellik türlerini de kullanabilirsiniz.

Bağımlılıklar

attr.label ve attr.label_list gibi bağımlılık özellikleri, özelliğe sahip olan hedeften, etiketi özelliğin değerinde görünen hedefe olan bağımlılığı bildirir. Bu tür bir özellik, hedef grafiğin temelini oluşturur.

BUILD dosyasında hedef etiket, //pkg:name gibi bir dize nesnesi olarak görünür. Uygulama işlevinde, hedefe bir Target nesnesi olarak erişilebilir. Örneğin, Target.files kullanılarak hedef tarafından döndürülen dosyaları görüntüleyebilirsiniz.

Birden fazla dosya

Varsayılan olarak, yalnızca kurallar tarafından oluşturulan hedefler bağımlılık olarak görünebilir (foo_library() hedefi gibi). Özelliğin giriş dosyaları (depodaki kaynak dosyalar gibi) olan hedefleri kabul etmesini istiyorsanız bunu allow_files ile yapıp kabul edilen dosya uzantılarının listesini (veya herhangi bir dosya uzantısına izin vermek için True) belirtebilirsiniz:

"srcs": attr.label_list(allow_files = [".java"]),

Dosya listesine ctx.files.<attribute name> ile erişilebilir. Örneğin, srcs özelliğindeki dosya listesine

ctx.files.srcs

Tek dosya

Yalnızca bir dosyaya ihtiyacınız varsa allow_single_file uzantısını kullanın:

"src": attr.label(allow_single_file = [".java"])

Daha sonra bu dosyaya ctx.file.<attribute name> altından erişilebilir:

ctx.file.src

Şablon kullanarak dosya oluşturma

Şablona dayalı .cc dosyası oluşturan bir kural oluşturabilirsiniz. Ayrıca, kural uygulama işlevinde oluşturulan bir dize üretmek için ctx.actions.write kullanabilirsiniz. Ancak bunun iki sorunu vardır. İlk olarak, şablon büyüdükçe, bunu ayrı bir dosyaya yerleştirmek ve analiz aşamasında büyük dizeler oluşturmaktan kaçınmak bellek daha verimli hale gelir. İkinci olarak, ayrı bir dosya kullanmak kullanıcı için daha uygun olur. Bunun yerine, bir şablon dosyasında değişiklikler gerçekleştiren ctx.actions.expand_template işlevini kullanın.

Şablon dosyasına bir bağımlılığı bildirmek için template özelliği oluşturun:

def _hello_world_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + ".cc")
    ctx.actions.expand_template(
        output = out,
        template = ctx.file.template,
        substitutions = {"{NAME}": ctx.attr.username},
    )
    return [DefaultInfo(files = depset([out]))]

hello_world = rule(
    implementation = _hello_world_impl,
    attrs = {
        "username": attr.string(default = "unknown person"),
        "template": attr.label(
            allow_single_file = [".cc.tpl"],
            mandatory = True,
        ),
    },
)

Kullanıcılar, kuralı şu şekilde kullanabilir:

hello_world(
    name = "hello",
    username = "Alice",
    template = "file.cc.tpl",
)

cc_binary(
    name = "hello_bin",
    srcs = [":hello"],
)

Şablonu son kullanıcıya göstermek ve her zaman aynı şablonu kullanmak istemiyorsanız varsayılan bir değer ayarlayabilir ve özelliği gizli yapabilirsiniz:

    "_template": attr.label(
        allow_single_file = True,
        default = "file.cc.tpl",
    ),

Alt çizgi ile başlayan özellikler gizlidir ve BUILD dosyasında ayarlanamaz. Şablon artık bir dolaylı bağımlılık oldu: Her hello_world hedefinin bu dosyaya bir bağımlılığı vardır. BUILD dosyasını güncelleyip exports_files kullanarak bu dosyayı diğer paketlere görünür hale getirmeyi unutmayın:

exports_files(["file.cc.tpl"])

Daha ileri gidiyoruz