빌드 스타일 가이드

문제 신고 소스 보기 나이틀리 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

DRY보다 DAMP 빌드 파일을 선호

DRY 원칙('불필요한 반복 금지')은 코드의 중복을 방지하기 위해 변수 및 함수와 같은 추상화를 도입하여 고유성을 장려합니다.

반면 DAMP 원칙('설명적이고 의미 있는 문구')은 파일의 이해와 유지관리를 용이하게 하기 위해 고유성보다 가독성을 권장합니다.

BUILD 파일은 코드가 아닌 구성입니다. 코드는 아니지만 사람과 도구로 유지관리해야 합니다. 따라서 DRY보다 DAMP가 더 적합합니다.

BUILD.bazel 파일 형식 지정

BUILD 파일 형식은 표준화된 도구가 대부분의 형식 문제를 처리하는 Go와 동일한 접근 방식을 따릅니다. Buildifier는 표준 스타일로 소스 코드를 파싱하고 내보내는 도구입니다. 따라서 모든 BUILD 파일은 동일한 자동 방식으로 형식이 지정되므로 코드 검토 중에 형식 지정이 문제가 되지 않습니다. 또한 도구에서 BUILD 파일을 더 쉽게 이해하고 수정하고 생성할 수 있습니다.

BUILD 파일 형식은 buildifier의 출력과 일치해야 합니다.

형식 지정 예

# Test code implementing the Foo controller.
package(default_testonly = True)

py_test(
    name = "foo_test",
    srcs = glob(["*.py"]),
    data = [
        "//data/production/foo:startfoo",
        "//foo",
        "//third_party/java/jdk:jdk-k8",
    ],
    flaky = True,
    deps = [
        ":check_bar_lib",
        ":foo_data_check",
        ":pick_foo_port",
        "//pyglib",
        "//testing/pybase",
    ],
)

파일 구조

권장사항: 다음 순서를 사용하세요 (모든 요소는 선택사항임).

  • 패키지 설명 (댓글)

  • 모든 load()

  • package() 함수

  • 규칙 및 매크로 호출

Buildifier는 독립형 주석과 요소에 연결된 주석을 구분합니다. 주석이 특정 요소에 연결되지 않은 경우 주석 뒤에 빈 줄을 사용합니다. 이 구분은 자동 변경을 실행할 때 중요합니다 (예: 규칙을 삭제할 때 댓글을 유지하거나 삭제).

# Standalone comment (such as to make a section in a file)

# Comment for the cc_library below
cc_library(name = "cc")

현재 패키지의 타겟 참조

파일은 패키지 디렉터리에 대한 상대 경로로 참조해야 합니다(..와 같은 상위 참조는 사용하지 않음). 생성된 파일에는 소스가 아님을 나타내는 ':'이 접두사로 붙어야 합니다. 소스 파일에는 :이 프리픽스로 붙으면 안 됩니다. 규칙에는 : 프리픽스가 추가되어야 합니다. 예를 들어 x.cc가 소스 파일이라고 가정하면 다음과 같습니다.

cc_library(
    name = "lib",
    srcs = ["x.cc"],
    hdrs = [":gen_header"],
)

genrule(
    name = "gen_header",
    srcs = [],
    outs = ["x.h"],
    cmd = "echo 'int x();' > $@",
)

타겟 이름 지정

타겟 이름은 설명적이어야 합니다. 타겟에 소스 파일이 하나 포함된 경우 타겟 이름은 일반적으로 해당 소스에서 파생되어야 합니다 (예: chat.cccc_librarychat로, DirectMessage.javajava_librarydirect_message로 지정할 수 있음).

패키지의 동명 타겟 (포함 디렉터리와 이름이 같은 타겟)은 디렉터리 이름으로 설명된 기능을 제공해야 합니다. 이러한 타겟이 없으면 동명의 타겟을 만들지 마세요.

이름이 같은 타겟을 참조할 때는 짧은 이름을 사용하는 것이 좋습니다 (//x:x 대신 //x). 동일한 패키지에 있는 경우 로컬 참조 (//x 대신 :x)를 사용하는 것이 좋습니다.

특별한 의미가 있는 '예약된' 타겟 이름은 사용하지 마세요. 여기에는 all, __pkg__, __subpackages__가 포함되며 이러한 이름은 특별한 의미를 가지므로 사용 시 혼동과 예기치 않은 동작을 유발할 수 있습니다.

지배적인 팀 규칙이 없는 경우 Google에서 널리 사용되는 구속력 없는 권장사항은 다음과 같습니다.

  • 일반적으로 'snake_case'를 사용합니다.
    • src이 하나인 java_library의 경우 확장자가 없는 파일 이름과 동일하지 않은 이름을 사용해야 합니다.
    • Java *_binary*_test 규칙의 경우 'Upper CamelCase'를 사용합니다. 이렇게 하면 타겟 이름이 src 중 하나와 일치할 수 있습니다. java_test의 경우 대상 이름에서 test_class 속성을 추론할 수 있습니다.
  • 특정 타겟의 변형이 여러 개 있는 경우 명확하게 구분하기 위해 접미사를 추가합니다 (예: :foo_dev, :foo_prod 또는 :bar_x86, :bar_x64)
  • _test, _unittest, Test 또는 Tests_test 타겟에 접미사 추가
  • _lib 또는 _library과 같은 의미 없는 접미사는 사용하지 마세요 (_library 타겟과 해당 _binary 간의 충돌을 방지하는 데 필요한 경우는 제외).
  • 프로토 관련 타겟의 경우:
    • proto_library 타겟의 이름은 _proto로 끝나야 합니다.
    • 언어별 *_proto_library 규칙은 기본 프로토와 일치해야 하지만 _proto을 다음과 같은 언어별 접미사로 대체해야 합니다.
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

공개 상태

공개 상태는 테스트와 역 종속 항목의 액세스를 허용하면서 가능한 한 좁은 범위로 지정해야 합니다. 적절한 경우 __pkg____subpackages__를 사용합니다.

패키지 default_visibility//visibility:public로 설정하지 마세요. //visibility:public는 프로젝트의 공개 API에 있는 타겟에만 개별적으로 설정해야 합니다. 이는 외부 프로젝트에서 종속되도록 설계된 라이브러리이거나 외부 프로젝트의 빌드 프로세스에서 사용할 수 있는 바이너리일 수 있습니다.

종속 항목

종속 항목은 직접 종속 항목 (규칙에 나열된 소스에 필요한 종속 항목)으로 제한해야 합니다. 전이 종속 항목을 나열하지 마세요.

패키지 로컬 종속 항목은 먼저 나열되어야 하며 위의 현재 패키지의 타겟 참조 섹션과 호환되는 방식으로 참조되어야 합니다 (절대 패키지 이름이 아님).

종속 항목을 단일 목록으로 직접 나열하는 것이 좋습니다. 여러 타겟의 '공통' 종속 항목을 변수에 넣으면 유지관리성이 떨어지고 도구에서 타겟의 종속 항목을 변경할 수 없으며 사용하지 않는 종속 항목이 발생할 수 있습니다.

Glob

[]로 '타겟 없음'을 나타냅니다. 아무것도 일치하지 않는 glob을 사용하지 마세요. 빈 목록보다 오류가 발생하기 쉽고 명확하지 않습니다.

재귀적

소스 파일을 일치시키는 데 재귀적 glob을 사용하지 마세요 (예: glob(["**/*.java"])).

재귀적 glob은 BUILD 파일을 포함하는 하위 디렉터리를 건너뛰므로 BUILD 파일에 대해 추론하기 어렵습니다.

일반적으로 재귀 glob은 디렉터리당 BUILD 파일이 있고 파일 간에 종속성 그래프가 정의되어 있는 것보다 효율성이 떨어집니다. 이렇게 하면 원격 캐싱과 병렬 처리가 더 잘 이루어지기 때문입니다.

각 디렉터리에 BUILD 파일을 작성하고 파일 간 종속성 그래프를 정의하는 것이 좋습니다.

비재귀적

비재귀적 glob은 일반적으로 허용됩니다.

목록 내포 피하기

BUILD.bazel 파일의 최상위 수준에서 목록 내포를 사용하지 마세요. 별도의 최상위 규칙 또는 매크로 호출을 사용하여 명명된 각 타겟을 만들어 반복적인 호출을 자동화합니다. 명확성을 위해 각 항목에 짧은 name 매개변수를 지정합니다.

목록 이해는 다음을 줄여줍니다.

  • 유지 관리성. 인간 유지관리자와 대규모 자동 변경사항이 목록 내포를 올바르게 업데이트하기는 어렵거나 불가능합니다.
  • 검색 가능성 패턴에 name 매개변수가 없으므로 이름으로 규칙을 찾기 어렵습니다.

목록 내포 패턴의 일반적인 적용 사례는 테스트를 생성하는 것입니다. 예를 들면 다음과 같습니다.

[[java_test(
    name = "test_%s_%s" % (backend, count),
    srcs = [ ... ],
    deps = [ ... ],
    ...
) for backend in [
    "fake",
    "mock",
]] for count in [
    1,
    10,
]]

더 간단한 대안을 사용하는 것이 좋습니다. 예를 들어 테스트 하나를 생성하는 매크로를 정의하고 각 최상위 name에 대해 호출합니다.

my_java_test(name = "test_fake_1",
    ...)
my_java_test(name = "test_fake_10",
    ...)
...

deps 변수 사용 안 함

목록 변수를 사용하여 공통 종속 항목을 캡슐화하지 마세요.

COMMON_DEPS = [
  "//d:e",
  "//x/y:z",
]

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = COMMON_DEPS + [ ... ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = COMMON_DEPS + [ ... ],
)

마찬가지로 exports가 있는 라이브러리 타겟을 사용하여 종속 항목을 그룹화하지 마세요.

대신 각 타겟의 종속 항목을 별도로 나열하세요.

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

Gazelle 및 기타 도구에서 유지관리하도록 합니다. 반복이 발생하지만 종속 항목을 관리하는 방법을 생각할 필요가 없습니다.

리터럴 문자열 선호

Starlark는 연결 (+) 및 서식 지정 (%)을 위한 문자열 연산자를 제공하지만 주의해서 사용하세요. 표현식을 더 간결하게 만들거나 긴 줄을 끊기 위해 공통 문자열 부분을 팩터링하고 싶을 수 있습니다. 그러나

  • 분할된 문자열 값을 한눈에 읽기가 더 어렵습니다.

  • buildozer 및 코드 검색과 같은 자동화된 도구는 값이 분리된 경우 값을 찾고 올바르게 업데이트하는 데 문제가 있습니다.

  • BUILD 파일에서는 반복을 피하는 것보다 가독성이 더 중요합니다(DAMP와 DRY 비교 참고).

  • 이 스타일 가이드에서는 라벨 값 문자열을 분할하지 않도록 경고하고 긴 줄을 명시적으로 허용합니다.

  • Buildifier는 연결된 문자열이 라벨임을 감지하면 자동으로 연결된 문자열을 병합합니다.

따라서 연결되거나 형식이 지정된 문자열보다는 명시적인 리터럴 문자열을 사용하는 것이 좋습니다. 특히 namedeps과 같은 라벨 유형 속성에서는 더욱 그렇습니다. 예를 들어 다음 BUILD 프래그먼트가 있습니다.

NAME = "foo"
PACKAGE = "//a/b"

proto_library(
  name = "%s_proto" % NAME,
  deps = [PACKAGE + ":other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:" +
            "extravagantly_long_target_name",
)

다음과 같이 다시 작성하는 것이 좋습니다.

proto_library(
  name = "foo_proto",
  deps = ["//a/b:other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)

.bzl 파일에서 내보내는 기호 제한

각 공개 .bzl (Starlark) 파일에서 내보내는 기호 (규칙, 매크로, 상수, 함수) 수를 최소화합니다. 파일은 함께 사용되는 것이 확실한 경우에만 여러 기호를 내보내는 것이 좋습니다. 그렇지 않으면 자체 bzl_library가 있는 여러 .bzl 파일로 분할합니다.

과도한 심볼로 인해 .bzl 파일이 광범위한 심볼 '라이브러리'로 커질 수 있으며, 이로 인해 단일 파일의 변경사항으로 인해 Bazel이 많은 타겟을 다시 빌드해야 합니다.

기타 규칙

  • 대문자와 밑줄을 사용하여 상수 (예: GLOBAL_CONSTANT)를 선언하고 소문자와 밑줄을 사용하여 변수 (예: my_variable)를 선언합니다.

  • 라벨은 79자(영문 기준)를 초과하더라도 분할해서는 안 됩니다. 라벨은 가능하면 문자열 리터럴이어야 합니다. 이유: 찾기 및 바꾸기를 쉽게 할 수 있습니다. 또한 가독성도 향상됩니다.

  • 이름 속성의 값은 리터럴 상수 문자열이어야 합니다 (매크로 제외). 근거: 외부 도구는 이름 속성을 사용하여 규칙을 참조합니다. 코드를 해석하지 않고 규칙을 찾아야 합니다.

  • 불리언 유형 속성을 설정할 때는 정수 값이 아닌 불리언 값을 사용하세요. 기존 이유로 인해 규칙은 필요에 따라 정수를 불리언으로 변환하지만 이는 권장되지 않습니다. 이유: flaky = 1를 '한 번 다시 실행하여 이 타겟의 플래키를 제거하세요'라고 잘못 읽을 수 있습니다. flaky = True는 '이 테스트는 불안정합니다'라고 명확하게 말합니다.

Python 스타일 가이드와의 차이점

Python 스타일 가이드와의 호환성이 목표이지만 몇 가지 차이점이 있습니다.

  • 엄격한 행 길이 제한이 없습니다. 긴 주석과 긴 문자열은 79열로 분할되는 경우가 많지만 필수는 아닙니다. 코드 검토나 제출 전 스크립트에서 강제 적용해서는 안 됩니다. 근거: 라벨이 길어 이 한도를 초과할 수 있습니다. BUILD 파일은 도구에 의해 생성되거나 수정되는 경우가 많으며 이는 줄 길이 제한과 잘 맞지 않습니다.

  • 암시적 문자열 연결은 지원되지 않습니다. + 연산자를 사용합니다. 이유: BUILD 파일에는 많은 문자열 목록이 포함되어 있습니다. 쉼표를 잊기 쉬우며, 이로 인해 완전히 다른 결과가 나올 수 있습니다. 이로 인해 과거에 많은 버그가 발생했습니다. 이 토론도 참고하세요.

  • 규칙에서 키워드 인수의 = 기호 주위에 공백을 사용합니다. 이유: 이름이 지정된 인수는 Python보다 훨씬 더 자주 사용되며 항상 별도의 줄에 있습니다. 공백은 가독성을 향상합니다. 이 규칙은 오랫동안 사용되어 왔으며 기존 BUILD 파일을 모두 수정할 가치는 없습니다.

  • 기본적으로 문자열에는 큰따옴표를 사용합니다. 이유: Python 스타일 가이드에 지정되어 있지는 않지만 일관성을 유지하는 것이 좋습니다. 따라서 큰따옴표로 묶인 문자열만 사용하기로 했습니다. 많은 언어에서 문자열 리터럴에 큰따옴표를 사용합니다.

  • 두 최상위 수준 정의 사이에는 빈 줄 하나를 사용합니다. 이유: BUILD 파일의 구조는 일반적인 Python 파일과 다릅니다. 최상위 문만 있습니다. 빈 줄을 하나만 사용하면 BUILD 파일이 짧아집니다.