이 페이지에서는 매크로 사용의 기본사항을 다루며 일반적인 사용 사례, 디버깅, 규칙을 포함합니다.
매크로는 규칙을 인스턴스화할 수 있는 BUILD
파일에서 호출되는 함수입니다.
매크로는 기존 규칙 및 기타 매크로의 캡슐화 및 코드 재사용에 주로 사용됩니다.
매크로에는 이 페이지에서 설명하는 기호 매크로와 기존 매크로의 두 가지 유형이 있습니다. 가능한 경우 코드 명확성을 위해 기호 매크로를 사용하는 것이 좋습니다.
심볼 매크로는 유형이 지정된 인수 (매크로가 호출된 위치와 관련된 문자열-라벨 변환)와 생성된 타겟의 공개 상태를 제한하고 지정하는 기능을 제공합니다. 지연 평가 (향후 Bazel 출시에서 추가될 예정)에 적합하도록 설계되었습니다. 기호 매크로는 Bazel 8에서 기본적으로 사용할 수 있습니다. 이 문서에서 macros
는 기호 매크로를 의미합니다.
실행 가능한 기호 매크로 예는 예 저장소에서 확인할 수 있습니다.
사용
매크로는 필수 매개변수 attrs
및 implementation
와 함께 macro()
함수를 호출하여 .bzl
파일에 정의됩니다.
속성
attrs
는 매크로의 인수를 나타내는 속성 이름에서 속성 유형으로의 사전이 허용됩니다. name
및 visibility
이라는 두 가지 일반적인 속성은 모든 매크로에 암시적으로 추가되며 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"})
이 됩니다. 선택에서 자세히 알아보세요.
속성 상속
매크로는 규칙 (또는 다른 매크로)을 래핑하기 위한 용도로 사용되는 경우가 많으며, 매크로 작성자는 **kwargs
를 사용하여 래핑된 기호의 속성 대부분을 변경하지 않고 매크로의 기본 타겟 (또는 기본 내부 매크로)으로 전달하려고 하는 경우가 많습니다.
이 패턴을 지원하기 위해 매크로는 macro()
의 inherit_attrs
인수에 rule 또는 macro symbol을 전달하여 규칙이나 다른 매크로에서 attributes를 상속할 수 있습니다. (규칙 또는 매크로 기호 대신 특수 문자열 "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
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,
)
...
구현
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
).
매크로 이름 지정 규칙을 위반하는 타겟이나 파일은 선언할 수 있지만 빌드할 수 없고 종속 항목으로 사용할 수 없습니다.
매크로 인스턴스와 동일한 패키지 내의 비매크로 파일과 타겟은 잠재적인 매크로 타겟 이름과 충돌하는 이름을 가져서는 안 됩니다. 하지만 이러한 독점성은 강제되지 않습니다. 이름 지정 스키마를 위반하는 패키지에서 손상되는 기호 매크로의 성능 개선으로 지연 평가를 구현하고 있습니다.
제한사항
기호 매크로는 기존 매크로에 비해 몇 가지 추가 제한사항이 있습니다.
기호 매크로
name
인수와visibility
인수를 사용해야 합니다.implementation
함수가 있어야 합니다.- 값을 반환하지 않을 수 있습니다.
- 인수를 변경해서는 안 됩니다.
- 특수
finalizer
매크로가 아닌 경우native.existing_rules()
를 호출할 수 없습니다. native.package()
에 전화를 걸 수 없음glob()
에 전화를 걸 수 없음native.environment_group()
에 전화를 걸 수 없음- 이름이 이름 지정 스키마를 준수하는 타겟을 만들어야 합니다.
- 선언되지 않았거나 인수로 전달되지 않은 입력 파일을 참조할 수 없음
- 호출자의 비공개 타겟을 참조할 수 없습니다 (자세한 내용은 공개 상태 및 매크로 참고).
공개 상태 및 매크로
가시성 시스템은 (기호) 매크로와 호출자의 구현 세부정보를 모두 보호하는 데 도움이 됩니다.
기본적으로 심볼릭 매크로에서 생성된 타겟은 매크로 자체 내에서는 표시되지만 매크로 호출자에게는 표시되지 않을 수 있습니다. 매크로는 some_rule(..., visibility = visibility)
와 같이 자체 visibility
속성 값을 전달하여 타겟을 공개 API로 '내보낼' 수 있습니다.
매크로 공개 상태의 핵심 아이디어는 다음과 같습니다.
공개 상태는 매크로를 호출한 패키지가 아닌 타겟을 선언한 매크로를 기반으로 확인됩니다.
- 즉, 동일한 패키지에 있다고 해서 한 타겟이 다른 타겟에 표시되는 것은 아닙니다. 이렇게 하면 매크로의 내부 타겟이 패키지의 다른 매크로 또는 최상위 타겟의 종속 항목이 되지 않습니다.
규칙과 매크로 모두의 모든
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_macro
이 visibility = ["//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 파일이나 심볼릭 매크로인 것처럼 동작합니다.
종료자 및 공개 상태
일반적인 심볼 매크로 공개 상태 규칙을 따르는 타겟을 보는 것 외에도 규칙 파이널라이저에 선언된 타겟은 파이널라이저 타겟의 패키지에 표시되는 모든 타겟을 볼 수 있습니다.
즉, native.existing_rules()
기반 기존 매크로를 종료자로 이전하면 종료자가 선언한 타겟이 이전 종속 항목을 계속 볼 수 있습니다.
하지만 파이널라이저가 native.existing_rules()
를 사용하여 속성을 인트로스펙트할 수 있더라도 파이널라이저의 타겟이 공개 상태 시스템에서 이를 볼 수 없도록 심볼 매크로에서 타겟을 선언할 수 있습니다.
선택
속성이 configurable
(기본값)이고 값이 None
이 아닌 경우 매크로 구현 함수는 속성 값이 사소한 select
로 래핑된 것으로 간주합니다. 이를 통해 매크로 작성자가 속성 값이 select
일 수 있다고 예상하지 못한 버그를 쉽게 포착할 수 있습니다.
예를 들어 다음 매크로를 고려해 보세요.
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
my_macro
이 deps = ["//a"]
로 호출되면 _my_macro_impl
가 deps
매개변수가 select({"//conditions:default":
["//a"]})
로 설정된 상태로 호출됩니다. 이로 인해 구현 함수가 실패하는 경우 (예: 코드가 deps[0]
에서와 같이 값에 색인을 생성하려고 시도함. 이는 select
에는 허용되지 않음) 매크로 작성자는 매크로를 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()
유형과 상속된 비필수 속성의 경우에는 가능합니다.
Finalizers
규칙 종료자는 BUILD 파일의 어휘적 위치와 관계없이 모든 종료자가 아닌 타겟이 정의된 후 패키지 로드의 최종 단계에서 평가되는 특수한 기호 매크로입니다. 일반적인 기호 매크로와 달리 파이널라이저는 native.existing_rules()
를 호출할 수 있으며, 여기서 기존 매크로와 약간 다르게 동작합니다. 파이널라이저가 아닌 규칙 타겟 집합만 반환합니다. 종료자는 해당 세트의 상태를 어설션하거나 새 타겟을 정의할 수 있습니다.
종료자를 선언하려면 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 파일에서 호출되는 기존 매크로)로 이동하고 라벨 목록 속성을 사용하여 glob()
값을 심볼 매크로에 전달합니다.
# BUILD file
my_macro(
...,
deps = glob(...),
)
- 레거시 매크로에 유효한 starlark
attr
유형이 아닌 매개변수가 있습니다.
가능한 한 많은 로직을 중첩된 기호 매크로로 가져오되 최상위 매크로는 기존 매크로로 유지합니다.
- 레거시 매크로가 명명 스키마를 위반하는 타겟을 만드는 규칙을 호출함
괜찮습니다. '위반' 타겟에 의존하지 마세요. 이름 지정 확인은 자동으로 무시됩니다.