Makrolar

Bu sayfada, makro kullanmayla ilgili temel bilgiler, tipik kullanım alanları, hata ayıklama ve kurallar ele alınmaktadır.

Makro, BUILD dosyasından çağrılan ve kuralları örnekleyebilen bir işlevdir. Makrolar, temel olarak mevcut kuralların ve diğer makroların sarmalanması ve kodlarının yeniden kullanılması için kullanılır.

Makrolar iki şekilde sunulur: Bu sayfada açıklanan sembolik makrolar ve eski makrolar. Mümkün olduğunda kod netliği için sembolik makrolar kullanmanızı öneririz.

Sembolik makrolar, yazılmış bağımsız değişkenler (makronun çağrıldığı yere göre dize etikete dönüştürme) ve oluşturulan hedeflerin görünürlüğünü kısıtlama ve belirtme olanağı sunar. Bunlar, gelecekteki bir Bazel sürümünde eklenecek olan tembel değerlendirmeye uygun olacak şekilde tasarlanmıştır. Sembolik makrolar, Bazel 8'de varsayılan olarak kullanılabilir. Bu belgede macros ifadesi, sembolik makrolar anlamına gelir.

Kullanım

Makrolar, .bzl dosyalarında macro() işlevi iki zorunlu parametreyle (attrs ve implementation) çağrılarak tanımlanır.

Özellikler

attrs, makronun bağımsız değişkenlerini temsil eden özellik adı ile özellik türleri arasındaki bir sözlük kabul eder. İki ortak özellik (name ve visibility) tüm makrolara dolaylı olarak eklenir ve attrs'ye iletilen sözlüğe dahil edilmez.

# macro/macro.bzl
my_macro = macro(
    attrs = {
        "deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
        "create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
    },
    implementation = _my_macro_impl,
)

Özellik türü bildirimleri mandatory, default ve doc parametrelerini kabul eder. Çoğu özellik türü, özelliğin select kabul edip etmediğini belirleyen configurable parametresini de kabul eder. Bir özellik configurable ise select olmayan değerler, yapılandırılabilir olmayan bir select olarak ayrıştırılır. "foo", select({"//conditions:default": "foo"}) olur. Daha fazla bilgiyi seçimler bölümünde bulabilirsiniz.

Özellik devralma

ÖNEMLİ: Özellik devralma, --experimental_enable_macro_inherit_attrs bayrağı tarafından etkinleştirilen deneysel bir özelliktir. Bu bölümde açıklanan davranışlardan bazıları, özellik varsayılan olarak etkinleştirilmeden önce değişebilir.

Makrolar genellikle bir kuralı (veya başka bir makroyu) sarmalamak için tasarlanır ve makronun yazarı genellikle **kwargs kullanarak sarmalanmış sembolün özelliklerinin büyük kısmını makronun ana hedefine (veya ana iç makroya) değiştirmeden iletmek ister.

Bu kalıbı desteklemek için bir makro, macro()'nin inherit_attrs bağımsız değişkenine kural veya makro simgesi ileterek bir kuraldan ya da başka bir makrodan özellikleri devralabilir. (Tüm Starlark derleme kuralları için tanımlanan ortak özellikleri devralmak üzere kural veya makro simgesi yerine "common" özel dizesini de kullanabilirsiniz.) Yalnızca herkese açık özellikler devralınır ve makronun kendi attrs sözlüğündeki özellikler, aynı ada sahip devralınan özellikleri geçersiz kılar. Devralınan özellikleri attrs sözlüğünde None değerini kullanarak da kaldırabilirsiniz:

# macro/macro.bzl
my_macro = macro(
    inherit_attrs = native.cc_library,
    attrs = {
        # override native.cc_library's `local_defines` attribute
        local_defines = attr.string_list(default = ["FOO"]),
        # do not inherit native.cc_library's `defines` attribute
        defines = None,
    },
    ...
)

Zorunlu olmayan devralınan özelliklerin varsayılan değeri, orijinal özellik tanımının varsayılan değerinden bağımsız olarak her zaman None olarak geçersiz kılınır. Devralınan zorunlu olmayan bir özelliği incelemeniz veya değiştirmeniz gerekiyorsa (ör. devralınan bir tags özelliğine etiket eklemek istiyorsanız) makronuzun uygulama işlevinde None durumunu ele almanız gerekir:

# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
    # Append a tag; tags attr is an inherited non-mandatory attribute, and
    # therefore is None unless explicitly set by the caller of our macro.
    my_tags = (tags or []) + ["another_tag"]
    native.cc_library(
        ...
        tags = my_tags,
        **kwargs,
    )
    ...

Uygulama

implementation, makronun mantığını içeren bir işlevi kabul eder. Uygulama işlevleri genellikle bir veya daha fazla kuralı çağırarak hedef oluşturur ve genellikle özeldir (başında alt çizgiyle adlandırılır). Geleneksel olarak, makrolarıyla aynı ada sahiptirler ancak önlerine _, sonlarına ise _impl eklenir.

Özelliklere referans içeren tek bir bağımsız değişken (ctx) alan kural uygulama işlevlerinin aksine, makro uygulama işlevleri her bağımsız değişken için bir parametre kabul eder.

# macro/macro.bzl
def _my_macro_impl(name, visibility, deps, create_test):
    cc_library(
        name = name + "_cc_lib",
        deps = deps,
    )

    if create_test:
        cc_test(
            name = name + "_test",
            srcs = ["my_test.cc"],
            deps = deps,
        )

Bir makro özellik devralırsa uygulama işlevinde **kwargs artık anahtar kelime parametresi bulunmalıdır. Bu parametre, devralınan kuralı veya alt makroyu çağıran çağrıya yönlendirilebilir. (Bu, devraldığınız kural veya makro yeni bir özellik eklerse makronuzun bozulmamasını sağlar.)

Bildirim

Makrolar, tanımları bir BUILD dosyasına yükleyerek ve çağırarak bildirilir.


# pkg/BUILD

my_macro(
    name = "macro_instance",
    deps = ["src.cc"] + select(
        {
            "//config_setting:special": ["special_source.cc"],
            "//conditions:default": [],
        },
    ),
    create_tests = True,
)

Bu işlem, //pkg:macro_instance_cc_lib ve//pkg:macro_instance_test hedeflerini oluşturur.

Kural çağrılarında olduğu gibi, makro çağrısındaki bir özellik değeri None olarak ayarlanırsa bu özellik, makroyu çağıran tarafından atlanmış gibi değerlendirilir. Örneğin, aşağıdaki iki makro çağrısı eşdeğerdir:

# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])

Bu, genellikle BUILD dosyalarında yararlı değildir ancak bir makroyu programatik olarak başka bir makronun içine sarmalamak için faydalıdır.

Ayrıntılar

Oluşturulan hedefler için adlandırma kuralları

Sembolik bir makro tarafından oluşturulan hedeflerin veya alt makroların adları, makronun name parametresiyle eşleşmeli ya da name ile başlamalı ve ardından _ (tercih edilen), . veya - gelmelidir. Örneğin, my_macro(name = "foo") yalnızca foo adlı veya foo_, foo- ya da foo. ön ekiyle başlayan dosyalar veya hedefler (ör. foo_bar) oluşturabilir.

Makro adlandırma kuralını ihlal eden hedefler veya dosyalar tanımlanabilir ancak derlenemez ve bağımlılık olarak kullanılamaz.

Makro örneğiyle aynı paketteki makro dışı dosya ve hedeflerin adları, olası makro hedef adlarıyla çakışmamalıdır ancak bu münhasırlık zorunlu kılınmaz. Sembolik makrolar için performans iyileştirmesi olarak tembel değerlendirme özelliğini uygulama sürecindeyiz. Bu özellik, adlandırma şemasını ihlal eden paketlerde bozulacaktır.

Kısıtlamalar

Sembolik makrolar, eski makrolara kıyasla bazı ek kısıtlamalara sahiptir.

Sembolik makrolar

  • name bağımsız değişkeni ve visibility bağımsız değişkeni almalıdır
  • implementation işlevi olmalıdır
  • değer döndürmez.
  • bağımsız değişkenlerini değiştiremez
  • özel finalizer makroları olmadıkça native.existing_rules() çağıramaz
  • native.package()'yi arayamaz
  • glob()'yi arayamaz
  • native.environment_group()'yi arayamaz
  • Adları adlandırma şemasına uygun olan hedefler oluşturmalıdır.
  • Tanımlanmamış veya bağımsız değişken olarak iletilmemiş giriş dosyalarına referans veremez (daha fazla bilgi için görünürlük ve makrolar bölümüne bakın).

Görünürlük ve makrolar

Bazel'deki görünürlük hakkında ayrıntılı bilgi için Görünürlük bölümüne bakın.

Hedef görünürlük

Sembolik makro tarafından oluşturulan hedefler varsayılan olarak yalnızca makroyu tanımlayan .bzl dosyasını içeren pakette görünür. Özellikle, simgesel makroyu çağıran kullanıcı tarafından görülemezler. Çağıran kullanıcı, makronun .bzl dosyasıyla aynı pakette değilse bu durum geçerlidir.

Bir hedefi sembolik makroyu çağıran kullanıcıya görünür hale getirmek için visibility = visibility değerini kurala veya iç makroya iletin. Hedefe daha geniş (veya herkese açık) bir görünürlük vererek hedefi ek paketlerde de görünür hale getirebilirsiniz.

Bir paketin varsayılan görünürlük değeri (package() içinde belirtildiği gibi), varsayılan olarak en dış makronun visibility parametresine iletilir. Ancak bu visibility değerini, makronun oluşturduğu hedeflere iletmesi (veya iletmemesi) makronun kendisine bağlıdır.

Bağımlılık görünürlüğü

Bir makronun uygulamasında atıfta bulunulan hedefler, söz konusu makronun tanımı tarafından görülebilmelidir. Görünürlük aşağıdaki yöntemlerden biriyle verilebilir:

  • Hedefler, makroya etiket, etiket listesi veya etiketli anahtar ya da değer sözlük özellikleri aracılığıyla açıkça iletilirse makro tarafından görülebilir:

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • ... veya özellik varsayılan değerleri olarak:
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])},
  ...
)
  • Hedefler, makroyu tanımlayan .bzl dosyasını içeren paket için görünür olarak tanımlanmışsa makro tarafından da görülebilir:
# other_package/BUILD
# Any macro defined in a .bzl file in //my_macro package can use this tool.
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

Seçimler

Bir özellik configurable (varsayılan) ise ve değeri None değilse makro uygulama işlevi, özellik değerini önemsiz bir select içine sarmalanmış olarak görür. Bu, makro yazarının özellik değerinin select olabileceğini tahmin etmediği durumlardaki hataları yakalamasını kolaylaştırır.

Örneğin, aşağıdaki makroyu ele alalım:

my_macro = macro(
    attrs = {"deps": attr.label_list()},  # configurable unless specified otherwise
    implementation = _my_macro_impl,
)

my_macro, deps = ["//a"] ile çağrılırsa _my_macro_impl, deps parametresi select({"//conditions:default": ["//a"]}) olarak ayarlanmış şekilde çağrılır. Bu durum, uygulama işlevinin başarısız olmasına neden olursa (ör. kod, select için izin verilmeyen deps[0]'te olduğu gibi değeri dizine eklemeye çalıştıysa) makro yazarı bir seçim yapabilir: Makrosunu yalnızca select ile uyumlu işlemleri kullanacak şekilde yeniden yazabilir veya özelliği yapılandırılamaz olarak işaretleyebilir (attr.label_list(configurable = False)). İkinci seçenek, kullanıcıların select değeri iletmesine izin vermez.

Kural hedefleri bu dönüşümü tersine çevirir ve önemsiz select'leri koşulsuz değerleri olarak depolar. Yukarıdaki örnekte, _my_macro_impl bir kural hedefi my_rule(..., deps = deps) tanımlarsa bu kural hedefinin deps değeri ["//a"] olarak depolanır. Bu, select-sarmalama işleminin, makrolar tarafından oluşturulan tüm hedeflerde önemsiz select değerlerin depolanmasına neden olmamasını sağlar.

Yapılandırılabilir bir özelliğin değeri None ise select içine sarmalanmaz. Bu sayede my_attr == None gibi testler çalışmaya devam eder ve özellik, hesaplanmış bir varsayılan değere sahip bir kurala yönlendirildiğinde kural düzgün şekilde davranır (yani özellik hiç iletilmemiş gibi). Bir özelliğin None değerini alması her zaman mümkün değildir ancak attr.label() türü ve devralınan zorunlu olmayan özellikler için bu durum söz konusu olabilir.

Sonlandırıcılar

Kural sonlandırıcı, BUILD dosyasında söz dizimi konumundan bağımsız olarak, bir paketin yüklenmesinin son aşamasında, sonlandırıcı olmayan tüm hedefler tanımlandıktan sonra değerlendirilen özel bir sembolik makrodur. Normal sembolik makrolardan farklı olarak sonlandırıcı, native.existing_rules()'ü çağırabilir. Bu durumda, eski makrolardan biraz farklı davranır: Yalnızca sonlandırıcı olmayan kural hedeflerinin kümesini döndürür. Sonlandırıcı, bu kümenin durumu hakkında iddiada bulunabilir veya yeni hedefler tanımlayabilir.

Bir sonlandırıcıyı beyan etmek için finalizer = True ile macro()'ü çağırın:

def _my_finalizer_impl(name, visibility, tags_filter):
    for r in native.existing_rules().values():
        for tag in r.get("tags", []):
            if tag in tags_filter:
                my_test(
                    name = name + "_" + r["name"] + "_finalizer_test",
                    deps = [r["name"]],
                    data = r["srcs"],
                    ...
                )
                continue

my_finalizer = macro(
    attrs = {"tags_filter": attr.string_list(configurable = False)},
    implementation = _impl,
    finalizer = True,
)

Tembellik

ÖNEMLİ: Tembel makro genişletmeyi ve değerlendirmeyi uygulama sürecindeyiz. Bu özellik henüz kullanıma sunulmadı.

Şu anda tüm makrolar, BUILD dosyası yüklendikten hemen sonra değerlendirilir. Bu durum, maliyetli ve alakasız makroların da bulunduğu paketlerdeki hedeflerin performansını olumsuz yönde etkileyebilir. Gelecekte, sonlandırıcı olmayan sembolik makrolar yalnızca derleme için gerekliyse değerlendirilecektir. Önek adlandırma şeması, istenen bir hedef verildiğinde Basel'in hangi makronun genişletileceğini belirlemesine yardımcı olur.

Taşımayla ilgili sorunları giderme

Taşımayla ilgili bazı yaygın sorunlar ve bunların nasıl düzeltileceği aşağıda açıklanmıştır.

  • Eski makro çağrıları glob()

glob() çağrısını BUILD dosyanıza (veya BUILD dosyasından çağrılan eski bir makroya) taşıyın ve glob() değerini etiket listesi özelliğini kullanarak sembolik makroya iletin:

# BUILD file
my_macro(
    ...,
    deps = glob(...),
)
  • Eski makroda geçerli bir starlark attr türü olmayan bir parametre var.

Mümkün olduğunca fazla mantığı iç içe yerleştirilmiş sembolik bir makroya alın ancak üst düzey makroyu eski bir makro olarak tutun.

  • Eski makro, adlandırma şemasını ihlal eden bir hedef oluşturan bir kural çağırıyor

Sorun değil, "rahatsız edici" hedefe güvenmeyin. Adlandırma denetimi sessizce yoksayılır.