Makrolar

Sorun bildir Kaynağı görüntüle Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Bu sayfada, makro kullanmanın temel bilgileri ele alınmakta ve tipik kullanım alanları, hata ayıklama ve kurallar açıklanmaktadır.

Makro, BUILD dosyasından çağrılan ve kuralları başlatabilen bir işlevdir. Makrolar, temel olarak mevcut kuralların ve diğer makroların kapsüllenmesi ve kodun yeniden kullanılması için kullanılır.

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

Sembolik makrolar, türü belirlenmiş bağımsız değişkenler (makronun çağrıldığı yere göre dizeyi etikete dönüştürme) ve oluşturulan hedeflerin görünürlüğünü kısıtlama ve belirtme olanağı sunar. Bu işlevler, tembel değerlendirmeye uygun olacak şekilde tasarlanmıştır (gelecekteki bir Bazel sürümünde eklenecektir). Sembolik makrolar, Bazel 8'de varsayılan olarak kullanılabilir. Bu belgede macros ifadesinin geçtiği yerlerde sembolik makrolar kastedilmektedir.

Sembolik makroların çalıştırılabilir bir örneğini örnekler deposunda bulabilirsiniz.

Kullanım

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

Özellikler

attrs, makroya iletilecek bağımsız değişkenleri temsil eden, özellik adından özellik türlerine kadar olan bir sözlüğü kabul eder. İki ortak özellik (name ve visibility) tüm makrolara örtülü 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 parametreleri, mandatory, default ve doc'yi kabul eder. Çoğu özellik türü, özelliğin configurable kabul edip etmeyeceğini belirleyen select parametresini de kabul eder. Bir özellik configurable ise select olmayan değerleri yapılandırılabilir olmayan bir select olarak ayrıştırır. Örneğin, "foo", select({"//conditions:default": "foo"}) olur. Seçimler bölümünden daha fazla bilgi edinebilirsiniz.

Özellik devralma

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

Bu kalıbı desteklemek için bir makro, macro()'nın inherit_attrs bağımsız değişkenine kural veya makro sembolü iletilerek bir kuraldan ya da başka bir makrodan özellikleri devralabilir. ("common" özel dizesini, tüm Starlark derleme kuralları için tanımlanan ortak özellikleri devralmak üzere bir kural veya makro sembolü yerine 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. Ayrıca, attrs sözlüğünde değer olarak None kullanarak devralınan özellikleri 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 aldığınızdan emin olmanız gerekir:

# macro/macro.bzl
def _my_macro_impl(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 hedefler oluşturur ve genellikle özeldir (başında alt çizgiyle adlandırılır). Geleneksel olarak, makrolarıyla aynı şekilde adlandırılırlar ancak _ ile öneklenir ve _impl ile soneklenirler.

Ö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ıyorsa uygulama işlevinde, devralınan kuralı veya alt makroyu çağıran çağrıya iletilebilen bir zorunlu **kwargs artık anahtar kelime parametresi olmalıdır. (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üklenip çağrılarak 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, bir makro çağrısındaki özellik değeri None olarak ayarlanırsa bu özellik, makronun arayanı 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 işlev genellikle BUILD dosyalarında kullanışlı olmasa da bir makroyu programatik olarak başka bir makronun içine sarmak istediğinizde faydalıdır.

Ayrıntılar

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

Sembolik bir makro tarafından oluşturulan tüm 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. ile başlayan dosyalar veya hedefler oluşturabilir. Örneğin, foo_bar.

Makro adlandırma kuralını ihlal eden hedefler veya dosyalar beyan edilebilir ancak oluşturulamaz ve bağımlılık olarak kullanılamaz.

Bir makro örneğiyle aynı paketteki makro olmayan dosyalar ve hedefler, olası makro hedef adlarıyla çakışan adlara sahip olmamalıdır. Ancak bu ayrıcalık zorunlu değildir. Sembolik makrolarda performans iyileştirmesi olarak tembel değerlendirme özelliğini kullanıma sunma sürecindeyiz. Bu özellik, adlandırma şemasını ihlal eden paketlerde devre dışı bırakılacak.

Kısıtlamalar

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

Sembolik makrolar

  • name bağımsız değişkenini ve visibility bağımsız değişkenini almalıdır.
  • implementation işlevi olmalıdır
  • değer döndürmeyebilir
  • bağımsız değişkenlerini değiştiremez.
  • özel finalizer makrolar olmadığı sürece native.existing_rules() çağrılamaz.
  • native.package() aranmayabilir
  • glob() aranmayabilir
  • native.environment_group() aranmayabilir
  • Adları adlandırma şemasına uygun hedefler oluşturmalıdır.
  • Bildirilmeyen veya bağımsız değişken olarak geçirilmeyen giriş dosyalarına başvuramaz.
  • Arayanların özel hedeflerine başvuramaz (daha fazla bilgi için görünürlük ve makrolar konusuna bakın).

Görünürlük ve makrolar

Görünürlük sistemi, hem (sembolik) makroların hem de bunların arayanlarının uygulama ayrıntılarını korumaya yardımcı olur.

Varsayılan olarak, sembolik bir makroda oluşturulan hedefler makronun kendisinde görünür ancak makroyu çağıran kişi tarafından görünmeyebilir. Makro, kendi visibility özelliğinin değerini some_rule(..., visibility = visibility) örneğindeki gibi ileterek 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 çağıran pakete göre değil, hedefi bildiren makroya göre kontrol edilir.

    • Diğer bir deyişle, aynı pakette bulunmak bir hedefi diğerine görünür kılmaz. Bu, makronun dahili hedeflerinin paketteki diğer makroların veya üst düzey hedeflerin bağımlılıkları olmasını engeller.
  2. Hem kurallarda hem de makrolarda bulunan 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) belirtilen diğer hedeflere koşulsuz olarak görünür.

Uygulamada bu, bir makro, visibility değerini ayarlamadan bir hedef bildirdiğinde hedefin varsayılan olarak makroya ait olacağı anlamına gelir. (Paketin varsayılan görünürlüğü makro içinde geçerli değildir.) Hedefin dışa aktarılması, hedefin makronun visibility özelliğinde makronun çağıranı tarafından belirtilen her şeyin yanı sıra makronun çağıranının kendisinin paketi ve makronun kendi kodu tarafından görülebilir olduğu anlamına gelir. Bunu düşünmenin bir başka yolu da makronun görünürlüğünün, makronun kendisi dışında, makronun dışa aktarılan hedeflerini kimlerin görebileceğini belirlediğidir.

# 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 içinde tanımlanan bir makroda da kullanılabilir ancak //pkg:foo_helper korunmaya devam eder.

Bir makro, visibility = ["//some_friend:__pkg__"] (dahili bir hedef için) veya visibility = visibility + ["//some_friend:__pkg__"] (dışa aktarılan bir hedef için) ileterek bir hedefin arkadaş paketine görünür olduğunu bildirebilir. Bir makronun herkese açık görünürlük (visibility = ["//visibility:public"]) ile hedef bildirmesinin bir anti-pattern olduğunu unutmayın. Bunun nedeni, arayan daha kısıtlı bir görünürlük belirtmiş olsa bile hedefin her pakete koşulsuz olarak görünür hale gelmesidir.

Tüm görünürlük kontrolleri, şu anda çalışmakta olan en içteki sembolik makroya göre yapılır. Ancak bir görünürlük temsil mekanizması vardır: Bir makro, etiketi bir özellik değeri olarak içteki bir makroya geçirirse etiketin içteki makrodaki tüm kullanımları dıştaki 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 açısından tamamen şeffaf olduğunu ve konumlarının, çağrıldıkları BUILD dosyası veya sembolik makro olduğunu unutmayın.

Sonlandırıcılar ve görünürlük

Bir kural sonlandırıcıda belirtilen hedefler, normal sembolik makro görünürlük kurallarını izleyen hedefleri görmenin yanı sıra sonlandırıcı hedefinin paketine görünür olan tüm hedefleri de görebilir.

Bu, native.existing_rules() tabanlı eski bir makroyu sonlandırıcıya taşırsanız sonlandırıcı tarafından bildirilen hedeflerin eski bağımlılıklarını görmeye devam edebileceği anlamına gelir.

Ancak, bir hedefi sembolik bir makroda, sonlandırıcıların hedeflerinin görünürlük sistemi altında göremeyeceği şekilde bildirmenin mümkün olduğunu unutmayın. Sonlandırıcı, native.existing_rules() kullanarak özelliklerini inceleyebilse bile.

Seçmeler

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

Örneğin, aşağıdaki makroyu inceleyin:

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 (örneğin, kod, deps[0] içinde olduğu gibi değere indeks oluşturmaya çalıştığı için, bu select'ler için izin verilmeyen bir durumdur) makro yazarı bir seçim yapabilir: Makrosunu yalnızca select ile uyumlu işlemleri kullanacak şekilde yeniden yazabilir veya özelliği yapılandırılabilir olmayan olarak işaretleyebilir (attr.label_list(configurable = False)). İkinci seçenek, kullanıcıların select değeri iletmesine izin verilmemesini sağlar.

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

Yapılandırılabilir bir özelliğin değeri None ise bu değer select içine alınmaz. Bu, my_attr == None gibi testlerin çalışmaya devam etmesini ve özellik, hesaplanmış bir varsayılan değere sahip bir kurala iletildiğinde kuralın düzgün şekilde (yani özellik hiç iletilmemiş gibi) davranmasını sağlar. Bir özelliğin her zaman None değerini alması mümkün olmasa da attr.label() türü ve devralınan zorunlu olmayan tüm özellikler için bu durum geçerli olabilir.

Sonlandırıcılar

Kural sonlandırıcı, bir BUILD dosyasındaki sözcüksel 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. Sıradan sembolik makroların aksine, sonlandırıcılar 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.

Sonlandırıcı bildirmek için macro() ile finalizer = True işlevini ç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şletme ve değerlendirme özelliğini kullanıma sunma sürecindeyiz. Bu özellik henüz kullanılamıyor.

Şu anda tüm makrolar BUILD dosyası yüklendiği anda değerlendirilir. Bu durum, pahalı ve alakasız makroların da bulunduğu paketlerdeki hedeflerin performansını olumsuz etkileyebilir. Gelecekte, sonlandırıcı olmayan sembolik makrolar yalnızca derleme için gerekli olduklarında değerlendirilecektir. Önek adlandırma şeması, istenen bir hedef verildiğinde Bazel'in hangi makroyu genişleteceğini belirlemesine yardımcı olur.

Taşıma ile ilgili sorunları giderme

Taşıma sırasında sık karşılaşılan sorunlar ve çözümleri 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 label-list ö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 çekin ancak üst düzey makroyu eski bir makro olarak tutun.

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

Bu durumda, "hatalı" hedefe bağlı kalmayın. Adlandırma kontrolü sessizce yok sayılır.