자주 묻는 질문(FAQ)

이 페이지에서는 Bazel의 외부 종속 항목에 관해 자주 묻는 질문에 답변합니다.

MODULE.bazel

Bazel 모듈의 버전을 지정하려면 어떻게 해야 하나요?

소스 보관 파일 MODULE.bazel에서 module 지시어를 사용하여 version을 설정하면 신중하게 관리하지 않는 경우 여러 가지 단점과 의도하지 않은 부작용이 발생할 수 있습니다.

  • 중복: 일반적으로 모듈의 새 버전을 출시하려면 MODULE.bazel에서 버전을 증분하고 출시 버전을 태그 지정하는 두 가지 별도의 단계를 거쳐야 하며, 이 두 단계가 동기화되지 않을 수 있습니다. 자동화를 통해 이 위험을 줄일 수 있지만, 완전히 피하는 것이 더 간단하고 안전합니다.

  • 일관성 없음: 레지스트리 외 재정의를 사용하여 특정 커밋으로 모듈을 재정의하는 사용자에게는 잘못된 버전이 표시됩니다. 예를 들어 소스 보관 파일의 MODULE.bazel에서 version = "0.3.0"을 설정했지만 출시 이후 추가 커밋이 이루어진 경우 이러한 커밋 중 하나로 재정의하는 사용자에게는 여전히 0.3.0이 표시됩니다. 실제로 버전은 출시 버전보다 앞서 있음을 반영해야 합니다(예: 0.3.1-rc1).

  • 레지스트리 외 재정의 문제: 자리표시자 값을 사용하면 사용자가 레지스트리 외 재정의로 모듈을 재정의할 때 문제가 발생할 수 있습니다. 예를 들어 0.0.0은 일반적으로 사용자가 레지스트리 외 재정의를 실행할 때 원하는 예상 동작인 가장 높은 버전으로 정렬되지 않습니다.

따라서 소스 보관 파일 MODULE.bazel에서 버전을 설정하지 않는 것이 가장 좋습니다. 대신 레지스트리(예: Bazel 중앙 레지스트리)에 저장된 MODULE.bazel에서 버전을 설정합니다. 이는 Bazel의 외부 종속 항목 확인 중에 모듈 버전의 실제 소스입니다 (Bazel 레지스트리 참고).

일반적으로 이 작업은 자동화됩니다. 예를 들어 rules-template 예시 규칙 저장소는 bazel-contrib/publish-to-bcr publish.yaml GitHub 작업을 사용하여 출시 버전을 BCR에 게시합니다. 이 작업은 출시 버전이 포함된 소스 보관 파일 MODULE.bazel의 패치를 생성합니다. 이 패치는 레지스트리에 저장되며 Bazel의 외부 종속 항목 확인 중에 모듈이 가져올 때 적용됩니다.

이렇게 하면 레지스트리의 출시 버전에서 버전이 출시 버전으로 올바르게 설정되므로 bazel_dep, single_version_overridemultiple_version_override가 예상대로 작동하며, 소스 보관 파일의 버전이 항상 올바르게 처리되는 기본값('')이므로 레지스트리 외 재정의를 실행할 때 발생할 수 있는 문제를 방지할 수 있습니다. 기본 버전 값이며 정렬할 때 예상대로 작동합니다(빈 문자열은 가장 높은 버전으로 처리됨).

호환성 수준이란 무엇인가요?

compatibility_level 사용을 중단해야 합니다.

compatibility_level을 늘리면 최종 사용자가 해결하기 어려운 버전 충돌이 발생합니다. 따라서 Bazel 8.6.0 및 9.1.0부터 compatibility_levelmax_compatibility_level은 모두 no-op입니다.

주요 호환성이 손상되는 변경사항을 도입하는 모듈 관리자는 빌드 실패 시 명확한 오류 메시지와 실행 가능한 마이그레이션 경로를 제공해야 합니다.

기존 문서:

Bazel 모듈의 compatibility_level은 이전 버전과 호환되지 않는('호환성이 손상되는') 변경사항을 도입하는 동일한 커밋에서 증분되어야 합니다.

하지만 Bazel은 확인된 종속 항목 그래프에 호환성 수준이 다른 동일한 모듈 버전이 있는 것으로 감지되면 오류를 발생시킬 수 있습니다. 예를 들어 두 모듈이 호환성 수준이 다른 세 번째 모듈의 버전에 종속되는 경우에 발생할 수 있습니다.

따라서 compatibility_level을 너무 자주 증분하면 매우 방해가 될 수 있으므로 권장하지 않습니다. 이 상황을 방지하려면 compatibility_level은 호환성이 손상되는 변경사항이 대부분의 사용 사례에 영향을 미치고 마이그레이션 또는 해결 방법이 쉽지 않은 경우에만 증분해야 합니다.

MODULE.bazel이 load를 지원하지 않는 이유는 무엇인가요?

종속 항목 확인 중에 참조된 모든 외부 종속 항목의 MODULE.bazel 파일이 레지스트리에서 가져옵니다. 이 단계에서는 종속 항목의 소스 보관 파일이 아직 가져오지 않습니다. 따라서 MODULE.bazel 파일이 다른 파일을 load하는 경우 Bazel은 전체 소스 보관 파일을 가져오지 않고는 해당 파일을 실제로 가져올 수 없습니다. MODULE.bazel 파일 자체는 레지스트리에 직접 호스팅되므로 특별합니다.

MODULE.bazel에서 load를 요청하는 사용자가 일반적으로 관심을 갖는 몇 가지 사용 사례가 있으며, load 없이도 해결할 수 있습니다.

  • MODULE.bazel에 나열된 버전이 다른 곳에 저장된 빌드 메타데이터(예: .bzl 파일)와 일치하는지 확인: BUILD 파일에서 로드된 .bzl 파일에서 native.module_version 메서드를 사용하여 이 작업을 실행할 수 있습니다.
  • 특히 모노리포의 경우 매우 큰 MODULE.bazel 파일을 관리 가능한 섹션으로 분할: 루트 모듈은 include 지시어를 사용하여 MODULE.bazel 파일을 여러 세그먼트로 분할할 수 있습니다. MODULE.bazel 파일에서 load를 허용하지 않는 것과 같은 이유로 루트가 아닌 모듈에서는 include를 사용할 수 없습니다.
  • 이전 WORKSPACE 시스템의 사용자는 저장소를 선언한 다음 복잡한 로직을 실행하기 위해 해당 저장소에서 즉시 load하는 것을 기억할 수 있습니다. 이 기능은 모듈 확장 프로그램으로 대체되었습니다.

bazel_dep에 SemVer 범위를 지정할 수 있나요?

아니요. npmCargo 와 같은 일부 다른 패키지 관리자는 버전 범위를 지원하며 (암시적 또는 명시적) 이를 위해서는 제약 조건 해결 도구가 필요한 경우가 많고 (사용자가 출력을 예측하기 어려움) 잠금 파일이 없으면 버전 확인을 재현할 수 없습니다.

대신 Bazel은 최소 버전 선택을 Go와 같이 사용하며, 이는 출력을 쉽게 예측할 수 있고 재현성을 보장합니다. 이는 Bazel의 설계 목표와 일치하는 절충안입니다.

또한 Bazel 모듈 버전은 SemVer 의 상위 집합이므로 엄격한 SemVer 환경에서 의미가 있는 것이 항상 Bazel 모듈 버전으로 이어지는 것은 아닙니다.

bazel_dep의 최신 버전을 자동으로 가져올 수 있나요?

일부 사용자는 종종 bazel_dep(name = "foo", version = "latest")를 지정하여 종속 항목의 최신 버전을 자동으로 가져올 수 있는 기능을 요청합니다. 이는 SemVer 범위에 관한 질문과 유사하며 답변도 아니요입니다.

여기에서 권장되는 해결 방법은 자동화를 통해 이 작업을 처리하는 것입니다. 예를 들어 Renovate는 Bazel 모듈을 지원합니다.

이 질문을 하는 사용자는 로컬 개발 중에 빠르게 반복할 수 있는 방법을 찾고 있는 경우가 있습니다. 이 작업은 local_path_override를 사용하여 실행할 수 있습니다.

이러한 use_repo는 모두 무엇인가요?

MODULE.bazel 파일의 모듈 확장 프로그램 사용에는 큰 use_repo 지시어가 포함되는 경우가 있습니다. 예를 들어 go_deps 확장 프로그램의 일반적인 사용은 다음과 같습니다.gazelle

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
    go_deps,
    "com_github_gogo_protobuf",
    "com_github_golang_mock",
    "com_github_golang_protobuf",
    "org_golang_x_net",
    ...  # potentially dozens of lines...
)

정보가 이미 참조된 go.mod 파일에 있다고 주장할 수 있으므로 긴 use_repo 지시어가 중복되는 것처럼 보일 수 있습니다.

Bazel에 이 use_repo 지시어가 필요한 이유는 모듈 확장 프로그램을 지연 실행하기 때문입니다. 즉, 모듈 확장 프로그램은 결과가 관찰되는 경우에만 실행됩니다. 모듈 확장 프로그램의 '출력'은 저장소 정의이므로 정의하는 저장소가 요청되는 경우에만 모듈 확장 프로그램을 실행합니다 (예: 위의 예에서 @org_golang_x_net//:foo 대상이 빌드되는 경우). 하지만 모듈 확장 프로그램이 실행될 때까지 정의할 저장소를 알 수 없습니다. 여기에서 use_repo 지시어가 사용됩니다. 사용자는 확장 프로그램에서 생성할 것으로 예상되는 저장소를 Bazel에 알릴 수 있으며, Bazel은 이러한 특정 저장소가 사용될 때만 확장 프로그램을 실행합니다.

use_repo 지시어를 유지관리하는 데 도움이 되도록 모듈 확장 프로그램은 extension_metadata 객체를 구현 함수에서 반환할 수 있습니다. 사용자는 bazel mod tidy 명령어를 실행하여 이러한 모듈 확장 프로그램의 use_repo 지시어를 업데이트할 수 있습니다.

Bzlmod 마이그레이션

MODULE.bazel 또는 WORKSPACE 중 어느 것이 먼저 평가되나요?

--enable_bzlmod--enable_workspace가 모두 설정된 경우 어떤 시스템이 먼저 참조되는지 궁금할 수 있습니다. 간단히 말해 MODULE.bazel(Bzlmod)이 먼저 평가됩니다.

긴 답변은 "어느 것이 먼저 평가되는가"가 올바른 질문이 아니라는 것입니다. 오히려 올바른 질문은 정식 이름이 @@foo인 저장소의 컨텍스트에서 @bar가 무엇으로 확인되는가입니다. 또는 @@base의 저장소 매핑은 무엇인가요?

명백한 저장소 이름 (선행 @ 하나)이 있는 라벨은 확인되는 컨텍스트에 따라 다른 것을 참조할 수 있습니다. @bar//:baz 라벨을 보고 실제로 가리키는 항목이 무엇인지 궁금한 경우 먼저 컨텍스트 저장소가 무엇인지 알아내야 합니다. 예를 들어 라벨이 @@foo 저장소에 있는 BUILD 파일에 있는 경우 컨텍스트 저장소는 @@foo입니다.

그런 다음 컨텍스트 저장소에 따라 마이그레이션 가이드의 "저장소 공개 상태" 표를 사용하여 명백한 이름이 실제로 확인되는 저장소를 확인할 수 있습니다.

  • 컨텍스트 저장소가 기본 저장소 (@@)인 경우:
    1. bar가 루트 모듈의 MODULE.bazel 파일에 의해 도입된 명백한 저장소 이름인 경우 (bazel_dep, use_repo, module, use_repo_rule 중 하나를 통해) @bar 는 해당 MODULE.bazel 파일에서 주장하는 항목으로 확인됩니다.
    2. 그렇지 않고 bar가 WORKSPACE에 정의된 저장소인 경우 (정식 이름이 @@bar임) @bar@@bar로 확인됩니다.
    3. 그렇지 않으면 @bar@@[unknown repo 'bar' requested from @@]와 같은 항목으로 확인되며, 결국 오류가 발생합니다.
  • 컨텍스트 저장소가 Bzlmod 세계 저장소인 경우 (즉, 루트가 아닌 Bazel 모듈에 상응하거나 모듈 확장 프로그램에 의해 생성됨) 다른 Bzlmod 세계 저장소만 볼 수 있으며 WORKSPACE 세계 저장소는 볼 수 없습니다.
    • 특히 여기에는 루트 모듈의 non_module_deps와 같은 모듈 확장 프로그램 또는 루트 모듈의 use_repo_rule 인스턴스화에 도입된 저장소가 포함됩니다.
  • 컨텍스트 저장소가 WORKSPACE에 정의된 경우:
    1. 먼저 컨텍스트 저장소 정의에 매직 repo_mapping 속성이 있는지 확인합니다. 그렇다면 먼저 매핑을 살펴봅니다 (따라서 로 정의된 저장소의 경우 아래에서 repo_mapping = {"@bar": "@baz"} 을 살펴봅니다).@baz
    2. bar가 루트 모듈의 MODULE.bazel 파일에 의해 도입된 명백한 저장소 이름인 경우 @bar는 해당 MODULE.bazel 파일 에서 주장하는 항목으로 확인됩니다. (이는 기본 저장소 사례의 항목 1과 동일합니다.)
    3. 그렇지 않으면 @bar@@bar로 확인됩니다. 이는 WORKSPACE에 정의된 저장소 bar를 가리킬 가능성이 높습니다. 이러한 저장소가 정의되지 않은 경우 Bazel은 오류를 발생시킵니다.

더 간결한 버전의 경우:

  • Bzlmod 세계 저장소 (기본 저장소 제외)는 Bzlmod 세계 저장소만 볼 수 있습니다.
  • WORKSPACE 세계 저장소 (기본 저장소 포함)는 먼저 Bzlmod 세계의 루트 모듈이 정의하는 항목을 확인한 다음 WORKSPACE 세계 저장소를 확인합니다.

Bazel 명령어 줄의 라벨 (Starlark 플래그, 라벨 유형 플래그 값, 빌드/테스트 대상 패턴 포함)은 기본 저장소를 컨텍스트 저장소로 갖는 것으로 처리됩니다.

기타

오프라인 빌드를 준비하고 실행하려면 어떻게 해야 하나요?

bazel fetch 명령어를 사용하여 저장소를 미리 가져옵니다. --repo 플래그 (bazel fetch --repo @foo와 같이)를 사용하여 @foo 저장소만 가져오거나 (기본 저장소의 컨텍스트에서 확인됨, 위의 질문 참고) 대상 패턴 (bazel fetch @foo//:bar와 같이)을 사용하여 @foo//:bar의 모든 전이 종속 항목을 가져올 수 있습니다 (이는 bazel build --nobuild @foo//:bar와 동일함).

빌드 중에 가져오기가 발생하지 않도록 하려면 --nofetch를 사용합니다. 더 정확히 말하면 이렇게 하면 로컬이 아닌 저장소 규칙을 실행하려는 시도가 실패합니다.

저장소를 가져오고 로컬에서 테스트하도록 수정하려면 bazel vendor 명령어를 사용하는 것이 좋습니다.

인터넷에서 빌드를 격리하려면 어떻게 해야 하나요?

다음은 일반적인 Bazel 빌드가 의존하는 공개적으로 액세스 가능한 인터넷의 웹사이트와 잠재적인 중단으로부터 자신을 격리하기 위해 할 수 있는 작업입니다(특히 엔터프라이즈 Bazel 사용자의 경우).

  • releases.bazel.build: Bazelisk는 이 웹사이트에서 Bazel 출시 바이너리를 다운로드합니다. Bazelisk가 회사 내부 미러에서 다운로드하도록 구성할 수 있습니다.
  • bcr.bazel.build: Bazel은 모듈 확인 중에 모듈 메타데이터를 위해 BCR을 참조합니다. BCR 자체를 미러링하고 --registry 플래그를 설정하여 BCR 제공 인프라에 대한 종속 항목을 삭제할 수 있습니다 (자세한 내용은 면책조항 참고). 또는 MODULE.bazel.lock 파일이 최신 상태인지 확인하고 미리 채워진 다운로드 캐시(--repository_cache)로 CI 또는 개발자 머신을 설정할 수 있습니다. 올바르게 설정되면 다운로드 캐시에는 빌드에 필요한 모든 레지스트리 파일이 포함되고 잠금 파일에는 체크섬이 포함됩니다. 그러면 Bazel은 캐시된 결과를 사용하고 모듈 확인 중에 인터넷 액세스를 완전히 피합니다.
  • mirror.bazel.buildgithub.com: 많은 모듈에는 이러한 두 웹사이트에 호스팅된 소스 보관 파일이 있습니다. 소스 보관 파일의 회사 내부 미러를 설정하고 --downloader_config 또는 --module_mirrors를 사용하여 Bazel이 이를 가리키도록 하는 것이 좋습니다. 또는 이전 글머리 기호에서 언급한 미리 채워진 다운로드 캐시도 Bazel이 소스 보관 파일에 액세스하기 위해 인터넷에 액세스하는 것을 완전히 방지하는 데 도움이 됩니다.

HTTP 프록시를 사용하려면 어떻게 해야 하나요?

Bazel은 curl과 같은 다른 프로그램에서 일반적으로 허용되는 http_proxyHTTPS_PROXY 환경 변수를 준수합니다.

이중 스택 IPv4/IPv6 설정에서 Bazel이 IPv6을 선호하도록 하려면 어떻게 해야 하나요?

IPv6 전용 머신에서 Bazel은 변경사항 없이 종속 항목을 다운로드할 수 있습니다. 하지만 이중 스택 IPv4/IPv6 머신에서 Bazel은 Java와 동일한 규칙을 따르며 사용 설정된 경우 IPv4를 선호합니다. 예를 들어 IPv4 네트워크가 외부 주소를 확인하거나 연결할 수 없는 경우 Network unreachable 예외 및 빌드 실패가 발생할 수 있습니다. 이러한 경우 Bazel의 동작을 재정의하여 IPv6을 선호할 수 있습니다. java.net.preferIPv6Addresses=true 시스템 속성을 사용하여 구체적인 내용은 다음과 같습니다.

  • --host_jvm_args=-Djava.net.preferIPv6Addresses=true 시작 옵션을 사용합니다. 예를 들어 .bazelrc 파일에 다음 줄을 추가합니다.

    startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true

  • 인터넷에 연결해야 하는 Java 빌드 대상 (예: 통합 테스트)을 실행할 때는 --jvmopt=-Djava.net.preferIPv6Addresses=true 도구 플래그를 사용합니다. 예를 들어 .bazelrc 파일에 다음을 포함합니다.

    build --jvmopt=-Djava.net.preferIPv6Addresses

  • 종속 항목 버전 확인에 rules_jvm_external을 사용하는 경우 Coursier의 JVM 옵션을 제공하려면 COURSIER_OPTS 환경 변수에 -Djava.net.preferIPv6Addresses=true도 추가합니다.

저장소 규칙을 원격 실행으로 원격으로 실행할 수 있나요?

아니요. 아직은 아닙니다. 빌드 속도를 높이기 위해 원격 실행 서비스를 사용하는 사용자는 저장소 규칙이 여전히 로컬에서 실행되는 것을 알 수 있습니다. 예를 들어 http_archive는 먼저 로컬 머신에 다운로드되고 (해당하는 경우 로컬 다운로드 캐시 사용) 추출된 다음 각 소스 파일이 입력 파일로 원격 실행 서비스에 업로드됩니다. 원격 실행 서비스가 해당 보관 파일을 다운로드하고 추출하여 쓸모없는 왕복을 저장하지 않는 이유는 무엇인지 묻는 것이 당연합니다.

그 이유 중 하나는 저장소 규칙 (및 모듈 확장 프로그램)이 Bazel 자체에서 실행되는 '스크립트'와 유사하기 때문입니다. 원격 실행기에는 Bazel이 설치되어 있지 않을 수도 있습니다.

또 다른 이유는 Bazel이 로드 및 분석을 실행하기 위해 다운로드되고 추출된 보관 파일의 BUILD 파일이 필요한 경우가 많기 때문입니다. 이러한 파일은 로컬에서 실행됩니다.

저장소 규칙을 빌드 규칙으로 다시 구상하여 이 문제를 해결하는 예비 아이디어가 있습니다. 이렇게 하면 저장소 규칙을 원격으로 실행할 수 있지만 반대로 새로운 아키텍처 문제가 발생합니다 (예: query 명령어는 작업을 실행해야 할 수 있으므로 설계가 복잡해짐).

이 주제에 관한 이전 논의는 가져오기를 위해 Bazel이 필요한 저장소를 지원하는 방법을 참고하세요.