BazelCon 2022는 11월 16~17일에 뉴욕과 온라인에서 개최됩니다.
지금 등록하기

규칙

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

규칙은 출력에 대해 Bazel이 입력에 대해 수행하는 일련의 작업을 정의하며, 이는 규칙의 구현 함수에서 반환된 제공업체에서 참조됩니다. 예를 들어 C++ 바이너리 규칙은 다음과 같을 수 있습니다.

  1. .cpp 소스 파일 (입력) 세트를 사용하세요.
  2. 소스 파일 (작업)에서 g++를 실행합니다.
  3. 실행 가능한 출력과 기타 파일을 포함하여 DefaultInfo 제공업체를 반환하여 런타임에 제공합니다.
  4. 타겟 및 종속 항목에서 수집한 C++ 관련 정보가 포함된 CcInfo 제공자를 반환합니다.

Bazel의 관점에서 g++ 및 표준 C++ 라이브러리도 이 규칙에 대한 입력입니다. 규칙 작성자는 규칙에 사용자가 제공한 입력뿐만 아니라 작업을 실행하는 데 필요한 모든 도구와 라이브러리를 고려해야 합니다.

규칙을 만들거나 수정하기 전에 Bazel의 빌드 단계를 숙지해야 합니다. 빌드의 3가지 단계 (로드, 분석, 실행)를 이해하는 것이 중요합니다. 규칙과 매크로의 차이를 이해하기 위해 매크로에 대해 알아보는 것도 유용합니다. 시작하려면 먼저 규칙 가이드를 검토하세요. 그런 다음 이 페이지를 참고 자료로 사용하세요.

Bazel 자체에 몇 가지 규칙이 내장되어 있습니다. 이러한 네이티브 규칙(예: cc_libraryjava_binary)은 특정 언어를 위한 핵심 지원을 제공합니다. 자체 규칙을 정의하면 Bazel이 기본적으로 지원하지 않는 언어 및 도구에 대한 유사한 지원을 추가할 수 있습니다.

Bazel은 Starlark 언어를 사용하여 규칙을 작성하기 위한 확장성 모델을 제공합니다. 이러한 규칙은 .bzl 파일에서 작성되며 BUILD 파일에서 직접 로드될 수 있습니다.

자체 규칙을 정의할 때 규칙에서 지원하는 속성과 출력을 생성하는 방법을 결정할 수 있습니다.

규칙의 implementation 함수는 분석 단계 중에 동작을 정확하게 정의합니다. 이 함수는 외부 명령어를 실행하지 않습니다. 대신 필요한 경우 실행 단계에서 규칙의 출력을 빌드하는 데 사용할 작업을 등록합니다.

규칙 생성

.bzl 파일에서 rule 함수를 사용하여 새 규칙을 정의하고 결과를 전역 변수에 저장합니다. rule 호출은 속성구현 함수를 지정합니다.

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

이는 example_library라는 규칙의 종류를 정의합니다.

또한 rule 호출은 규칙이 실행 출력 (executable=True 사용)을 만드는지, 아니면 테스트 실행 파일 (test=True 포함)을 만드는지를 지정해야 합니다. 후자의 경우 규칙은 테스트 규칙이며 규칙의 이름은 _test로 끝나야 합니다.

대상 인스턴스화

규칙은 로드하여 BUILD 파일에서 호출할 수 있습니다.

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

빌드 규칙을 호출할 때마다 값이 반환되지 않지만 대상을 정의하는 부작용이 있습니다. 이를 규칙 인스턴트화라고 합니다. 새 타겟의 이름과 타겟의 속성 값을 지정합니다.

Starlark 함수에서 규칙을 호출하고 .bzl 파일에 로드할 수도 있습니다. 규칙을 호출하는 Starlark 함수를 Starlark 매크로라고 합니다. Starlark 매크로는 궁극적으로 BUILD 파일에서 호출되어야 하며, BUILD 파일이 대상을 인스턴스화하도록 평가되는 로드 단계 중에만 호출할 수 있습니다.

속성

속성은 규칙 인수입니다. 속성은 대상의 구현에 특정 값을 제공하거나 다른 타겟을 참조하여 종속 항목 그래프를 만들 수 있습니다.

srcs 또는 deps과 같은 규칙별 속성은 속성 이름에서 attr 모듈을 사용하여 만든 스키마를 스키마의 attrs 매개변수에 전달하여 정의합니다. 일반 속성(예: namevisibility)은 모든 규칙에 암시적으로 추가됩니다. 추가적인 속성은 암시적으로 실행 및 테스트 규칙에 명시적으로 추가됩니다. 규칙에 암시적으로 추가된 속성은 attrs에 전달되는 사전에 포함할 수 없습니다.

종속 항목 속성

소스 코드를 처리하는 규칙은 일반적으로 다음 속성을 정의하여 다양한 종속 항목 유형을 처리합니다.

  • srcs는 타겟의 작업에서 처리하는 소스 파일을 지정합니다. 속성 스키마는 규칙이 처리하는 소스 파일의 종류에 예상되는 파일 확장자를 지정하는 경우가 많습니다. 헤더 파일이 포함된 언어 규칙은 일반적으로 대상과 그 소비자가 처리하는 헤더에 별도의 hdrs 속성을 지정합니다.
  • deps은 타겟의 코드 종속 항목을 지정합니다. 속성 스키마는 종속 항목이 제공해야 하는 제공업체를 지정해야 합니다. 예를 들어 cc_libraryCcInfo를 제공합니다.
  • data은 런타임에 종속된 실행 파일에 제공할 파일을 지정합니다. 이렇게 하면 임의의 파일을 지정할 수 있습니다.
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

다음은 종속 항목 속성의 예입니다. 입력 라벨을 지정하는 모든 속성 (attr.label_list, attr.label 또는 attr.label_keyed_string_dict으로 정의된 속성)은 타겟이 정의될 때 타겟과 라벨 (또는 상응하는 Label 객체)이 나열되는 타겟 간의 특정 유형 종속 항목을 지정합니다. 이러한 라벨의 저장소(및 경로)는 정의된 대상을 기준으로 확인됩니다.

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

이 예에서 other_targetmy_target의 종속 항목이므로 other_target이 먼저 분석됩니다. 대상의 종속 항목 그래프에 주기가 있으면 오류가 발생합니다.

비공개 속성 및 암시적 종속 항목

종속 항목이 기본값을 가진 암시적 종속 항목이 생성됩니다. 암시적인 이유는 사용자가 BUILD 파일에 지정하지 않는 타겟 그래프의 일부이기 때문입니다. 암시적 종속 항목은 일반적으로 규칙과 도구 간의 관계(컴파일러와 같은 빌드 시간 종속 항목)에 하드 코딩하는 데 유용합니다. 사용자가 규칙에서 사용하는 도구를 지정하는 데 관심이 없기 때문입니다. 규칙의 구현 함수에서는 이를 다른 종속 항목과 동일하게 취급합니다.

사용자가 해당 값을 재정의하도록 허용하지 않고 암시적 종속 항목을 제공하려는 경우 밑줄 (_)로 시작하는 이름을 지정하여 속성을 private으로 만들면 됩니다. 비공개 속성에는 기본값이 있어야 합니다. 일반적으로 암시적 종속 항목에 비공개 속성만 사용하는 것이 합리적입니다.

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

이 예에서 example_library 유형의 모든 대상은 컴파일러 //tools:example_compiler에 암시적 종속 항목을 갖습니다. 이렇게 하면 사용자가 라벨을 입력으로 전달하지 않았더라도 example_library의 구현 함수가 컴파일러를 호출하는 작업을 생성할 수 있습니다. _compiler는 비공개 속성이므로 ctx.attr._compiler가 이 규칙 유형의 모든 대상에서 항상 //tools:example_compiler을 가리킵니다. 또는 밑줄 없이 속성 이름을 compiler로 지정하고 기본값을 유지할 수 있습니다. 이를 통해 사용자는 필요한 경우 다른 컴파일러를 대체할 수 있지만 컴파일러 라벨을 인식할 필요는 없습니다.

암시적 종속 항목은 일반적으로 규칙 구현과 동일한 저장소에 있는 도구에 사용됩니다. 도구가 실행 플랫폼이나 다른 저장소에서 제공하는 경우 규칙은 도구 모음에서 도구를 얻어야 합니다.

출력 속성

attr.outputattr.output_list과 같은 출력 속성은 대상이 생성하는 출력 파일을 선언합니다. 이 속성은 종속 항목과 다음과 같은 두 가지 차이점이 있습니다.

  • 다른 위치에서 정의된 타겟을 참조하는 대신 출력 파일 대상을 정의합니다.
  • 출력 파일 대상은 반대의 방법이 아니라 인스턴스화된 규칙 대상에 따라 달라집니다.

일반적으로 출력 속성은 대상 이름을 기반으로 할 수 없는 사용자 정의 이름으로 출력을 만들어야 하는 경우에만 사용됩니다. 규칙에 출력 속성이 1개면 일반적으로 이름은 out 또는 outs입니다.

출력 속성은 사전 선언 출력을 만들 때 선호되는 방법이며, 명시적으로 또는 명령줄에서 요청할 수 있습니다.

구현 함수

모든 규칙에는 implementation 함수가 필요합니다. 이러한 함수는 분석 단계에서 엄격하게 실행되며, 로드 단계에서 생성된 타겟의 그래프를 실행 단계 중에 실행될 작업의 그래프로 변환합니다. 따라서 구현 함수는 실제로 파일을 읽거나 쓸 수 없습니다.

규칙 구현 함수는 일반적으로 비공개이며 선행 밑줄로 이름이 지정됩니다. 일반적으로 규칙 이름은 동일하지만 _impl 접미사가 붙습니다.

구현 함수는 정확히 1개의 매개변수, 즉 일반적으로 ctx이라는 규칙 컨텍스트를 사용합니다. 제공업체 목록을 반환합니다.

대상

종속 항목은 분석 시간에 Target 객체로 표현됩니다. 이러한 객체에는 대상의 구현 함수가 실행될 때 생성된 제공업체가 포함됩니다.

ctx.attr에는 각 종속 항목 속성의 이름에 상응하는 필드가 포함되어 있으며, 이 속성을 통해 각 직접 종속 항목을 나타내는 Target 객체가 포함되어 있습니다. label_list 속성의 경우 Targets 목록입니다. label 속성의 경우 단일 Target 또는 None입니다.

제공자 객체의 목록은 타겟의 구현 함수에 의해 반환됩니다.

return [ExampleInfo(headers = depset(...))]

제공자 유형을 키로 사용하여 색인 표기법 ([])을 사용하여 액세스할 수 있습니다. Starlark에서 정의된 커스텀 제공자 또는 Starlark 전역 변수로 제공되는 네이티브 규칙의 제공업체가 될 수 있습니다.

예를 들어, 규칙이 hdrs 속성을 통해 헤더 파일을 가져와 타겟과 그 소비자의 컴파일 작업에 제공하는 경우 다음과 같이 규칙을 수집할 수 있습니다.

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

제공자 객체 목록 대신 struct가 타겟의 구현 함수에서 반환되는 기존 스타일의 경우:

return struct(example_info = struct(headers = depset(...)))

제공자는 Target 객체의 해당 필드에서 가져올 수 있습니다.

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

이 스타일은 권장되지 않으며 규칙은 스타일에서 이전해야 합니다.

파일

파일은 File 객체로 표현됩니다. Bazel은 분석 단계 중에 파일 I/O를 수행하지 않으므로 이러한 객체를 사용하여 파일 콘텐츠를 직접 읽거나 쓸 수 없습니다. 대신 작업 그래프 함수 (ctx.actions 참고)로 전달되어 작업 그래프 조각을 구성합니다.

File은 소스 파일 또는 생성된 파일일 수 있습니다. 생성된 각 파일은 정확히 1개의 작업의 출력이어야 합니다. 소스 파일은 어떤 작업의 출력도 할 수 없습니다.

각 종속 항목 속성의 경우, ctx.files의 해당 필드에는 이 속성을 통한 모든 종속 항목의 기본 출력 목록이 포함됩니다.

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive=transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file에는 사양이 allow_single_file=True로 설정된 종속 항목 속성의 단일 File 또는 None이 포함되어 있습니다. ctx.executablectx.file과 동일하게 작동하지만 사양이 executable=True로 설정된 종속 항목 속성의 필드만 포함합니다.

출력 선언

분석 단계 중에는 규칙의 구현 함수에서 출력을 생성할 수 있습니다. 로드 단계에서 모든 라벨을 알아야 하므로 이러한 추가 출력에는 라벨이 없습니다. ctx.actions.declare_filectx.actions.declare_directory를 사용하여 출력용 File 객체를 만들 수 있습니다. 출력 이름은 대개 대상의 이름(ctx.label.name)을 기반으로 합니다.

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

출력 속성과 같이 선언된 출력의 경우 ctx.outputs의 해당 필드에서 File 객체를 대신 가져올 수 있습니다.

작업

작업은 입력 집합에서 출력 집합을 생성하는 방법을 설명합니다. 예를 들어 quo.c에서 gcc를 실행하고 hello.o"를 가져옵니다. 작업이 생성되면 Bazel은 명령어를 즉시 실행하지 않습니다. 한 작업이 다른 작업의 출력에 종속될 수 있으므로 종속 항목 그래프에 이를 등록합니다. 예를 들어 C에서 링커는 컴파일러 다음에 호출되어야 합니다.

작업을 생성하는 범용 함수는 ctx.actions에 정의됩니다.

ctx.actions.args을 사용하여 작업의 인수를 효율적으로 누적할 수 있습니다. 실행 시간까지 디셋 평탄화를 방지합니다.

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive=transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive=[headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with=",")
    args.add_joined("-s", srcs, join_with=",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

작업은 입력 파일의 목록 또는 디셋을 취하고 비어 있지 않은 출력 파일 목록을 생성합니다. 입력 및 출력 파일 세트는 분석 단계 중에 알려져 있어야 합니다. 종속 항목의 제공업체를 포함하여 속성 값에 따라 달라질 수 있지만 실행 결과에 따라 달라질 수 있습니다. 예를 들어 작업에서 압축 해제 명령어를 실행하는 경우 압축이 해제될 것으로 예상되는 파일을 지정해야 합니다 (압축 풀기 실행 전에). 내부적으로 가변적인 수의 파일을 생성하는 작업은 단일 파일 (예: zip, tar 또는 기타 보관 형식)으로 파일을 래핑할 수 있습니다.

작업은 입력 항목을 모두 나열해야 합니다. 사용되지 않는 입력을 나열하는 것은 허용되지만 비효율적입니다.

작업은 모든 출력을 생성해야 합니다. 다른 파일을 쓸 수도 있지만 출력에 없는 파일은 소비자에게 제공되지 않습니다. 선언된 모든 출력은 어떤 작업으로도 기록되어야 합니다.

작업은 순수 함수와 비슷합니다. 작업은 제공된 입력에만 의존해야 하며 컴퓨터 정보, 사용자 이름, 시계, 네트워크 또는 I/O 기기에 액세스하지 않아야 합니다 (입력 읽기 및 출력 쓰기 제외). 출력이 캐시되고 재사용되므로 이는 중요합니다.

종속 항목은 Bazel에 의해 해결되며, 이는 실행될 작업을 결정합니다. 종속 항목 그래프에 주기가 있는 경우 오류가 발생합니다. 작업을 만들어도 실행이 보장되지는 않으며, 이는 빌드에 출력이 필요한지에 따라 달라집니다.

제공업체

제공자는 규칙에서 규칙을 사용하는 다른 규칙에 노출하는 정보의 일부입니다. 이 데이터에는 출력 파일, 라이브러리, 도구의 명령줄을 통해 전달할 매개변수 또는 타겟에 관해 소비자가 알아야 하는 모든 항목이 포함될 수 있습니다.

규칙의 구현 함수는 인스턴스화된 대상의 직접적인 종속 항목에서만 제공자를 읽을 수 있으므로 규칙은 대상의 종속 항목에 있는 정보를 전달해 야 합니다. 일반적으로 depset에 축적하여 대상 소비자의 종속 항목을 전달해야 합니다.

타겟 공급자는 구현 함수에서 반환한 Provider 객체 목록으로 지정됩니다.

이전 구현 함수는 제공업체 객체 목록 대신 struct을 반환하는 기존 스타일로 작성할 수도 있습니다. 이 스타일은 권장되지 않으며 규칙은 스타일에서 이전해야 합니다.

기본 출력

대상의 기본 출력은 명령줄에서 빌드를 요청할 때 기본적으로 요청되는 출력입니다. 예를 들어 java_library 대상 //pkg:foo는 기본 출력으로 foo.jar을 가리키므로 bazel build //pkg:foo 명령어로 빌드됩니다.

기본 출력은 DefaultInfofiles 매개변수로 지정됩니다.

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

규칙 구현에서 DefaultInfo를 반환하지 않거나 files 매개변수가 지정되지 않은 경우 DefaultInfo.files는 기본적으로 모든 사전 선언된 출력(일반적으로 출력 속성에서 생성된 출력)으로 설정됩니다.

작업을 실행하는 규칙은 해당 출력이 직접 사용될 것으로 예상되지 않는 경우에도 기본 출력을 제공해야 합니다. 요청된 출력의 그래프에 없는 작업은 잘립니다. 출력이 타겟의 소비자에 의해서만 사용되는 경우, 타겟은 격리된 상태로 빌드될 때 실행되지 않습니다. 실패한 타겟만 다시 빌드하면 오류가 재현되지 않기 때문에 디버깅이 더 어려워집니다.

실행 파일

실행 파일은 (대상이 아닌) 런타임에 대상이 사용하는 파일 집합입니다. 실행 단계 중에 Bazel은 실행 파일을 가리키는 심볼릭 링크를 포함하는 디렉터리 트리를 만듭니다. 런타임 시 실행 파일에 액세스할 수 있도록 바이너리의 환경을 스테이징합니다.

규칙을 만드는 동안 실행 파일을 수동으로 추가할 수 있습니다. 규칙 컨텍스트의 ctx.runfiles runfiles 메서드에서 runfiles 객체를 만들고 DefaultInforunfiles 매개변수에 전달할 수 있습니다. 실행 파일 규칙의 실행 가능한 출력은 실행 파일에 암시적으로 추가됩니다.

일부 규칙은 일반적으로 data라는 속성을 지정하며, 타겟의 실행 파일에 출력이 추가됩니다. 또한 실행 파일은 data뿐 아니라 최종 실행 코드를 제공할 수 있는 속성(일반적으로 srcs(연결된 data가 있는 filegroup 타겟 포함) 및 deps)에서도 병합되어야 합니다.

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

커스텀 제공업체

provider 함수를 사용하여 공급자를 정의하여 규칙별 정보를 전달할 수 있습니다.

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields={
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    })

규칙 구현 함수는 다음과 같이 제공업체 인스턴스를 구성하고 반환할 수 있습니다.

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
제공자의 맞춤 초기화

커스텀 사전 처리 및 유효성 검사 로직을 사용하여 제공자의 인스턴스화를 보호할 수 있습니다. 모든 제공업체 인스턴스가 특정 변형을 준수하도록 하거나 사용자에게 인스턴스를 더 깔끔한 API를 제공하기 위해 사용할 수 있습니다.

이렇게 하려면 init 콜백을 provider 함수에 전달합니다. 이 콜백이 주어지면 provider()의 반환 유형이 두 값의 튜플로 변경됩니다. 즉, init가 사용되지 않으면 일반적인 반환 값인 제공자 기호와 '원시 생성자'입니다.

이 경우 제공자 기호가 호출될 때 새 인스턴스를 직접 반환하는 대신 init 콜백과 함께 인수를 전달합니다. 콜백의 반환 값은 필드 이름 (문자열)을 값에 매핑하는 dict여야 합니다. 이는 새 인스턴스의 필드를 초기화하는 데 사용됩니다. 콜백에는 서명이 있을 수 있으며 인수가 서명과 일치하지 않으면 콜백이 직접 호출되는 것처럼 오류가 보고됩니다.

반면에 원시 생성자는 init 콜백을 우회합니다.

다음 예시에서는 init를 사용하여 인수를 사전 처리하고 검증합니다.

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# It's possible to define an init accepting positional arguments, but
# keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {'files_to_link': files_to_link, 'headers': all_headers}

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init)

export ExampleInfo

규칙 구현은 다음과 같이 제공업체를 인스턴스화할 수 있습니다.

    ExampleInfo(
        files_to_link=my_files_to_link,  # may not be empty
        headers = my_headers,  # will automatically include the core headers
    )

원시 생성자는 init 로직을 통과하지 않는 대체 공개 팩토리 함수를 정의하는 데 사용할 수 있습니다. 예를 들어 exampleinfo.bzl에서 다음과 같이 정의할 수 있습니다.

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

일반적으로 원시 생성자는 이름이 밑줄(위의 _new_exampleinfo)로 시작하는 변수에 결합되므로 사용자 코드에서 로드할 수 없고 임의의 제공업체 인스턴스를 생성할 수 없습니다.

init의 또 다른 용도는 단순히 사용자가 제공자 기호를 아예 호출하지 못하게 하고 대신 팩토리 함수를 사용하도록 하는 것입니다.

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

실행 가능한 규칙 및 테스트 규칙

실행 가능한 규칙은 bazel run 명령어로 호출할 수 있는 대상을 정의합니다. 테스트 규칙은 bazel test 명령어로 호출할 수도 있는 특수한 종류의 실행 가능한 규칙입니다. rule 호출에서 각 executable 또는 test 인수를 True로 설정하면 실행 및 테스트 규칙이 생성됩니다.

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

테스트 규칙에는 _test로 끝나는 이름을 사용해야 합니다. 테스트 target 이름도 규칙에 따라 _test로 끝나는 경우가 많지만 필수는 아닙니다. 테스트 외 규칙에는 이 접미사가 없어야 합니다.

두 가지 유형의 규칙 모두 run 또는 test 명령어에 의해 호출되는 실행 가능한 출력 파일 (사전 선언되었을 수도 있고 그렇지 않을 수도 있음)을 생성해야 합니다. 이 실행 파일로 사용할 규칙의 출력에 Bazel을 알리려면 반환된 DefaultInfo 제공자의 executable 인수로 전달합니다. 이 executable는 규칙의 기본 출력에 추가됩니다. 따라서 executablefiles에 모두 전달할 필요가 없습니다. 또한 runfiles에 암시적으로 추가됩니다.

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

이 파일을 생성하는 작업은 파일에 실행 가능한 비트를 설정해야 합니다. ctx.actions.run 또는 ctx.actions.run_shell 작업의 경우 작업에서 호출하는 기본 도구에서 실행해야 합니다. ctx.actions.write 작업의 경우 is_executable=True를 전달합니다.

기존 동작으로 실행 가능한 규칙에는 특별한 ctx.outputs.executable 사전 선언 출력이 있습니다. DefaultInfo을 사용하여 지정하지 않으면 이 파일은 기본 실행 파일의 역할을 하며, 다른 경우에 사용하면 안 됩니다. 이 출력 메커니즘은 분석 시 실행 파일 이름 맞춤설정을 지원하지 않으므로 지원 중단되었습니다.

실행 규칙테스트 규칙의 예를 참조하세요.

실행 가능한 규칙테스트 규칙에는 모든 규칙에 추가된 속성 외에도 암시적으로 정의된 추가 속성이 있습니다. 암시적으로 추가된 속성의 기본값은 변경할 수 없지만 기본값을 변경하는 Starlark 매크로에서 비공개 규칙을 래핑하여 해결할 수 있습니다.

def example_test(size="small", **kwargs):
  _example_test(size=size, **kwargs)

_example_test = rule(
 ...
)

Runfiles 위치

실행 가능한 대상이 bazel run(또는 test)으로 실행되면 runfiles 디렉터리의 루트가 실행 파일과 인접합니다. 경로는 다음과 같습니다.

# Given executable_file and runfile_file:
runfiles_root = executable_file.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

runfiles 디렉터리 아래의 File 경로는 File.short_path에 해당합니다.

bazel에 의해 직접 실행되는 바이너리는 runfiles 디렉터리의 루트에 인접합니다. 하지만 runfile 에서 호출된 바이너리는 동일한 가정을 할 수 없습니다. 이 문제를 완화하려면 각 바이너리가 환경 또는 명령줄 인수/플래그를 사용하여 runfile 루트를 매개변수로 허용하는 방법을 제공해야 합니다. 이를 통해 바이너리는 호출하는 바이너리에 올바른 표준 runfile 루트를 전달할 수 있습니다. 이 속성이 설정되지 않은 경우 바이너리는 호출된 첫 번째 바이너리라고 추측하고 인접한 runfile 디렉터리를 찾을 수 있습니다.

고급 주제

출력 파일 요청

단일 대상에 여러 개의 출력 파일이 있을 수 있습니다. bazel build 명령어가 실행되면 명령어에 지정된 대상의 출력 중 일부가 요청된 것으로 간주됩니다. Bazel은 이렇게 요청된 파일과 이러한 파일이 직간접적으로 종속된 파일만 빌드합니다. 작업 그래프 측면에서 Bazel은 요청한 파일의 전이 종속 항목으로 도달할 수 있는 작업만 실행합니다.

기본 출력 외에도 명령줄에서 사전 선언된 출력을 명시적으로 요청할 수 있습니다. 규칙은 출력 속성을 통해 사전 선언된 출력을 지정할 수 있습니다. 이 경우 사용자는 규칙을 인스턴스화할 때 출력 라벨을 명시적으로 선택합니다. 출력 속성의 File 객체를 가져오려면 상응하는 ctx.outputs 속성을 사용합니다. 규칙은 또한 대상 이름에 기반하여 사전 선언된 출력을 암시적으로 정의할 수 있지만 이 기능은 지원 중단되었습니다.

기본 출력 외에도 함께 요청될 수 있는 출력 파일 모음인 출력 그룹이 있습니다. --output_groups을 사용하여 요청할 수 있습니다. 예를 들어 대상 //pkg:mytargetdebug_files 출력 그룹이 있는 규칙 유형인 경우 bazel build //pkg:mytarget --output_groups=debug_files를 실행하여 이러한 파일을 빌드할 수 있습니다. 사전 선언되지 않은 출력에는 라벨이 없으므로 기본 출력 또는 출력 그룹에 나타나는 방식으로만 요청할 수 있습니다.

OutputGroupInfo 제공업체를 통해 출력 그룹을 지정할 수 있습니다. 여러 기본 제공자와 달리 OutputGroupInfo은 임의의 이름이 있는 매개변수를 사용하여 그 이름으로 출력 그룹을 정의할 수 있습니다.

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

또한, 대부분의 제공업체와 달리 OutputGroupInfo은 같은 출력 그룹을 정의하지 않는 한 측면과 이 측면이 적용되는 규칙 대상에서 모두 반환될 수 있습니다. 이 경우 결과 제공자가 병합됩니다.

일반적으로 OutputGroupInfo는 특정 유형의 파일을 타겟에서 소비자의 작업에 전달하는 데 사용해서는 안 됩니다. 대신 규칙별 제공업체를 정의하세요.

구성

다른 아키텍처에 C++ 바이너리를 빌드한다고 가정해 보겠습니다. 빌드는 복잡할 수 있으며 여러 단계가 관련될 수 있습니다. 컴파일러 및 코드 생성기와 같은 일부 중간 바이너리는 실행 플랫폼(호스트 또는 원격 실행자일 수 있음)에서 실행되어야 합니다. 최종 출력과 같은 일부 바이너리는 타겟 아키텍처에 맞게 빌드되어야 합니다.

따라서 Bazel은 '구성' 및 전환이라는 개념을 가지고 있습니다. 최상위 대상 (명령줄에서 요청된 대상)은 '타겟' 구성에서 빌드되는 반면, 실행 플랫폼에서 실행되어야 하는 도구는 '실행' 구성으로 빌드됩니다. 규칙은 구성에 따라 다른 작업을 생성할 수 있습니다. 예를 들어 컴파일러에 전달되는 CPU 아키텍처는 변경될 수 있습니다. 경우에 따라 다른 구성에 동일한 라이브러리가 필요할 수 있습니다. 이 경우 데이터가 여러 번 분석되고 빌드됩니다.

기본적으로 Bazel은 대상의 종속 항목을 대상 자체와 동일한 구성, 즉 전환을 사용하지 않는 상태로 빌드합니다. 종속 항목이 타겟 빌드를 지원하는 데 필요한 도구라면 상응하는 속성에서 실행 구성으로의 전환을 지정해야 합니다. 그러면 도구와 모든 종속 항목이 실행 플랫폼에 맞게 빌드됩니다.

각 종속 항목 속성에 대해 cfg을 사용하여 종속 항목이 동일한 구성으로 빌드되어야 하는지 또는 실행 구성으로 전환되어야 하는지를 결정할 수 있습니다. 종속 항목 속성에 executable=True 플래그가 있으면 cfg을 명시적으로 설정해야 합니다. 이는 실수로 잘못된 구성을 위한 도구를 빌드하는 것을 방지하기 위한 것입니다. 예 보기

일반적으로 런타임에 필요한 소스, 종속 라이브러리, 실행 파일은 동일한 구성을 사용할 수 있습니다.

빌드의 일부로 실행되는 도구 (예: 컴파일러 또는 코드 생성기)는 실행 구성을 위해 빌드해야 합니다. 이 경우 속성에 cfg="exec"을 지정합니다.

그러지 않으면 런타임 시 (예: 테스트의 일부로) 사용되는 실행 파일이 타겟 구성에 맞게 빌드되어야 합니다. 이 경우 속성에 cfg="target"을 지정합니다.

cfg="target"는 실제로 아무것도 하지 않습니다. 이는 규칙 디자이너가 의도에 대해 명시적으로 밝히는 데 도움이 되는 순수한 가치일 뿐입니다. cfg(선택사항)을 의미하는 executable=False은 가독성에 실제로 도움이 되는 경우에만 설정합니다.

또한 cfg=my_transition를 사용하여 사용자 정의 전환을 사용할 수 있습니다. 이렇게 하면 규칙 작성자가 구성을 유연하게 변경할 수 있으므로 빌드 그래프를 더 크고 가독성이 떨어질 수 있습니다.

참고: 지금까지 Bazel에는 실행 플랫폼이라는 개념이 없었으며 대신 모든 빌드 작업이 호스트 머신에서 실행되는 것으로 간주되었습니다. 따라서 단일 구성과 호스트 구성에서 종속 항목을 빌드하는 데 사용할 수 있는 '호스트' 전환이 있습니다. 많은 규칙이 여전히 도구에 '호스트' 전환을 사용하지만 이 작업은 현재 지원 중단되었으며 가능하면 '실행' 전환을 사용하도록 이전 중입니다.

'호스트' 구성과 '실행' 구성 사이에는 많은 차이점이 있습니다.

  • '호스트'는 터미널이며, '실행'은 종속 항목이 '구성' 상태가 되면 더 이상 전환이 허용되지 않습니다. 실행 중인 구성을 계속 사용하면 추가로 전환할 수 있습니다.
  • '호스트'는 모놀리식이고 '실행'은 '호스트' 구성만 있지만 실행 플랫폼마다 다른 '구성'은 있을 수 있습니다.
  • Bazel과 동일한 머신 또는 상당히 유사한 머신에서 도구를 실행한다고 가정합니다. 이는 더 이상 사실이 아닙니다. 로컬 머신 또는 원격 실행자에서 빌드 작업을 실행할 수 있으며 원격 실행자가 로컬 머신과 동일한 CPU 및 OS라고 보장할 수 없습니다.

'실행' 및 '호스트' 구성은 모두 동일한 옵션 변경사항을 적용합니다(예: --host_compilation_mode에서 --compilation_mode 설정, --host_cpu에서 --cpu 설정 등). 차이점은 '호스트' 구성이 다른 모든 플래그의 기본 값으로 시작한다는 반면, '실행' 구성은 타겟 구성에 따라 플래그의 현재 값으로 시작한다는 것입니다.

구성 프래그먼트

규칙은 cpp, java, jvm와 같은 구성 프래그먼트에 액세스할 수 있습니다. 그러나 액세스 오류를 방지하기 위해 모든 필수 프래그먼트를 선언해야 합니다.

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    host_fragments = ["java"], # Required fragments of the host configuration
    ...
)

ctx.fragments는 타겟 구성을 위한 구성 프래그먼트만 제공합니다. 호스트 구성의 프래그먼트에 액세스하려면 ctx.host_fragments를 대신 사용하세요.

일반적으로 runfile 트리에 있는 파일의 상대 경로는 소스 트리나 생성된 출력 트리에 있는 파일의 상대 경로와 같습니다. 어떤 이유로든 달라야 하는 경우 root_symlinks 또는 symlinks 인수를 지정할 수 있습니다. root_symlinks는 파일에 경로를 매핑하는 사전이며 이 경로는 runfile 디렉터리의 루트를 기준으로 합니다. symlinks 사전은 동일하지만 경로는 암시적으로 작업공간 이름으로 시작합니다.

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

symlinks 또는 root_symlinks을 사용하는 경우 두 개의 다른 파일을 runfiles 트리의 동일한 경로에 매핑하지 않도록 주의하세요. 그러면 충돌을 설명하는 오류와 함께 빌드가 실패합니다. 이 문제를 해결하려면 ctx.runfiles 인수를 수정하여 충돌을 삭제해야 합니다. 규칙을 사용하는 모든 대상과 이러한 대상에 종속되는 모든 유형의 대상에 대해 이 검사가 수행됩니다. 이는 도구가 다른 도구에서 전이적으로 사용될 가능성이 높은 경우 특히 위험합니다. 심볼릭 링크 이름은 도구의 실행 파일 및 모든 종속 항목에서 고유해야 합니다.

코드 적용 범위

coverage 명령어가 실행되면 빌드에서 특정 타겟의 커버리지 계측을 추가해야 할 수 있습니다. 이 빌드는 계측되는 소스 파일 목록도 수집합니다. 고려되는 대상의 하위 집합은 플래그 --instrumentation_filter로 제어됩니다. --instrument_test_targets을 지정하지 않으면 테스트 대상이 제외됩니다.

규칙 구현이 빌드 시간에 커버리지 계측을 추가하는 경우 구현 함수에서 커버리지 계측을 고려해야 합니다. 타겟의 소스가 계측되어야 하는 경우 ctx.coverage_Instrumented가 커버리지 모드에서 true를 반환합니다.

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

항상 커버리지 모드에서 켜져 있어야 하는 로직 (특히 소스 작동 여부와 관계없이)은 ctx.configuration.coverage_enabled에 조건 지정될 수 있습니다.

컴파일 전에 종속 항목의 소스가 직접 포함된 규칙의 경우 종속 항목을 계측해야 하는 경우 컴파일 시간 계측을 사용 설정해야 할 수도 있습니다.

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
    (ctx.coverage_instrumented() or
     any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
    # Do something to turn on coverage for this compile action

또한 규칙은 coverage_common.instrumented_files_info를 사용하여 구성된 InstrumentedFilesInfo 제공자를 사용하는 범위와 관련된 속성에 대한 정보도 제공해야 합니다. instrumented_files_infodependency_attributes 매개변수는 deps와 같은 코드 종속 항목과 data과 같은 데이터 종속 항목을 포함한 모든 런타임 종속 항목 속성을 나열해야 합니다. 적용 범위 계측이 추가될 수 있는 경우 source_attributes 매개변수에 규칙의 소스 파일 속성을 나열해야 합니다.

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

InstrumentedFilesInfo가 반환되지 않으면 dependency_attributes에서 cfg"host" 또는 "exec"로 설정하지 않는 각 도구 이외의 종속 항목 속성과 함께 기본 종속 항목이 생성됩니다. (이는 srcs와 같은 속성을 source_attributes 대신 dependency_attributes에 넣기 때문에 이상적인 동작이 아니지만 종속 항목 체인의 모든 규칙에 대한 커버리지 구성은 필요하지 않습니다.)

유효성 검사 작업

빌드에 관한 정보를 검증해야 하는 경우도 있으며, 검증에 필요한 정보는 아티팩트(소스 파일 또는 생성된 파일)에서만 확인할 수 있습니다. 이 정보는 아티팩트에 있으므로 규칙에서 파일을 읽을 수 없으므로 분석 시 규칙에서 이 유효성 검사를 할 수 없습니다. 대신 실행 시 작업에서 이 검증을 수행해야 합니다. 검증이 실패하면 작업이 실패하고 따라서 빌드도 실패합니다.

실행될 수 있는 유효성 검사의 예로는 정적 분석, 린트 작업, 종속 항목 및 일관성 검사, 스타일 검사가 있습니다.

또한 검증 작업은 아티팩트를 별도의 작업으로 빌드하는 데 필요하지 않은 작업 일부를 이동하여 빌드 성능을 개선하는 데 도움이 될 수도 있습니다. 예를 들어, 컴파일과 린트 작업을 수행하는 단일 작업을 컴파일 작업과 린트 작업으로 구분할 수 있다면 린트 작업을 확인 작업으로 실행하고 다른 작업과 동시에 실행할 수 있습니다.

이러한 유효성 검사 작업은 빌드의 다른 곳에서 사용되는 항목을 생성하지 않는 경우가 많습니다. 입력에 관한 어설션만 있으면 되기 때문입니다. 그러나 문제가 발생합니다. 유효성 검사 작업으로 빌드의 다른 곳에서 사용되는 항목이 생성되지 않는 경우, 규칙에서 작업을 어떻게 실행하나요? 지금까지 접근 방식은 유효성 검사 작업이 빈 파일을 출력하고 해당 출력을 빌드의 다른 중요한 작업 입력에 인위적으로 추가하는 것이었습니다.

이렇게 하는 이유는 컴파일 작업이 실행될 때 Bazel이 항상 유효성 검사 작업을 실행하기 때문이지만, 이 경우 심각한 단점이 있습니다.

  1. 유효성 검사 작업은 빌드의 중요한 경로에 있습니다. Bazel은 컴파일 작업을 실행하려면 빈 출력이 필요하다고 판단하므로 컴파일 작업이 입력을 무시하더라도 먼저 유효성 검사 작업을 실행합니다. 따라서 동시 로드가 감소하고 빌드 속도가 느려집니다.

  2. 컴파일 작업 대신 빌드의 다른 작업이 실행될 수 있는 경우, 이러한 작업의 유효성 검사 작업의 빈 출력(예: java_library jar 출력)도 추가해야 합니다. 컴파일 작업 대신 실행될 수 있는 새 작업이 나중에 추가될 때 문제가 생기고 빈 유효성 검사 출력이 실수로 누락되는 문제도 있습니다.

이러한 문제의 해결 방법은 유효성 검사 출력 그룹을 사용하는 것입니다.

검증 출력 그룹

유효성 검사 출력 그룹은 검증 작업의 다른 출력을 보유하도록 설계된 출력 그룹으로, 다른 작업의 입력에 인위적으로 추가할 필요가 없습니다.

이 그룹은 --output_groups 플래그 값에 관계없이, 그리고 대상이 종속되는 방법 (예: 명령줄, 종속 항목으로, 대상의 암시적 출력을 통해)에 관계없이 출력이 항상 요청된다는 점에서 특별합니다. 정상적인 캐싱 및 성과 증분은 계속 적용됩니다. 유효성 검사 작업에 대한 입력이 변경되지 않고 유효성 검사 작업이 이전에 성공한 경우 유효성 검사 작업은 실행되지 않습니다.

이 출력 그룹을 사용하려면 검증 작업에서는 빈 파일을 포함하여 일부 파일을 출력해야 합니다. 이렇게 하려면 일반적으로는 출력을 생성하지 않는 일부 도구를 래핑하여 파일이 생성되도록 해야 할 수 있습니다.

타겟의 유효성 검사 작업은 세 가지 경우에 실행되지 않습니다.

  • 타겟이 도구로 사용되는 경우
  • 대상이 암시적 종속 항목으로 종속되는 경우 (예: "_"로 시작하는 속성)
  • 대상이 호스트 또는 실행 구성에서 빌드되는 경우

이러한 대상에는 검증 실패를 발견할 수 있는 별도의 빌드 및 테스트가 있다고 가정합니다.

유효성 검사 출력 그룹 사용

유효성 검사 출력 그룹의 이름은 _validation이며 다른 출력 그룹과 마찬가지로 사용됩니다.

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")

  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
      outputs = [validation_output],
      executable = ctx.executable._validation_tool,
      arguments = [validation_output.path])

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"),
  }
)

유효성 검사 출력 파일은 DefaultInfo 또는 다른 작업에 대한 입력에 추가되지 않습니다. 타겟이 라벨로 종속되거나 대상의 암시적 출력이 직접 또는 간접적으로 종속되면 이 규칙 종류의 대상에 대한 유효성 검사 작업이 계속 실행됩니다.

일반적으로 유효성 검사 작업 출력은 검증 출력 그룹으로만 들어가고 다른 작업의 입력에 추가되지 않는 것이 중요합니다. 병렬 처리 성능이 저하될 수 있기 때문입니다. 그러나 Bazel에는 현재 이를 시행하는 특별한 검사가 없습니다. 따라서 Starlark 규칙 테스트의 작업 입력에 검증 작업 출력이 추가되지 않는지 테스트해야 합니다. 예를 들면 다음과 같습니다.

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

유효성 검사 작업 플래그

유효성 검사 작업 실행은 --run_validations 명령줄 플래그로 제어되며 기본값은 true입니다.

지원이 중단된 기능

사전 선언된 출력 지원 중단됨

사전 선언된 출력을 사용하는 두 가지 방법은 다음과 같습니다.

  • ruleoutputs 매개변수는 사전 선언된 출력 라벨을 생성하기 위한 출력 속성 이름과 문자열 템플릿 간의 매핑을 지정합니다. 사전 선언되지 않은 출력을 사용하고 DefaultInfo.files에 명시적으로 출력을 추가하는 것이 좋습니다. 사전 선언된 출력 라벨 대신 출력을 사용하는 규칙을 입력할 때 규칙 타겟 라벨을 입력으로 사용하세요.

  • 실행 가능한 규칙의 경우 ctx.outputs.executable은 규칙 대상과 이름이 동일한 사전 선언된 실행 가능한 출력을 나타냅니다. 예를 들어 ctx.actions.declare_file(ctx.label.name)를 사용하여 출력을 명시적으로 선언하는 것이 좋습니다. 실행 파일을 생성하는 명령어가 실행을 허용하도록 권한을 설정해야 합니다. 실행 가능한 출력을 DefaultInfoexecutable 매개변수에 명시적으로 전달합니다.

피해야 할 Runfile 기능

ctx.runfilesrunfiles 유형에는 복잡한 특성 세트가 있으며 대부분은 기존의 이유로 유지됩니다. 복잡성을 줄이는 데 도움이 되는 권장사항은 다음과 같습니다.

  • ctx.runfilescollect_datacollect_default 모드를 사용하지 마세요. 이러한 모드는 특정 하드코딩된 종속 항목 가장자리 전반에 걸쳐 암시적으로 혼란스러운 방식으로 런파일을 수집합니다. 대신 ctx.runfilesfiles 또는 transitive_files 매개변수를 사용하거나 종속 항목의 runfile을 runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)과 병합하여 파일을 추가합니다.

  • DefaultInfo 생성자의 data_runfilesdefault_runfiles를 사용하지 마세요. 대신 DefaultInfo(runfiles = ...)을 지정하세요. '기본'과 '데이터' 실행 파일은 기존 이유로 유지됩니다. 예를 들어 일부 규칙은 기본 출력을 data_runfiles에 넣지만 default_runfiles에는 넣지 않습니다. 규칙은 data_runfiles를 사용하는 대신 기본 출력을 모두 포함하고 실행 파일을 제공하는 속성 (종종 data)의 default_runfiles에서 병합해야 합니다.

  • DefaultInfo에서 runfiles를 가져올 때(일반적으로 현재 규칙과 종속 항목 간에 런파일 병합 시에만) DefaultInfo.data_runfiles아닌 DefaultInfo.default_runfiles를 사용합니다.

기존 제공업체에서 마이그레이션

기존에는 Bazel 제공업체가 Target 객체의 간단한 필드였습니다. 점 연산자를 사용하여 액세스했으며 규칙의 구현 함수에서 반환한 구조체에 필드를 넣어 만들어졌습니다.

이 스타일은 지원 중단되었으며 새 코드에서 사용해서는 안 됩니다. 이전에 도움이 될 수 있는 정보는 아래를 참고하세요. 새 제공자 메커니즘은 이름 충돌을 방지합니다. 또한 제공업체 인스턴스에 액세스하는 코드가 제공업체 기호를 사용하여 검색하도록 하는 방법으로 데이터 숨김을 지원합니다.

현재는 기존 제공업체가 계속 지원됩니다. 규칙은 다음과 같이 레거시 제공업체와 최신 제공업체를 모두 반환할 수 있습니다.

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x="foo", ...)
  modern_data = MyInfo(y="bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

이 규칙 인스턴스의 dep Target 객체인 경우 제공자와 그 콘텐츠를 dep.legacy_info.xdep[MyInfo].y로 가져올 수 있습니다.

반환된 구조는 providers 외에도 특별한 의미를 갖는 다른 여러 필드를 가질 수 있으므로 상응하는 레거시 제공자를 만들지 않습니다.

  • files, runfiles, data_runfiles, default_runfiles, executable 필드는 DefaultInfo의 동일한 이름의 필드에 해당합니다. DefaultInfo 제공업체를 반환하는 동시에 이러한 필드를 지정할 수 없습니다.

  • output_groups 필드는 구조체 값을 취하며 OutputGroupInfo에 해당합니다.

규칙 provides 선언과 종속 항목 속성의 providers 선언에서 기존 제공자는 문자열로 전달되고 최신 제공자는 *Info 기호로 전달됩니다. 이전할 때 문자열에서 기호로 변경해야 합니다. 모든 규칙을 원자적으로 업데이트하기가 어려운 복잡하거나 큰 규칙 세트의 경우 다음 단계를 순서대로 수행하면 더 쉽게 시간을 유지할 수 있습니다.

  1. 위의 구문을 사용하여 기존 제공업체 및 최신 제공업체를 모두 생성하도록 기존 제공업체를 생성하는 규칙을 수정합니다. 기존 제공업체를 반환한다고 선언한 규칙의 경우 기존 제공업체와 최신 제공업체를 모두 포함하도록 선언을 업데이트하세요.

  2. 기존 제공자를 사용하는 규칙을 수정하여 최신 제공자를 대신 사용합니다. 속성 선언에 기존 제공자가 필요한 경우 대신 최신 제공자가 필요하도록 업데이트합니다. 원하는 경우 공급자가 hasattr(target, 'foo')를 사용하여 기존 제공업체의 존재를 테스트하거나 FooInfo in target을 사용하여 새 제공업체를 테스트하도록 하여 1단계에서 이 작업을 인터리브할 수 있습니다.

  3. 모든 규칙에서 기존 제공업체를 완전히 삭제합니다.