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, üşengeç değerlendirmeye (gelecekteki bir Bazel sürümünde eklenecek) 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.

Sembolik makroların yürütülebilir bir örneğini örnek deposunda bulabilirsiniz.

Kullanım

Makrolar, .bzl dosyalarında macro() işlevi attrs ve implementation olmak üzere iki gerekli parametreyle ç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 devralımı

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.
  • arayanlarının gizli hedeflerine atıfta bulunamaz (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

Görünürlük sistemi, hem (sembolik) makroların hem de bu makroları çağıranların uygulama ayrıntılarının korunmasına yardımcı olur.

Varsayılan olarak, sembolik bir makroda oluşturulan hedefler makronun kendisinde görünür ancak makroyu çağıran kullanıcı tarafından görülmeyebilir. Makro, some_rule(..., visibility = visibility)'te olduğu gibi kendi visibilityözelliğinin değerini yönlendirerek bir hedefi herkese açık API olarak "dışa aktarabilir".

Makro görünürlüğün temel fikirleri şunlardır:

  1. Görünürlük, makroyu hangi paketin çağırdığına değil, hangi makronun hedefi belirttiğine göre kontrol edilir.

    • Diğer bir deyişle, aynı pakette olmak tek başına bir hedefin diğerine görünür olmasını sağlamaz. Bu, makronun dahili hedeflerinin paketteki diğer makroların veya üst düzey hedeflerin bağımlılıkları haline gelmesini önler.
  2. Hem kurallarda hem de makrolarda tüm visibility özellikleri, kuralın veya makronun çağrıldığı yeri otomatik olarak içerir.

    • Bu nedenle, bir hedef aynı makroda (veya makroda değilse BUILD dosyasında) tanımlanan diğer hedefler tarafından koşulsuz olarak görülebilir.

Pratikte bu, bir makro visibility değerini ayarlamadan bir hedef beyan ettiğinde hedefin varsayılan olarak makronun içinde olduğu anlamına gelir. (Paketin varsayılan görünürlüğü bir makroda geçerli değildir.) Hedefin dışa aktarılması, hedefin makronun visibility özelliğinde makronun çağıranın belirttiği her şeye, makronun çağıranın paketine ve makronun kendi koduna göründüğü anlamına gelir. Bunu başka bir şekilde açıklamak gerekirse, bir makronun görünürlük ayarı, makronun dışa aktarılan hedeflerini kimin (makronun kendisi dışında) görebileceğini belirler.

# tool/BUILD
...
some_rule(
    name = "some_tool",
    visibility = ["//macro:__pkg__"],
)
# macro/macro.bzl

def _impl(name, visibility):
    cc_library(
        name = name + "_helper",
        ...
        # No visibility passed in. Same as passing `visibility = None` or
        # `visibility = ["//visibility:private"]`. Visible to the //macro
        # package only.
    )
    cc_binary(
        name = name + "_exported",
        deps = [
            # Allowed because we're also in //macro. (Targets in any other
            # instance of this macro, or any other macro in //macro, can see it
            # too.)
            name + "_helper",
            # Allowed by some_tool's visibility, regardless of what BUILD file
            # we're called from.
            "//tool:some_tool",
        ],
        ...
        visibility = visibility,
    )

my_macro = macro(implementation = _impl, ...)
# pkg/BUILD
load("//macro:macro.bzl", "my_macro")
...

my_macro(
    name = "foo",
    ...
)

some_rule(
    ...
    deps = [
        # Allowed, its visibility is ["//pkg:__pkg__", "//macro:__pkg__"].
        ":foo_exported",
        # Disallowed, its visibility is ["//macro:__pkg__"] and
        # we are not in //macro.
        ":foo_helper",
    ]
)

my_macro, visibility = ["//other_pkg:__pkg__"] ile çağrılırsa veya //pkg paketi default_visibility değerini bu değere ayarlarsa //pkg:foo_exported, //other_pkg/BUILD içinde veya //other_pkg:defs.bzl'de tanımlanan bir makro içinde de kullanılabilir ancak //pkg:foo_helper korunmaya devam eder.

Bir makro, visibility = ["//some_friend:__pkg__"] (dahili hedef için) veya visibility = visibility + ["//some_friend:__pkg__"] (dışa aktarılan hedef için) ileterek bir hedefin arkadaş paketi tarafından görülebileceğini belirtebilir. Bir makronun, hedefi herkese açık görünürlükle (visibility = ["//visibility:public"]) tanımlamasının anti-pattern olduğunu unutmayın. Bunun nedeni, arayan daha kısıtlanmış bir görünürlük belirtse bile hedefin her paket için koşulsuz olarak görünür hale gelmesidir.

Tüm görünürlük kontrolleri, şu anda çalışan en iç sembolik makroya göre yapılır. Ancak bir görünürlük yetkilendirme mekanizması vardır: Bir makro, bir etiketi iç makroya özellik değeri olarak aktarırsa iç makroda etiketin tüm kullanımları dış makroya göre kontrol edilir. Daha fazla bilgi için görünürlük sayfasına göz atın.

Eski makroların görünürlük sistemi için tamamen şeffaf olduğunu ve çağrıldıkları BUILD dosyası veya sembolik makronun konumu gibi davrandığını unutmayın.

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 bir etiket listesi özelliği 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.