구성 가능한 빌드 속성

구성 가능한 속성, 일반적으로 로 알려진 Bazel 기능으로 사용자가 명령줄에서 빌드 규칙 속성의 값 을 전환할 수 있습니다.select()

예를 들어 아키텍처에 적합한 구현을 자동으로 선택하는 멀티 플랫폼 라이브러리 또는 빌드 시간에 맞춤설정할 수 있는 기능 구성 가능한 바이너리에 사용할 수 있습니다.

# myapp/BUILD

cc_binary(
    name = "mybinary",
    srcs = ["main.cc"],
    deps = select({
        ":arm_build": [":arm_lib"],
        ":x86_debug_build": [":x86_dev_lib"],
        "//conditions:default": [":generic_lib"],
    }),
)

config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
    },
)

이렇게 하면 명령줄의 플래그를 기반으로 종속 항목을 '선택'하는 cc_binary가 선언됩니다. 특히 deps는 다음과 같이 됩니다.

명령어 deps =
bazel build //myapp:mybinary --cpu=arm [":arm_lib"]
bazel build //myapp:mybinary -c dbg --cpu=x86 [":x86_dev_lib"]
bazel build //myapp:mybinary --cpu=ppc [":generic_lib"]
bazel build //myapp:mybinary -c dbg --cpu=ppc [":generic_lib"]

select()구성 조건에 따라 선택되는 값의 자리표시자 역할을 합니다. 이는 config_setting 타겟을 참조하는 라벨입니다. 구성 가능한 속성에서 select()를 사용하면 속성이 조건이 다를 때 효과적으로 다른 값을 채택합니다.

일치는 명확해야 합니다. 여러 조건이 일치하는 경우 다음 중 하나입니다. * 모두 동일한 값으로 확인됩니다. 예를 들어 Linux x86에서 실행할 때 두 분기가 모두 "hello"로 확인되므로 {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}는 명확합니다. * 하나의 values는 다른 모든 값의 진포함집합입니다. 예를 들어 values = {"cpu": "x86", "compilation_mode": "dbg"}values = {"cpu": "x86"}의 명확한 전문화입니다.

기본 제공 조건 //conditions:default는 다른 조건이 일치하지 않을 때 자동으로 일치합니다.

이 예에서는 deps를 사용하지만 select()srcs, resources, cmd 및 기타 대부분의 속성에서도 잘 작동합니다. 소수의 속성 만 구성 불가능하며 이러한 속성은 명확하게 주석 처리됩니다. 예를 들어, config_setting's 자체의 values 속성은 구성 불가능합니다.

select() 및 종속 항목

특정 속성은 타겟 아래의 모든 전이 종속 항목의 빌드 매개변수를 변경합니다. 예를 들어 genrule's tools--cpu를 Bazel을 실행하는 머신의 CPU로 변경합니다 (교차 컴파일 덕분에 타겟이 빌드되는 CPU와 다를 수 있음). 이를 구성 전환이라고 합니다.

다음과 같은 경우

#myapp/BUILD

config_setting(
    name = "arm_cpu",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

genrule(
    name = "my_genrule",
    srcs = select({
        ":arm_cpu": ["g_arm.src"],
        ":x86_cpu": ["g_x86.src"],
    }),
    tools = select({
        ":arm_cpu": [":tool1"],
        ":x86_cpu": [":tool2"],
    }),
)

cc_binary(
    name = "tool1",
    srcs = select({
        ":arm_cpu": ["armtool.cc"],
        ":x86_cpu": ["x86tool.cc"],
    }),
)

실행 중

$ bazel build //myapp:my_genrule --cpu=arm

x86 개발 머신에서 빌드를 g_arm.src, tool1, x86tool.cc에 바인딩합니다. my_genrule에 연결된 두 select 모두 --cpu=arm을 포함하는 my_genrule의 빌드 매개변수를 사용합니다. tools 속성은 --cpux86로 변경합니다. tool1 및 전이 종속 항목의 tool1selecttool1's 빌드 매개변수를 사용합니다. 여기에는 --cpu=x86가 포함됩니다.

구성 조건

구성 가능한 속성의 각 키는 config_setting 또는 constraint_value에 대한 라벨 참조입니다.

config_setting은 예상되는 명령줄 플래그 설정의 모음일 뿐입니다. 이를 타겟에 캡슐화하면 사용자가 여러 위치에서 참조할 수 있는 "표준" 조건을 쉽게 유지할 수 있습니다.

constraint_value멀티 플랫폼 동작을 지원합니다.

기본 제공 플래그

--cpu와 같은 플래그는 Bazel에 기본 제공됩니다. 빌드 도구는 모든 프로젝트의 모든 빌드에 대해 이러한 플래그를 기본적으로 이해합니다 . 이러한 플래그는 config_setting's values 속성으로 지정됩니다.

config_setting(
    name = "meaningful_condition_name",
    values = {
        "flag1": "value1",
        "flag2": "value2",
        ...
    },
)

flagN은 플래그 이름입니다 (--가 없는 "cpu"). valueN 은 해당 플래그의 예상 값입니다."--cpu" :meaningful_condition_namevalues모든 항목이 일치하면 일치합니다. 순서는 중요하지 않습니다.

valueN은 명령줄에서 설정된 것처럼 파싱됩니다. 이는 다음을 의미합니다.

  • values = { "compilation_mode": "opt" }bazel build -c opt와 일치합니다.
  • values = { "force_pic": "true" }bazel build --force_pic=1과 일치합니다.
  • values = { "force_pic": "0" }bazel build --noforce_pic과 일치합니다.

config_setting은 타겟 동작에 영향을 미치는 플래그만 지원합니다. 예를 들어 --show_progress는 Bazel이 사용자에게 진행 상황을 보고하는 방식에만 영향을 미치므로 허용되지 않습니다. 타겟은 이 플래그를 사용하여 결과를 구성할 수 없습니다. 지원되는 플래그의 정확한 집합은 문서화되지 않았습니다. 실제로 '합리적인' 대부분의 플래그가 작동합니다.

맞춤 플래그

Starlark 빌드 설정을 사용하여 프로젝트별 플래그를 모델링할 수 있습니다. 기본 제공 플래그와 달리 이러한 플래그는 빌드 타겟으로 정의되므로 Bazel은 타겟 라벨로 참조합니다.

이러한 플래그는 config_setting's flag_values 속성으로 트리거됩니다.

config_setting(
    name = "meaningful_condition_name",
    flag_values = {
        "//myflags:flag1": "value1",
        "//myflags:flag2": "value2",
        ...
    },
)

동작은 기본 제공 플래그와 동일합니다. 작업 예는 여기 를 참고하세요.

--define 은 맞춤 플래그의 대체 레거시 구문입니다 (예: --define foo=bar). 이는 values 속성 (values = {"define": "foo=bar"}) 또는 define_values 속성 (define_values = {"foo": "bar"})으로 표현할 수 있습니다. --define은 이전 버전과의 호환성만 지원합니다. 가능하면 항상 Starlark 빌드 설정을 사용하세요.

values, flag_values, define_values는 독립적으로 평가됩니다. 모든 값의 모든 값이 일치하면 config_setting이 일치합니다.

기본 조건

기본 제공 조건 //conditions:default는 다른 조건 이 일치하지 않을 때 일치합니다.

'정확히 하나의 일치' 규칙으로 인해 일치 항목이 없고 기본 조건이 없는 구성 가능한 속성은 "no matching conditions" 오류를 발생시킵니다. 이렇게 하면 예기치 않은 설정으로 인한 자동 실패를 방지할 수 있습니다.

# myapp/BUILD

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

cc_library(
    name = "x86_only_lib",
    srcs = select({
        ":x86_cpu": ["lib.cc"],
    }),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //myapp:x86_cpu

더 명확한 오류를 위해 select()'s no_match_error 속성으로 맞춤 메시지를 설정할 수 있습니다.

플랫폼

명령줄에서 여러 플래그를 지정하는 기능은 유연성을 제공하지만 타겟을 빌드할 때마다 각 플래그를 개별적으로 설정하는 것은 부담이 될 수 있습니다. 플랫폼 을 사용하면 이러한 플래그를 간단한 번들로 통합할 수 있습니다.

# myapp/BUILD

sh_binary(
    name = "my_rocks",
    srcs = select({
        ":basalt": ["pyroxene.sh"],
        ":marble": ["calcite.sh"],
        "//conditions:default": ["feldspar.sh"],
    }),
)

config_setting(
    name = "basalt",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

config_setting(
    name = "marble",
    constraint_values = [
        ":white",
        ":metamorphic",
    ],
)

# constraint_setting acts as an enum type, and constraint_value as an enum value.
constraint_setting(name = "color")
constraint_value(name = "black", constraint_setting = "color")
constraint_value(name = "white", constraint_setting = "color")
constraint_setting(name = "texture")
constraint_value(name = "smooth", constraint_setting = "texture")
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")

platform(
    name = "basalt_platform",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

platform(
    name = "marble_platform",
    constraint_values = [
        ":white",
        ":smooth",
        ":metamorphic",
    ],
)

플랫폼은 명령줄에서 지정할 수 있습니다. 플랫폼의 constraint_values 하위 집합을 포함하는 config_setting을 활성화하여 이러한 config_settingselect() 표현식에서 일치하도록 합니다.

예를 들어 my_rockssrcs 속성을 calcite.sh로 설정하려면 다음을 실행하면 됩니다.

bazel build //my_app:my_rocks --platforms=//myapp:marble_platform

플랫폼이 없으면 다음과 같이 표시될 수 있습니다.

bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic

select()constraint_value를 직접 읽을 수도 있습니다.

constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
    name = "my_rocks",
    srcs = select({
        ":igneous": ["igneous.sh"],
        ":metamorphic" ["metamorphic.sh"],
    }),
)

이렇게 하면 단일 값에 대해서만 확인해야 할 때 상용구 config_setting이 필요하지 않습니다.

플랫폼은 아직 개발 중입니다. 자세한 내용은 문서를 참조하세요.

select() 결합

select는 동일한 속성에 여러 번 표시될 수 있습니다.

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"] +
           select({
               ":armeabi_mode": ["armeabi_src.sh"],
               ":x86_mode": ["x86_src.sh"],
           }) +
           select({
               ":opt_mode": ["opt_extras.sh"],
               ":dbg_mode": ["dbg_extras.sh"],
           }),
)

select는 다른 select 내에 표시될 수 없습니다. 를 중첩해야 하고 속성이 다른 타겟을 값으로 사용하는 경우 중간 타겟을 사용하세요.selects

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":armeabi_mode": [":armeabi_lib"],
        ...
    }),
)

sh_library(
    name = "armeabi_lib",
    srcs = select({
        ":opt_mode": ["armeabi_with_opt.sh"],
        ...
    }),
)

여러 조건이 일치할 때 select가 일치해야 하는 경우 AND 체이닝을 고려하세요.

OR 체이닝

다음을 고려하세요.

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": [":standard_lib"],
        ":config2": [":standard_lib"],
        ":config3": [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

대부분의 조건은 동일한 종속 항목으로 평가됩니다. 하지만 이 구문은 읽고 유지보수하기 어렵습니다. [":standard_lib"]를 여러 번 반복하지 않는 것이 좋습니다.

한 가지 옵션은 값을 BUILD 변수로 미리 정의하는 것입니다.

STANDARD_DEP = [":standard_lib"]

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": STANDARD_DEP,
        ":config2": STANDARD_DEP,
        ":config3": STANDARD_DEP,
        ":config4": [":special_lib"],
    }),
)

이렇게 하면 종속 항목을 더 쉽게 관리할 수 있습니다. 하지만 여전히 불필요한 중복이 발생합니다.

더 직접적인 지원을 받으려면 다음 중 하나를 사용하세요.

selects.with_or

with_or 매크로는 모듈 내에서 직접 조건을 OR링하는 것을 지원합니다.selectsselect

load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = selects.with_or({
        (":config1", ":config2", ":config3"): [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

selects.config_setting_group

Skylib의 `selects` 모듈에 있는 config_setting_group 매크로는 여러 config_settingOR링하는 것을 지원합니다:selects

load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_or_2",
    match_any = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_or_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

selects.with_or와 달리 여러 타겟은 여러 속성에서 :config1_or_2를 공유할 수 있습니다.

여러 조건이 일치하는 것은 오류입니다. 단, 하나가 다른 조건의 명확한 "전문화"이거나 모두 동일한 값으로 확인되는 경우는 예외입니다. 자세한 내용은 여기를 참고하세요.

AND 체이닝

여러 조건이 일치할 때 select 분기가 일치해야 하는 경우 Skylib 매크로 config_setting_group을 사용하세요.

config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_and_2",
    match_all = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_and_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

OR 체이닝과 달리 기존 config_settingselect 내에서 직접 AND될 수 없습니다. 명시적으로 config_setting_group으로 래핑해야 합니다.

맞춤 오류 메시지

기본적으로 일치하는 조건이 없으면 select()가 연결된 타겟이 다음 오류와 함께 실패합니다.

ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //tools/cc_target_os:darwin
  //tools/cc_target_os:android

이는 no_match_error 속성으로 맞춤설정할 수 있습니다.

cc_library(
    name = "my_lib",
    deps = select(
        {
            "//tools/cc_target_os:android": [":android_deps"],
            "//tools/cc_target_os:windows": [":windows_deps"],
        },
        no_match_error = "Please build with an Android or Windows toolchain",
    ),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain

규칙 호환성

규칙 구현은 구성 가능한 속성의 확인된 값 을 수신합니다. 다음과 같은 경우

# myapp/BUILD

some_rule(
    name = "my_target",
    some_attr = select({
        ":foo_mode": [":foo"],
        ":bar_mode": [":bar"],
    }),
)
$ bazel build //myapp/my_target --define mode=foo

규칙 구현 코드는 ctx.attr.some_attr[":foo"]로 봅니다.

매크로는 select() 절을 허용하고 이를 기본 규칙에 전달할 수 있습니다. 하지만 직접 조작할 수는 없습니다. 예를 들어 매크로가 다음을 변환할 방법은 없습니다.

select({"foo": "val"}, ...)

다음으로

select({"foo": "val_with_suffix"}, ...)

여기에는 두 가지 이유가 있습니다.

첫째, select가 선택할 경로를 알아야 하는 매크로는 플래그 값을 알기 전에 발생하는 Bazel의 로드 단계에서 평가되므로 작동할 수 없습니다. 이는 Bazel의 핵심 설계 제한사항이며 조만간 변경될 가능성은 낮습니다.

둘째, 모든 select 경로를 반복하기만 하면 되는 매크로는 기술적으로는 가능하지만 일관된 UI가 없습니다. 이를 변경하려면 추가 설계가 필요합니다.

Bazel 쿼리 및 cquery

Bazel query는 Bazel's 로드 단계에서 작동합니다. 즉, 빌드 후반 (분석 단계)까지 플래그가 평가되지 않으므로 타겟이 사용하는 명령줄 플래그를 알 수 없습니다. 따라서 선택되는 select() 분기를 결정할 수 없습니다.

Bazel cquery는 Bazel의 분석 단계 후에 작동하므로 이 모든 정보를 보유하고 있으며 select()를 정확하게 확인할 수 있습니다.

다음을 고려하세요.

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD

string_flag(
    name = "dog_type",
    build_setting_default = "cat"
)

cc_library(
    name = "my_lib",
    deps = select({
        ":long": [":foo_dep"],
        ":short": [":bar_dep"],
    }),
)

config_setting(
    name = "long",
    flag_values = {":dog_type": "dachshund"},
)

config_setting(
    name = "short",
    flag_values = {":dog_type": "pug"},
)

query:my_lib의 종속 항목을 과대평가합니다.

$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep

반면 cquery는 정확한 종속 항목을 보여줍니다.

$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep

FAQ

매크로에서 select()가 작동하지 않는 이유는 무엇인가요?

select()는 규칙에서 작동합니다. 자세한 내용은 규칙 호환성을(를) 참고하세요.

이 질문이 일반적으로 의미하는 주요 문제는 select()가 매크로에서 작동하지 않는다는 것입니다. 이는 규칙과 다릅니다. 차이점을 이해하려면 규칙매크로에 관한 문서를 참고하세요. 다음은 엔드 투 엔드 예입니다.

규칙 및 매크로 정의:

# myapp/defs.bzl

# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
    name = ctx.attr.name
    allcaps = ctx.attr.my_config_string.upper()  # This works fine on all values.
    print("My name is " + name + " with custom message: " + allcaps)

# Rule declaration:
my_custom_bazel_rule = rule(
    implementation = _impl,
    attrs = {"my_config_string": attr.string()},
)

# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
    allcaps = my_config_string.upper()  # This line won't work with select(s).
    print("My name is " + name + " with custom message: " + allcaps)

규칙 및 매크로 인스턴스화:

# myapp/BUILD

load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")

my_custom_bazel_rule(
    name = "happy_rule",
    my_config_string = select({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "second string",
    }),
)

my_custom_bazel_macro(
    name = "happy_macro",
    my_config_string = "fixed string",
)

my_custom_bazel_macro(
    name = "sad_macro",
    my_config_string = select({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "other string",
    }),
)

sad_macroselect()를 처리할 수 없으므로 빌드가 실패합니다.

$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.

sad_macro를 주석 처리하면 빌드가 성공합니다.

# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.

매크로는 Bazel이 빌드의 명령줄 플래그를 읽기 전에 평가되므로 정의상 변경할 수 없습니다. 즉, select()를 평가할 정보가 충분하지 않습니다.

하지만 매크로는 select()를 불투명 blob으로 규칙에 전달할 수 있습니다.

# myapp/defs.bzl

def my_custom_bazel_macro(name, my_config_string):
    print("Invoking macro " + name)
    my_custom_bazel_rule(
        name = name + "_as_target",
        my_config_string = my_config_string,
    )
$ bazel build //myapp:sad_macro_less_sad
DEBUG: /myworkspace/myapp/defs.bzl:23:3: Invoking macro sad_macro_less_sad.
DEBUG: /myworkspace/myapp/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING.

select()가 항상 true를 반환하는 이유는 무엇인가요?

정의상 매크로 (규칙은 아님)는 를 평가할 수 없으므로 이렇게 하려고 하면 일반적으로 오류가 발생합니다.select()

ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().

불리언은 자동으로 실패하는 특별한 경우이므로 특히 주의해야 합니다.

$ cat myapp/defs.bzl
def my_boolean_macro(boolval):
  print("TRUE" if boolval else "FALSE")

$ cat myapp/BUILD
load("//myapp:defs.bzl", "my_boolean_macro")
my_boolean_macro(
    boolval = select({
        "//third_party/bazel_platforms/cpu:x86_32": True,
        "//third_party/bazel_platforms/cpu:ppc": False,
    }),
)

$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.

이는 매크로가 select()의 콘텐츠를 이해하지 못하기 때문에 발생합니다. 따라서 실제로 평가하는 것은 select() 객체 자체입니다. Pythonic 설계 표준에 따라 매우 적은 수의 예외를 제외한 모든 객체는 자동으로 true를 반환합니다.

select()를 dict처럼 읽을 수 있나요?

매크로는 Bazel이 빌드의 명령줄 매개변수를 알기 전에 평가되므로 select()를 평가할 수 없습니다. 적어도 select()의 사전을 읽어 각 값에 접미사를 추가할 수 있나요?

개념적으로는 가능하지만 아직 Bazel 기능은 아닙니다. 현재 할 수 있는 작업은 일반 사전을 준비한 후 select()에 전달하는 것입니다.

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
  for key in select_cmd.keys():
    select_cmd[key] += " WITH SUFFIX"
  native.genrule(
      name = name,
      outs = [name + ".out"],
      srcs = [],
      cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
        + " > $@"
  )

$ cat myapp/BUILD
selecty_genrule(
    name = "selecty",
    select_cmd = {
        "//third_party/bazel_platforms/cpu:x86_32": "x86 mode",
    },
)

$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX

select()와 기본 유형을 모두 지원하려면 다음을 실행하면 됩니다.

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
    cmd_suffix = ""
    if type(select_cmd) == "string":
        cmd_suffix = select_cmd + " WITH SUFFIX"
    elif type(select_cmd) == "dict":
        for key in select_cmd.keys():
            select_cmd[key] += " WITH SUFFIX"
        cmd_suffix = select(select_cmd + {"//conditions:default": "default"})

    native.genrule(
        name = name,
        outs = [name + ".out"],
        srcs = [],
        cmd = "echo " + cmd_suffix + "> $@",
    )

select()가 bind()와 함께 작동하지 않는 이유는 무엇인가요?

무엇보다도 bind()를 사용하지 마세요. alias()로 대체되어 지원 중단되었습니다.

기술적인 답변은 bind()가 BUILD 규칙이 아닌 저장소 규칙이라는 것입니다.

저장소 규칙에는 특정 구성이 없으며 BUILD 규칙과 동일한 방식으로 평가되지 않습니다. 따라서 select()bind()는 실제로 특정 분기로 평가될 수 없습니다.

대신 alias()를 사용해야 합니다. select()actual 속성에 있습니다. 이러한 유형의 런타임 결정을 실행합니다. This 는 BUILD 규칙이며 특정 구성으로 평가되므로 올바르게 작동합니다.alias()

$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)

$ cat BUILD
config_setting(
    name = "alt_ssl",
    define_values = {
        "ssl_library": "alternative",
    },
)

alias(
    name = "ssl",
    actual = select({
        "//:alt_ssl": "@alternative//:ssl",
        "//conditions:default": "@boringssl//:ssl",
    }),
)

이 설정을 사용하면 --define ssl_library=alternative를 전달할 수 있으며 //:ssl 또는 //external:ssl에 종속된 모든 타겟은 @alternative//:ssl에 있는 대안을 보게 됩니다.

하지만 정말로 bind() 사용을 중단하세요.

select()가 예상대로 선택하지 않는 이유는 무엇인가요?

//myapp:foo에 예상한 조건을 선택하지 않는 select()가 있는 경우 cquerybazel config를 사용하여 디버그합니다.

//myapp:foo가 빌드하는 최상위 타겟인 경우 다음을 실행합니다.

$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)

하위 그래프의 어딘가에서 //myapp:foo에 종속된 다른 타겟 //bar를 빌드하는 경우 다음을 실행합니다.

$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar   (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)

//myapp:foo 옆에 있는 (12e23b9a2b534a)//myapp:fooselect()를 확인하는 구성의 해시입니다. bazel config를 사용하여 값을 검사할 수 있습니다.

$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
  cpu: darwin
  compilation_mode: fastbuild
  ...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
  linkopt: [-Dfoo=bar]
  ...
}
...

그런 다음 이 출력을 각 config_setting에서 예상하는 설정과 비교합니다.

//myapp:foo는 동일한 빌드의 여러 구성에 있을 수 있습니다. 올바른 구성을 가져오는 데 somepath를 사용하는 방법에 관한 안내는 cquery 문서를 참고하세요.

select()가 플랫폼에서 작동하지 않는 이유는 무엇인가요?

Bazel은 의미가 명확하지 않으므로 지정된 플랫폼 이 타겟 플랫폼인지 확인하는 구성 가능한 속성을 지원하지 않습니다.

예를 들면 다음과 같습니다.

platform(
    name = "x86_linux_platform",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

BUILD 파일에서 타겟 플랫폼에 @platforms//cpu:x86@platforms//os:linux 제약 조건이 모두 있지만 여기에 정의된 :x86_linux_platform아닌 경우 어떤 select()를 사용해야 할까요? BUILD 파일의 작성자와 별도의 플랫폼을 정의한 사용자 는 서로 다른 아이디어를 가지고 있을 수 있습니다.

대체할 수 있는 방법은 무엇인가요?

대신 이러한 제약 조건이 있는 모든 플랫폼과 일치하는 config_setting을 정의합니다.

config_setting(
    name = "is_x86_linux",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_x86_linux": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

이 프로세스는 특정 의미를 정의하여 사용자에게 원하는 조건을 충입하는 플랫폼을 더 명확하게 보여줍니다.

플랫폼에서 select를 정말로 하고 싶다면 어떻게 해야 하나요?

빌드 요구사항에 플랫폼 확인이 필요한 경우 --platforms 플래그의 값을 config_setting에서 전환할 수 있습니다.

config_setting(
    name = "is_specific_x86_linux_platform",
    values = {
        "platforms": ["//package:x86_linux_platform"],
    },
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

Bazel팀은 이 작업을 권장하지 않습니다. 빌드를 지나치게 제한하고 예상 조건이 일치하지 않을 때 사용자를 혼란스럽게 합니다.