이 페이지에서는 매크로 사용의 기본사항을 다루고 일반적인 사용 사례, 디버깅, 규칙을 포함합니다.
매크로는 규칙을 인스턴스화할 수 있는 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을 허용합니다. 대부분의 속성 유형은 속성이
selects를 허용하는지 여부를 결정하는
configurable 매개변수도 허용합니다. 속성이 configurable이면 select가 아닌 값을 구성할 수 없는 select로 파싱합니다. 즉, "foo"는 select({"//conditions:default": "foo"})가 됩니다. 선택에서 자세히 알아보세요.
속성 상속
매크로는 규칙 (또는 다른 매크로)을 래핑하기 위한 것이며 매크로
작성자는 래핑된 기호의 속성
대부분을 변경하지 않고 **kwargs를 사용하여 매크로의 기본 타겟 (또는 기본 내부 매크로)으로 전달하는 경우가 많습니다.
이 패턴을 지원하기 위해 매크로는 규칙 또는 다른
매크로에서 속성을 상속할 수 있습니다. 규칙 또는
매크로 기호를 macro()'s
inherit_attrs 인수에 전달하여 (규칙 또는 매크로 기호 대신 특수 문자열 "common"
을 사용하여 모든 Starlark 빌드 규칙에 정의된 일반적인 속성을 상속할 수도 있습니다.)
공개 속성만 상속되며 매크로 자체의
attrs 딕셔너리에 있는 속성은 동일한 이름의 상속된 속성을 재정의합니다. `attrs` 딕셔너리에서 None을 값으로 사용하여 상속된 속성을 삭제 할 수도 있습니다.attrs
# 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()을 호출할 수 없습니다.- 이름 지정 스키마를 준수하는 이름의 타겟을 만들어야 합니다.
- 선언되지 않았거나 인수로 전달되지 않은 입력 파일을 참조할 수 없습니다.
- 호출자의 비공개 타겟을 참조할 수 없습니다 (자세한 내용은 공개 상태 및 매크로 참고).
공개 상태 및 매크로
공개 상태 시스템은 구현 세부정보를 (기호) 매크로와 호출자 모두 보호하는 데 도움이 됩니다.
기본적으로 기호 매크로에서 생성된 타겟은 매크로
자체 내에서 볼 수 있지만 매크로 호출자에게는 반드시 표시되는 것은 아닙니다. 매크로는 자체 visibility 속성 값을 전달하여 타겟을 공개 API로 "내보낼" 수 있습니다.some_rule(..., visibility = visibility)
매크로 공개 상태의 주요 아이디어는 다음과 같습니다.
공개 상태는 매크로를 호출한 패키지가 아니라 타겟을 선언한 매크로를 기준으로 확인됩니다.
- 즉, 동일한 패키지에 있다고 해서 한 타겟이 다른 타겟에 표시되는 것은 아닙니다. 이렇게 하면 매크로의 내부 타겟이 패키지의 다른 매크로 또는 최상위 타겟의 종속 항목이 되지 않도록 보호됩니다.
규칙과 매크로 모두에서 모든
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"]로 호출하면 deps 매개변수가 select({"//conditions:default":
["//a"]})로 설정된 상태로 _my_macro_impl
가 호출됩니다. 이로 인해 구현 함수가 실패하는 경우 (예: 코드가 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() 유형 및 상속된 필수 속성에는 발생할 수 있습니다.
최종 처리기
규칙 최종 처리기는 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유형이 아닌 매개변수가 있습니다.
가능한 한 많은 로직을 중첩된 기호 매크로로 가져오되 최상위 매크로는 기존 매크로로 유지합니다.
- 기존 매크로가 이름 지정 스키마를 위반하는 타겟을 만드는 규칙을 호출합니다.
괜찮습니다. '문제가 되는' 타겟에 종속되지 마세요. 이름 지정 검사는 자동으로 무시됩니다.