이 페이지에서는 규칙 작성자가 도구의 플랫폼 기반 선택에서 규칙 로직을 분리할 수 있는 방법인 도구 모음 프레임워크를 설명합니다. 계속하기 전에 규칙 및 플랫폼 페이지를 읽는 것이 좋습니다. 이 페이지에서는 도구 모음이 필요한 이유, 도구 모음을 정의하고 사용하는 방법, Bazel이 플랫폼 제약조건을 기반으로 적절한 도구 모음을 선택하는 방법을 설명합니다.
동기
먼저 도구 모음이 해결하도록 설계된 문제를 살펴보겠습니다. 'bar' 프로그래밍 언어를 지원하는 규칙을 작성한다고 가정해 보겠습니다. bar_binary
규칙은 *.bar 파일을 barc 컴파일러를 사용하여 컴파일합니다. 이 컴파일러는 작업공간에서 다른 대상으로 빌드되는 도구입니다.
bar_binary
대상을 작성하는 사용자는 컴파일러에 종속 항목을 지정할 필요가 없으므로 규칙 정의에 비공개 속성으로 추가하여 암시적 종속 항목으로 만듭니다.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
"_compiler": attr.label(
default = "//bar_tools:barc_linux", # the compiler running on linux
providers = [BarcInfo],
),
},
)
//bar_tools:barc_linux는 이제 모든 bar_binary 대상의 종속 항목이므로
bar_binary 대상보다 먼저 빌드됩니다. 다른 속성과 마찬가지로 규칙의
구현 함수에서 액세스할 수 있습니다.
BarcInfo = provider(
doc = "Information about how to invoke the barc compiler.",
# In the real world, compiler_path and system_lib might hold File objects,
# but for simplicity they are strings for this example. arch_flags is a list
# of strings.
fields = ["compiler_path", "system_lib", "arch_flags"],
)
def _bar_binary_impl(ctx):
...
info = ctx.attr._compiler[BarcInfo]
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
여기서 문제는 컴파일러의 라벨이 bar_binary에 하드 코딩되어 있지만,
서로 다른 대상은 빌드되는 플랫폼과 빌드되는 플랫폼(각각 대상 플랫폼 및 실행 플랫폼이라고 함)에 따라 서로 다른 컴파일러가 필요할 수 있다는 것입니다. 또한 규칙
작성자는 사용 가능한 모든 도구와 플랫폼을 알 필요가 없으므로
규칙 정의에 하드 코딩하는 것은 불가능합니다.
덜 이상적인 해결 방법은
_compiler 속성을 비공개로 만들어 사용자에게 부담을 주는 것입니다. 그러면 개별 대상을
한 플랫폼 또는 다른 플랫폼용으로 빌드하도록 하드 코딩할 수 있습니다.
bar_binary(
name = "myprog_on_linux",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_linux",
)
bar_binary(
name = "myprog_on_windows",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_windows",
)
select를 사용하여 플랫폼을 기반으로 compiler
를 선택하면 이 솔루션을 개선할 수 있습니다:
config_setting(
name = "on_linux",
constraint_values = [
"@platforms//os:linux",
],
)
config_setting(
name = "on_windows",
constraint_values = [
"@platforms//os:windows",
],
)
bar_binary(
name = "myprog",
srcs = ["mysrc.bar"],
compiler = select({
":on_linux": "//bar_tools:barc_linux",
":on_windows": "//bar_tools:barc_windows",
}),
)
하지만 이는 지루하고 모든 bar_binary 사용자에게 요구하기에는 다소 과도합니다.
이 스타일이 작업공간 전체에서 일관되게 사용되지 않으면 단일 플랫폼에서는 제대로 작동하지만
다중 플랫폼 시나리오로 확장할 때 실패하는
빌드가 발생합니다. 또한 기존 규칙이나 대상을 수정하지 않고 새 플랫폼과 컴파일러에 대한 지원을 추가하는 문제도 해결하지 못합니다.
도구 모음 프레임워크는 간접 수준을 추가하여 이 문제를 해결합니다. 기본적으로 규칙이 대상 계열 (도구 모음 유형)의 일부 구성원에 추상 종속 항목이 있다고 선언하면 Bazel은 적용 가능한 플랫폼 제약조건을 기반으로 이를 특정 대상 (도구 모음)으로 자동 확인합니다. 규칙 작성자나 대상 작성자는 사용 가능한 플랫폼과 도구 모음의 전체 집합을 알 필요가 없습니다.
도구 모음을 사용하는 규칙 작성
도구 모음 프레임워크에서는 규칙이 도구에 직접 종속되는 대신 도구 모음 유형에 종속됩니다. 도구 모음 유형은 서로 다른 플랫폼에서 동일한 역할을 하는 도구 클래스를 나타내는 간단한 대상 입니다. 예를 들어 bar 컴파일러를 나타내는 유형을 선언할 수 있습니다.
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
이전 섹션의 규칙 정의는 컴파일러를 속성으로 가져오는 대신
도구 모음을 사용한다고 선언하도록 수정됩니다.
//bar_tools:toolchain_type
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
이제 구현 함수는 도구 모음 유형을 키로 사용하여 ctx.toolchains
대신 ctx.attr에서 이 종속 항목에 액세스합니다.
def _bar_binary_impl(ctx):
...
info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
# The rest is unchanged.
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
ctx.toolchains["//bar_tools:toolchain_type"]는 Bazel이 도구 모음 종속 항목을 확인한 대상의
ToolchainInfo 제공자
를 반환합니다.
ToolchainInfo 객체의 필드는 기본 도구의 규칙에 의해 설정됩니다. 다음
섹션에서는 barcinfo 필드가 BarcInfo 객체를 래핑하도록 이 규칙이 정의됩니다.
도구 모음을 대상으로 확인하는 Bazel의 절차는
아래에 설명되어 있습니다. 확인된 도구 모음 대상만 후보
도구 모음의 전체 공간이 아닌 bar_binary 대상의 종속 항목이 실제로
됩니다.
필수 및 선택사항 도구 모음
기본적으로 규칙이 위와 같이 기본 라벨을 사용하여 도구 모음 유형 종속 항목을 표현하면 도구 모음 유형은 필수 로 간주됩니다. Bazel 이 필수 도구 모음 유형에 일치하는 도구 모음을 찾을 수 없는 경우 (도구 모음 확인 아래 참조) 이는 오류이며 분석이 중지됩니다.
대신 다음과 같이 선택사항 도구 모음 유형 종속 항목을 선포할 수 있습니다.
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
선택사항 도구 모음 유형을 확인할 수 없는 경우 분석이 계속되고
ctx.toolchains["//bar_tools:toolchain_type"]의 결과는 None입니다.
config_common.toolchain_type
함수는 기본적으로 필수입니다.
다음 형식을 사용할 수 있습니다.
- 필수 도구 모음 유형:
toolchains = ["//bar_tools:toolchain_type"]toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
- 선택사항 도구 모음 유형:
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
...
toolchains = [
"//foo_tools:toolchain_type",
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
동일한 규칙에서 형식을 혼합하여 사용할 수도 있습니다. 하지만 동일한 도구 모음 유형이 여러 번 나열되면 가장 엄격한 버전이 적용됩니다. 여기서 필수사항은 선택사항보다 더 엄격합니다.
도구 모음을 사용하는 측면 작성
측면은 규칙과 동일한 도구 모음 API에 액세스할 수 있습니다. 필요한 도구 모음 유형을 정의하고 컨텍스트를 통해 도구 모음에 액세스하며 도구 모음을 사용하여 새 작업을 생성할 수 있습니다.
bar_aspect = aspect(
implementation = _bar_aspect_impl,
attrs = {},
toolchains = ['//bar_tools:toolchain_type'],
)
def _bar_aspect_impl(target, ctx):
toolchain = ctx.toolchains['//bar_tools:toolchain_type']
# Use the toolchain provider like in a rule.
return []
도구 모음 정의
지정된 도구 모음 유형에 대한 도구 모음을 정의하려면 다음 세 가지가 필요합니다.
도구 또는 도구 모음의 종류를 나타내는 언어별 규칙입니다. 관례에 따라 이 규칙의 이름은 '_toolchain'으로 끝납니다.
- 참고:
\_toolchain규칙은 빌드 작업을 만들 수 없습니다. 대신 다른 규칙에서 아티팩트를 수집하여 도구 모음을 사용하는 규칙에 전달합니다. 해당 규칙은 모든 빌드 작업을 만듭니다.
- 참고:
서로 다른 플랫폼의 도구 또는 도구 모음 버전을 나타내는 이 규칙 유형의 여러 대상입니다.
각 대상에 대해 도구 모음 프레임워크에서 사용하는 메타데이터를 제공하는 일반
toolchain규칙의 연결된 대상입니다. 이toolchain대상은 이 도구 모음과 연결된toolchain_type도 참조합니다. 즉, 지정된_toolchain규칙은 모든toolchain_type과 연결될 수 있으며, 이_toolchain규칙을 사용하는toolchain인스턴스에서만 규칙이toolchain_type과 연결됩니다.
실행 중인 예시의 경우 bar_toolchain 규칙의 정의는 다음과 같습니다. 이
예시에는 컴파일러만 있지만 링커와 같은 다른 도구도 그 아래에
그룹화할 수 있습니다.
def _bar_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
barcinfo = BarcInfo(
compiler_path = ctx.attr.compiler_path,
system_lib = ctx.attr.system_lib,
arch_flags = ctx.attr.arch_flags,
),
)
return [toolchain_info]
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler_path": attr.string(),
"system_lib": attr.string(),
"arch_flags": attr.string_list(),
},
)
규칙은 ToolchainInfo 제공자를 반환해야 합니다. 이 제공자는 사용 규칙이 ctx.toolchains 및 도구 모음 유형의 라벨을 사용하여 가져오는 객체가 됩니다. ToolchainInfo는 struct와 마찬가지로 임의의 필드-값
쌍을 보유할 수 있습니다. ToolchainInfo
에 추가되는 필드를 정확하게 지정하는 것은 도구 모음 유형에서 명확하게 문서화되어야 합니다. 이 예시에서는 위에 정의된 스키마를 재사용하기 위해 BarcInfo 객체로 래핑된 값을
반환합니다. 이
스타일은 유효성 검사 및 코드 재사용에 유용할 수 있습니다.
이제 특정 barc 컴파일러의 대상을 정의할 수 있습니다.
bar_toolchain(
name = "barc_linux",
arch_flags = [
"--arch=Linux",
"--debug_everything",
],
compiler_path = "/path/to/barc/on/linux",
system_lib = "/usr/lib/libbarc.so",
)
bar_toolchain(
name = "barc_windows",
arch_flags = [
"--arch=Windows",
# Different flags, no debug support on windows.
],
compiler_path = "C:\\path\\on\\windows\\barc.exe",
system_lib = "C:\\path\\on\\windows\\barclib.dll",
)
마지막으로 두 bar_toolchain 대상의 toolchain 정의를 만듭니다.
이러한 정의는 언어별 대상을 도구 모음 유형에 연결하고
Bazel에 도구 모음이 지정된 플랫폼에 적합한 시점을 알려주는 제약조건 정보를 제공합니다.
toolchain(
name = "barc_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_linux",
toolchain_type = ":toolchain_type",
)
toolchain(
name = "barc_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_windows",
toolchain_type = ":toolchain_type",
)
위의 상대 경로 구문 사용은 이러한 정의가 모두 동일한
패키지에 있음을 나타내지만 도구 모음 유형, 언어별
도구 모음 대상, toolchain 정의 대상이 모두 별도의
패키지에 있을 수 없는 이유는 없습니다.
실제 예시는 go_toolchain
을 참고하세요.
도구 모음 및 구성
규칙 작성자에게 중요한 질문은 bar_toolchain 대상이
분석될 때 어떤 구성을 확인하고 종속 항목에 어떤 전환
을 사용해야 하는가입니다. 위의 예시에서는 문자열 속성을 사용하지만
Bazel 저장소의 다른 대상에 종속되는 더 복잡한 도구 모음의 경우 어떻게 될까요?
더 복잡한 버전의 bar_toolchain을 살펴보겠습니다.
def _bar_toolchain_impl(ctx):
# The implementation is mostly the same as above, so skipping.
pass
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler": attr.label(
executable = True,
mandatory = True,
cfg = "exec",
),
"system_lib": attr.label(
mandatory = True,
cfg = "target",
),
"arch_flags": attr.string_list(),
},
)
attr.label의 사용은 표준 규칙과 동일하지만
cfg 매개변수의 의미는 약간 다릅니다.
도구 모음 확인을 통한 대상('상위 요소' 라고 함)에서 도구 모음으로의 종속 항목은 '도구 모음
전환' 이라는 특수 구성 전환을 사용합니다. 도구 모음 전환은 도구 모음의 실행 플랫폼이 상위 요소의 실행 플랫폼과 동일하도록 강제하는 것을 제외하고는 구성을 동일하게 유지합니다.
그렇지 않으면 도구 모음의 도구 모음 확인이 실행 플랫폼을 선택할 수 있으며 상위 요소의 실행 플랫폼과 동일하지 않을 수 있습니다. 이렇게
하면 도구 모음의 모든 exec 종속 항목이 상위 요소의
빌드 작업에 실행 가능하게 됩니다. `cfg = "target"`을 사용하는 도구 모음의 종속 항목 (또는 'target'이 기본값이므로 cfg을 지정하지 않는 종속 항목)은
상위 요소와 동일한 대상 플랫폼용으로 빌드됩니다.cfg =
"target" 이렇게 하면 도구 모음 규칙이
필요한 빌드 규칙에 라이브러리 (system_lib 속성)와 도구 (compiler 속성)를 모두 제공할 수 있습니다. 시스템 라이브러리는 최종 아티팩트에 연결되므로 동일한 플랫폼용으로 빌드해야 하지만 컴파일러는 빌드 중에 호출되는 도구이며 실행 플랫폼에서 실행할 수 있어야 합니다.
도구 모음으로 등록 및 빌드
이 시점에서 모든 빌딩 블록이 조립되었으며 Bazel의 확인 절차에서 도구 모음을 사용할 수 있도록 하기만 하면 됩니다. 이는 도구 모음을 등록하여 수행됩니다. 도구 모음은 MODULE.bazel 파일을 사용하여 register_toolchains()에 등록하거나 --extra_toolchains 플래그를 사용하여 명령줄에서 도구 모음의 라벨을 전달하여 등록할 수 있습니다.
register_toolchains(
"//bar_tools:barc_linux_toolchain",
"//bar_tools:barc_windows_toolchain",
# Target patterns are also permitted, so you could have also written:
# "//bar_tools:all",
# or even
# "//bar_tools/...",
)
대상 패턴을 사용하여 도구 모음을 등록할 때 개별 도구 모음이 등록되는 순서는 다음 규칙에 따라 결정됩니다.
- 패키지의 하위 패키지에 정의된 도구 모음은 패키지 자체에 정의된 도구 모음보다 먼저 등록됩니다.
- 패키지 내에서 도구 모음은 이름의 사전순으로 등록됩니다.
이제 도구 모음 유형에 종속되는 대상을 빌드하면 대상 및 실행 플랫폼을 기반으로 적절한 도구 모음이 선택됩니다.
# my_pkg/BUILD
platform(
name = "my_target_platform",
constraint_values = [
"@platforms//os:linux",
],
)
bar_binary(
name = "my_bar_binary",
...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
Bazel은 //my_pkg:my_bar_binary가 @platforms//os:linux가 있는 플랫폼으로 빌드되고 있음을 확인하므로 //bar_tools:toolchain_type 참조를 //bar_tools:barc_linux_toolchain으로 확인합니다.
이렇게 하면 //bar_tools:barc_linux는 빌드되지만
//bar_tools:barc_windows는 빌드되지 않습니다.
도구 모음 확인
도구 모음을 사용하는 각 대상에 대해 Bazel의 도구 모음 확인 절차 는 대상의 구체적인 도구 모음 종속 항목을 결정합니다. 이 절차는 필수 도구 모음 유형 집합, 대상 플랫폼, 사용 가능한 실행 플랫폼 목록, 사용 가능한 도구 모음 목록을 입력으로 사용합니다. 출력은 각 도구 모음 유형에 대해 선택된 도구 모음과 현재 대상에 대해 선택된 실행 플랫폼입니다.
사용 가능한 실행 플랫폼과 도구 모음은
외부 종속 항목 그래프에서
register_execution_platforms
및
register_toolchains 호출을 통해
MODULE.bazel 파일에서 수집됩니다.
명령줄에서
--extra_execution_platforms
및
--extra_toolchains를 통해 추가 실행 플랫폼과 도구 모음을 지정할 수도 있습니다.
호스트 플랫폼은 사용 가능한 실행 플랫폼으로 자동 포함됩니다.
사용 가능한 플랫폼과 도구 모음은 결정론을 위해 정렬된 목록으로 추적되며,
목록의 이전 항목에 우선순위가 부여됩니다.
우선순위 순서로 사용 가능한 도구 모음 집합은
--extra_toolchains 및 register_toolchains에서 생성됩니다.
--extra_toolchains를 사용하여 등록된 도구 모음이 먼저 추가됩니다. (이러한 도구 모음 중에서 마지막 도구 모음의 우선순위가 가장 높습니다.)- 전이적 외부
종속 항목 그래프에서
register_toolchains를 사용하여 등록된 도구 모음(이러한 도구 모음 중에서 처음 언급된 도구 모음의 우선순위가 가장 높습니다.)- 루트 모듈에서 등록한 도구 모음 (예:
MODULE.bazel의 작업공간 루트) - 사용자의
WORKSPACE파일에 등록된 도구 모음(여기에서 호출된 모든 매크로 포함) - 루트가 아닌 모듈에서 등록한 도구 모음 (예: 루트 모듈에서 지정한 루트 모듈의 종속 항목 및 해당 종속 항목 등)
- 'WORKSPACE 접미사'에 등록된 도구 모음. 이는 Bazel 설치와 함께 번들로 제공되는 특정 네이티브 규칙에서만 사용됩니다.
- 루트 모듈에서 등록한 도구 모음 (예:
참고: :all, :*,
/...와 같은 가상 대상은 사전순으로 정렬되는 Bazel
패키지 로드 메커니즘에 의해 정렬됩니다.
확인 단계는 다음과 같습니다.
목록의 각
constraint_value에 대해 플랫폼에도 해당constraint_value가 있는 경우(target_compatible_with또는exec_compatible_with절이 플랫폼과 일치합니다(명시적으로 또는 기본값으로).플랫폼에 절에서 참조되지 않은
constraint_setting의constraint_value가 있는 경우 일치에 영향을 미치지 않습니다.빌드 중인 대상이
exec_compatible_with속성 (또는 규칙 정의가exec_compatible_with인수를 지정하는 경우) 사용 가능한 실행 플랫폼 목록이 실행 제약조건과 일치하지 않는 플랫폼을 삭제하도록 필터링됩니다.사용 가능한 도구 모음 목록이 현재 구성과 일치하지 않는
target_settings를 지정하는 도구 모음 을 삭제하도록 필터링됩니다.사용 가능한 각 실행 플랫폼에 대해 각 도구 모음 유형을 이 실행 플랫폼 및 대상 플랫폼과 호환되는 첫 번째 사용 가능한 도구 모음과 연결합니다(있는 경우).
도구 모음 유형 중 하나에 호환되는 필수 도구 모음을 찾지 못한 실행 플랫폼은 제외됩니다. 나머지 플랫폼 중에서 첫 번째 플랫폼이 현재 대상의 실행 플랫폼이 되고 연결된 도구 모음 (있는 경우)이 대상의 종속 항목이 됩니다.
선택한 실행 플랫폼은 대상이 생성하는 모든 작업을 실행하는 데 사용됩니다.
동일한 빌드 내에서 동일한 대상을 여러 구성 (예: 서로 다른 CPU)으로 빌드할 수 있는 경우 확인 절차가 대상의 각 버전에 독립적으로 적용됩니다.
규칙이 실행 그룹을 사용하는 경우 각 실행 그룹은 도구 모음 확인을 별도로 실행하며 각 실행 그룹에는 자체 실행 플랫폼과 도구 모음이 있습니다.
도구 모음 디버깅
기존 규칙에 도구 모음 지원을 추가하는 경우
--toolchain_resolution_debug=regex 플래그를 사용합니다. 도구 모음 확인 중에 플래그
는 regex 변수와 일치하는 도구 모음 유형 또는 대상 이름에 대한 상세 출력을 제공합니다.
를 사용하여 모든 정보를 출력할 수 있습니다..* Bazel은 확인 프로세스 중에 확인하고 건너뛰는 도구 모음의 이름을 출력합니다.
도구 모음
확인에서 어떤 cquery 종속 항목이 있는지 확인하려면 cquery's --transitions 플래그를 사용하세요.
# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)
# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
[toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211