매크로

이 페이지에서는 매크로 사용의 기본사항을 다루며 일반적인 사용 사례, 디버깅, 규칙을 포함합니다.

매크로는 규칙을 인스턴스화할 수 있는 BUILD 파일에서 호출되는 함수입니다. 매크로는 주로 기존 규칙 및 기타 매크로의 캡슐화 및 코드 재사용에 사용됩니다.

매크로는 이 페이지에 설명된 기호 매크로와 기존 매크로의 두 가지 버전으로 제공됩니다. 가능하면 코드의 명확성을 위해 기호 매크로를 사용하는 것이 좋습니다.

기호 매크로는 유형이 지정된 인수 (매크로가 호출된 위치를 기준으로 문자열을 라벨로 변환)와 생성된 타겟의 공개 상태를 제한하고 지정하는 기능을 제공합니다. 이는 지연 평가 (향후 Bazel 출시에서 추가될 예정)를 수용할 수 있도록 설계되었습니다. 기호 매크로는 Bazel 8에서 기본적으로 사용할 수 있습니다. 이 문서에서 macros를 언급하는 경우 기호 매크로를 의미합니다.

기호 매크로의 실행 파일 예시는 예시 저장소에서 확인할 수 있습니다.

사용

매크로는 필수 매개변수 attrsimplementation를 사용하여 macro() 함수를 호출하여 .bzl 파일에 정의됩니다.

속성

attrs는 매크로의 인수를 나타내는 속성 이름과 속성 유형의 사전을 허용합니다. 두 가지 공통 속성(namevisibility)은 모든 매크로에 암시적으로 추가되며 attrs에 전달된 사전에 포함되지 않습니다.

# 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,
)

속성 유형 선언은 매개변수 mandatory, default, doc를 허용합니다. 대부분의 속성 유형은 속성이 select를 허용하는지 여부를 결정하는 configurable 매개변수도 허용합니다. 속성이 configurable인 경우 select가 아닌 값을 구성할 수 없는 select로 파싱합니다. "foo"select({"//conditions:default": "foo"})이 됩니다. selects에서 자세히 알아보세요.

속성 상속

매크로는 규칙 (또는 다른 매크로)을 래핑하기 위한 경우가 많으며, 매크로 작성자는 래핑된 기호의 대부분의 속성을 변경하지 않고 **kwargs를 사용하여 매크로의 기본 타겟 (또는 기본 내부 매크로)에 전달하려고 합니다.

이 패턴을 지원하기 위해 매크로는 macro()inherit_attrs 인수에 규칙 또는 매크로 기호를 전달하여 규칙 또는 다른 매크로에서 속성을 상속할 수 있습니다. 규칙이나 매크로 기호 대신 특수 문자열 "common"을 사용하여 모든 Starlark 빌드 규칙에 정의된 공통 속성을 상속할 수도 있습니다. 공개 속성만 상속되며 매크로 자체 attrs 사전의 속성은 동일한 이름의 상속된 속성을 재정의합니다. attrs 사전에서 None를 값으로 사용하여 상속된 속성을 삭제할 수도 있습니다.

# 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,
    },
    ...
)

필수가 아닌 상속된 속성의 기본값은 원래 속성 정의의 기본값과 관계없이 항상 None로 재정의됩니다. 상속된 비필수 속성을 검사하거나 수정해야 하는 경우(예: 상속된 tags 속성에 태그를 추가하려는 경우) 매크로의 구현 함수에서 None 사례를 처리해야 합니다.

# 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,
    )
    ...

구현

implementation는 매크로의 로직이 포함된 함수를 허용합니다. 구현 함수는 하나 이상의 규칙을 호출하여 타겟을 만드는 경우가 많으며 일반적으로 비공개입니다 (이름 앞에 밑줄이 표시됨). 일반적으로 매크로와 동일한 이름을 사용하지만 앞에 _이, 뒤에 _impl이 붙습니다.

속성에 대한 참조를 포함하는 단일 인수 (ctx)를 사용하는 규칙 구현 함수와 달리 매크로 구현 함수는 각 인수의 매개변수를 허용합니다.

# 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,
        )

매크로가 속성을 상속하는 경우 구현 함수에는 상속된 규칙 또는 하위 매크로를 호출하는 호출로 전달할 수 있는 **kwargs 잔여 키워드 매개변수가 있어야 합니다. 이렇게 하면 상속하는 규칙 또는 매크로에 새 속성이 추가되어도 매크로가 손상되지 않습니다.

선언

매크로는 BUILD 파일에서 정의를 로드하고 호출하여 선언됩니다.


# pkg/BUILD

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

그러면 //pkg:macro_instance_cc_lib//pkg:macro_instance_test 타겟이 생성됩니다.

규칙 호출과 마찬가지로 매크로 호출의 속성 값이 None로 설정되면 해당 속성은 매크로 호출자가 생략한 것처럼 취급됩니다. 예를 들어 다음 두 매크로 호출은 동일합니다.

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

이는 일반적으로 BUILD 파일에서는 유용하지 않지만 매크로를 프로그래매틱 방식으로 다른 매크로 내에 래핑할 때는 유용합니다.

세부정보

생성된 타겟의 이름 지정 규칙

기호 매크로로 만든 타겟 또는 하위 매크로의 이름은 매크로의 name 매개변수와 일치하거나 name 뒤에 _ (권장), . 또는 -가 오는 프리픽스를 사용해야 합니다. 예를 들어 my_macro(name = "foo")foo라는 이름의 파일이나 타겟만 만들거나 foo_, foo- 또는 foo. 접두사가 있는 파일이나 타겟(예: foo_bar)만 만들 수 있습니다.

매크로 이름 지정 규칙을 위반하는 타겟 또는 파일은 선언할 수 있지만 빌드할 수 없으며 종속 항목으로 사용할 수 없습니다.

매크로 인스턴스와 동일한 패키지 내에 있는 매크로가 아닌 파일과 타겟은 잠재적인 매크로 타겟 이름과 충돌하는 이름을 가져서는 안 됩니다. 단, 이 배타성은 시행되지 않습니다. Google에서는 이름 지정 스키마를 위반하는 패키지에서 성능이 저하되는 기호 매크로의 성능 개선으로 지연 평가를 구현하는 중입니다.

제한사항

기호 매크로에는 기존 매크로에 비해 몇 가지 추가 제한사항이 있습니다.

기호 매크로

  • name 인수와 visibility 인수를 사용해야 합니다.
  • implementation 함수가 있어야 합니다.
  • 값을 반환하지 않을 수 있습니다.
  • 인수를 변경하지 않을 수 있습니다.
  • 특수 finalizer 매크로가 아닌 한 native.existing_rules()를 호출하지 않을 수 있습니다.
  • native.package()를 호출하지 않을 수 있음
  • glob()를 호출하지 않을 수 있음
  • native.environment_group()를 호출하지 않을 수 있음
  • 이름이 이름 지정 스키마를 준수하는 타겟을 만들어야 합니다.
  • 선언되지 않았거나 인수로 전달되지 않은 입력 파일을 참조할 수 없습니다(자세한 내용은 공개 상태 및 매크로 참고).

공개 상태 및 매크로

가시성 시스템은 (기호) 매크로와 호출자의 구현 세부정보를 모두 보호하는 데 도움이 됩니다.

기본적으로 기호 매크로에서 생성된 타겟은 매크로 자체 내에서 표시되지만 매크로 호출자에게는 반드시 표시되지는 않습니다. 매크로는 some_rule(..., visibility = visibility)에서와 같이 자체 visibility 속성의 값을 전달하여 타겟을 공개 API로 '내보낼' 수 있습니다.

매크로 공개 상태의 주요 개념은 다음과 같습니다.

  1. 공개 상태는 매크로를 호출한 패키지가 아닌 타겟을 선언한 매크로를 기준으로 확인됩니다.

    • 즉, 동일한 패키지에 있다고 해서 한 타겟이 다른 타겟에 표시되는 것은 아닙니다. 이렇게 하면 매크로의 내부 타겟이 패키지의 다른 매크로 또는 최상위 타겟의 종속 항목이 되는 것을 방지할 수 있습니다.
  2. 규칙과 매크로의 모든 visibility 속성에는 규칙 또는 매크로가 호출된 위치가 자동으로 포함됩니다.

    • 따라서 타겟은 동일한 매크로 (또는 매크로에 없는 경우 BUILD 파일)에 선언된 다른 타겟에 무조건 표시됩니다.

실제로는 매크로가 visibility를 설정하지 않고 타겟을 선언하면 타겟이 기본적으로 매크로 내부로 설정됩니다. 패키지의 기본 공개 상태는 매크로 내에서 적용되지 않습니다. 타겟을 내보내면 타겟이 매크로의 visibility 속성에 지정된 매크로의 호출자, 매크로의 호출자 자체의 패키지, 매크로 자체 코드에 표시됩니다. 다른 관점에서 보면 매크로의 공개 상태에 따라 매크로 자체를 제외하고 매크로의 내보낸 타겟을 볼 수 있는 사용자가 결정됩니다.

# 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_macrovisibility = ["//other_pkg:__pkg__"]로 호출되었거나 //pkg 패키지가 default_visibility를 해당 값으로 설정한 경우 //pkg:foo_exported//other_pkg/BUILD 내에서 또는 //other_pkg:defs.bzl에 정의된 매크로 내에서 사용할 수도 있지만 //pkg:foo_helper는 계속 보호됩니다.

매크로는 visibility = ["//some_friend:__pkg__"] (내부 타겟의 경우) 또는 visibility = visibility + ["//some_friend:__pkg__"] (내보낸 타겟의 경우)를 전달하여 타겟이 친구 패키지에 표시된다고 선언할 수 있습니다. 매크로에서 공개 공개 상태 (visibility = ["//visibility:public"])로 타겟을 선언하는 것은 안티패턴입니다. 호출자가 더 제한된 공개 상태를 지정했더라도 타겟이 모든 패키지에 무조건 표시되기 때문입니다.

모든 공개 상태 확인은 현재 실행 중인 가장 안쪽의 기호 매크로를 기준으로 실행됩니다. 그러나 공개 범위 위임 메커니즘이 있습니다. 매크로가 라벨을 내부 매크로에 속성 값으로 전달하면 내부 매크로에서 라벨을 사용하는 모든 위치가 외부 매크로를 기준으로 확인됩니다. 자세한 내용은 공개 상태 페이지를 참고하세요.

기존 매크로는 가시성 시스템에 완전히 투명하며 호출된 위치가 BUILD 파일 또는 기호 매크로인 것처럼 동작합니다.

선택

속성이 configurable (기본값)이고 값이 None가 아닌 경우 매크로 구현 함수에는 속성 값이 사소한 select로 래핑된 것으로 표시됩니다. 이렇게 하면 매크로 작성자가 속성 값이 select일 수 있다고 예상하지 못한 버그를 더 쉽게 포착할 수 있습니다.

예를 들어 다음 매크로를 고려해 보세요.

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

my_macrodeps = ["//a"]로 호출되면 _my_macro_impldeps 매개변수가 select({"//conditions:default": ["//a"]})로 설정된 상태로 호출됩니다. 이로 인해 구현 함수가 실패하면 (예: 코드가 select에 허용되지 않는 deps[0]와 같이 값의 색인을 생성하려고 시도함) 매크로 작성자는 select와 호환되는 작업만 사용하도록 매크로를 다시 작성하거나 속성을 구성 불가능한 것으로 표시 (attr.label_list(configurable = False))할 수 있습니다. 후자의 경우 사용자가 select 값을 전달할 수 없습니다.

규칙 타겟은 이 변환을 역으로 적용하고 사소한 select를 무조건 값으로 저장합니다. 위 예에서 _my_macro_impl가 규칙 타겟 my_rule(..., deps = deps)를 선언하면 해당 규칙 타겟의 deps["//a"]로 저장됩니다. 이렇게 하면 select 래핑으로 인해 매크로로 인스턴스화된 모든 타겟에 사소한 select 값이 저장되지 않습니다.

구성 가능한 속성의 값이 None이면 select로 래핑되지 않습니다. 이렇게 하면 my_attr == None와 같은 테스트가 계속 작동하고 속성이 계산된 기본값이 있는 규칙으로 전달될 때 규칙이 올바르게 동작합니다 (즉, 속성이 전달되지 않은 것처럼). 속성이 항상 None 값을 취할 수 있는 것은 아니지만 attr.label() 유형과 상속된 비필수 속성의 경우 가능합니다.

최종 처리자

규칙 최종 처리기는 BUILD 파일의 문법적 위치와 관계없이 모든 최종 처리기가 아닌 타겟이 정의된 후 패키지 로드의 마지막 단계에서 평가되는 특수한 기호 매크로입니다. 일반 기호 매크로와 달리 종료자는 native.existing_rules()를 호출할 수 있으며, 여기서 종료자는 기존 매크로와 약간 다르게 작동합니다. 종료자가 아닌 규칙 타겟 집합만 반환합니다. finalizer는 해당 세트의 상태를 어설션하거나 새 타겟을 정의할 수 있습니다.

finalizer를 선언하려면 finalizer = True를 사용하여 macro()를 호출합니다.

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,
)

게으름

중요: 지연된 매크로 확장 및 평가를 구현하는 중입니다. 이 기능은 아직 사용할 수 없습니다.

현재 모든 매크로는 BUILD 파일이 로드되는 즉시 평가되므로 비용이 많이 들고 관련 없는 매크로가 있는 패키지의 타겟 성능에 부정적인 영향을 미칠 수 있습니다. 향후 종료자가 아닌 기호 매크로는 빌드에 필요한 경우에만 평가됩니다. 접두사 이름 지정 스키마는 요청된 타겟을 기준으로 Bazel에서 확장할 매크로를 결정하는 데 도움이 됩니다.

이전 문제 해결

다음은 일반적인 이전 문제와 해결 방법입니다.

  • 기존 매크로 호출 glob()

glob() 호출을 BUILD 파일 (또는 BUILD 파일에서 호출된 기존 매크로)로 이동하고 label-list 속성을 사용하여 glob() 값을 기호 매크로에 전달합니다.

# BUILD file
my_macro(
    ...,
    deps = glob(...),
)
  • 기존 매크로에 유효한 Starlark attr 유형이 아닌 매개변수가 있습니다.

중첩된 기호 매크로로 최대한 많은 로직을 가져오되 최상위 매크로는 기존 매크로로 유지합니다.

  • 기존 매크로가 이름 지정 스키마를 위반하는 타겟을 만드는 규칙을 호출함

괜찮습니다. '오해의 소지가 있는' 타겟에 의존하지 마세요. 이름 지정 검사는 자동으로 무시됩니다.