Bzlmod로 외부 종속 항목 관리

Bzlmod는 Bazel 5.0에 도입된 새로운 외부 종속 항목 시스템의 코드 이름입니다. 이는 단계적으로 수정할 수 없는 이전 시스템의 몇 가지 고충을 해결하기 위해 도입되었습니다. 자세한 내용은 원본 디자인 문서의 문제 설명 섹션을 참고하세요.

Bazel 5.0에서 Bzlmod는 기본적으로 사용 설정되지 않습니다. --experimental_enable_bzlmod를 적용하려면 다음을 지정해야 합니다. 플래그 이름에서 알 수 있듯이 이 기능은 현재 실험용이며 이 기능이 공식적으로 출시될 때까지 API와 동작이 변경될 수 있습니다.

Bazel 모듈

기존WORKSPACE 기반의 외부 종속 항목 시스템은저장소 (또는저장소 ), 에 의해 생성됨저장소 규칙 (또는저장소 규칙 참고). 저장소는 새 시스템에서 여전히 중요한 개념이지만 모듈은 종속 항목의 핵심 단위입니다.

모듈은 본질적으로 여러 버전을 포함할 수 있는 Bazel 프로젝트이며, 각 프로젝트에는 종속된 다른 모듈에 대한 메타데이터가 게시됩니다. 이는 다른 종속 항목 관리 시스템의 익숙한 개념인 Maven 아티팩트, npm 패키지, Cargo 크레이트와 비슷합니다. , Go 모듈

모듈은 단순히 WORKSPACE의 특정 URL 대신 nameversion 쌍을 사용하여 종속 항목을 지정합니다. 그런 다음 Bazel 레지스트리에서 종속 항목을 찾습니다. 기본적으로 Bazel Central Registry입니다. 작업공간에서 각 모듈이 저장소로 바뀝니다.

MODULE.bazel

모든 모듈의 모든 버전에는 종속 항목 및 기타 메타데이터를 선언하는 MODULE.bazel 파일이 있습니다. 다음은 기본적인 예입니다.

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

MODULE.bazel 파일은 작업공간 디렉터리의 루트(WORKSPACE 파일 옆)에 있어야 합니다. WORKSPACE 파일과 달리 전이 종속 항목을 지정할 필요가 없습니다. 대신 직접 종속 항목만 지정해야 하며, 종속 항목의 MODULE.bazel 파일은 전이 종속 항목을 자동으로 검색하도록 처리됩니다.

MODULE.bazel 파일은 어떠한 형태의 제어 흐름도 지원하지 않으므로 BUILD 파일과 유사합니다. load 문도 추가로 금지됩니다. MODULE.bazel 파일에서 지원하는 명령어는 다음과 같습니다.

버전 형식

Bazel은 다양한 생태계를 보유하고 있으며 프로젝트는 다양한 버전 관리 체계를 사용합니다. 현재 가장 많이 사용되는 방법은 SemVer이지만 Abseil과 같이 서로 다른 스키마를 사용하는 명시적 프로젝트도 있습니다. 날짜 기반(예: 20210324.2)

이러한 이유로 Bzlmod는 특히 SemVer 사양에서 정확히 3개가 아닌 '출시' 부분의 숫자 시퀀스를 모두 허용하여 SemVer 사양의 더 완화된 버전을 채택합니다.MAJOR.MINOR.PATCH 또한 주, 부, 패치 버전 증가의 의미 체계는 적용되지 않습니다. 하지만 이전 버전과의 호환성을 표시하는 방법에 관한 자세한 내용은 호환성 수준을 참조하세요. 출시 전 버전을 나타내는 하이픈과 같은 SemVer 사양의 다른 부분은 수정되지 않습니다.

버전 해결

다이아몬드 종속 문제는 버전이 지정된 종속 항목 관리 공간의 필수 요소입니다. 다음과 같은 종속 항목 그래프가 있다고 가정해 보겠습니다.

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

어떤 D 버전을 사용해야 하나요? 이 문제를 해결하기 위해 Bzlmod는 Go 모듈 시스템에 도입된 최소 버전 선택(MVS) 알고리즘을 사용합니다. MVS는 모듈의 모든 새 버전이 이전 버전과 호환된다고 가정하므로 종속 항목에 지정된 가장 높은 버전만 선택합니다 (이 예에서는 D 1.1). D1.1이 Google 요구사항을 충족할 수 있는 최소 버전이므로 '최소' 버전을 뜻합니다. D1.2 이상이 있더라도 버전을 선택하지 않습니다. 이렇게 하면 버전 선택이 고충실도재현 가능성이 강화된다는 추가 이점이 있습니다.

버전 확인은 레지스트리가 아닌 컴퓨터에서 로컬로 수행됩니다.

호환성 수준

참고로, MVS는 이전 버전과 호환되지 않는 버전을 별도의 모듈로 취급하기 때문에 이전 버전과의 호환성에 관해 가정할 수 있습니다. SemVer의 관점에서 A 1.x 및 A 2.x는 별개의 모듈로 간주되며 확인된 종속 항목 그래프와 함께 존재할 수 있습니다. 이는 주 버전이 Go의 패키지 경로에 인코딩되어 있기 때문에 컴파일 시간 또는 연결 시간 충돌이 발생하지 않습니다.

Bazel에서는 이러한 보장이 없습니다. 따라서 이전 버전과 호환되지 않는 버전을 감지하려면 '주 버전' 번호를 표시해야 합니다. 이 숫자를 호환성 수준이라고 하며 module() 지시문에 있는 각 모듈 버전에 의해 지정됩니다. 이 정보를 활용하면 확인된 종속 항목 그래프에 호환성 수준이 다른 동일한 모듈의 버전이 있는 것을 감지하면 오류가 발생할 수 있습니다.

저장소 이름

Bazel에서 모든 외부 종속 항목에는 저장소 이름이 있습니다. 경우에 따라 동일한 종속 항목이 다른 저장소 이름을 통해 사용되기도 합니다 (예: @io_bazel_skylib@bazel_skylib 모두 Bazel skylib를 의미). 저장소 이름은 프로젝트마다 다른 종속 항목에 사용될 수 있습니다.

Bzlmod에서는 Bazel 모듈과 모듈 확장 프로그램으로 저장소를 생성할 수 있습니다. 저장소 이름 충돌을 해결하기 위해 새 시스템에서 저장소 매핑 메커니즘을 사용합니다. 다음은 두 가지 중요한 개념입니다.

  • 정규 저장소 이름: 각 저장소의 전역적으로 고유한 저장소 이름입니다. 저장소가 있는 디렉터리 이름이 됩니다.
    다음과 같이 구성됩니다 (경고: 표준 이름 형식은 개발자가 사용해야 하는 API가 아니며 언제든지 변경될 수 있음).

    • Bazel 모듈 저장소의 경우: module_name.version
      (. @bazel_skylib.1.0.3)
    • 모듈 확장 저장소: module_name.version.extension_name.repo_name
      (. @rules_cc.0.0.1.cc_configure.local_config_cc)
  • 로컬 저장소 이름: 저장소 내 BUILD.bzl 파일에 사용할 저장소 이름입니다. 동일한 종속 항목은 저장소마다 다른 로컬 이름을 가질 수 있습니다.
    다음과 같이 결정됩니다.

    • Bazel 모듈 저장소의 경우 기본적으로 module_name 또는 bazel_deprepo_name 속성으로 지정된 이름입니다.
    • 모듈 확장 저장소: use_repo를 통해 도입된 저장소 이름입니다.

모든 저장소에는 로컬 저장소 이름과 정규 저장소 이름 간의 매핑인 직접 종속 항목의 저장소 매핑 사전이 있습니다. 라벨을 구성할 때 저장소 매핑을 사용하여 저장소 이름을 확인합니다. 표준 저장소 이름은 충돌하지 않으며 MODULE.bazel 파일을 파싱하여 로컬 저장소 이름의 사용을 알 수 있으므로 충돌 없이 쉽게 충돌을 포착하고 해결할 수 있습니다. 기타 종속 항목

엄격한 deps

새로운 종속 항목 사양 형식을 사용하면 더 엄격하게 확인할 수 있습니다. 특히 모듈은 직접적인 종속 항목에서 생성된 저장소만 사용할 수 있도록 했습니다. 이렇게 하면 전이 종속 항목 그래프에서 무언가가 변경될 때 우발적이고 디버그하기 어려운 중단을 방지할 수 있습니다.

엄격한 deps는 저장소 매핑에 따라 구현됩니다. 기본적으로 각 저장소의 저장소 매핑에는 모든 직접 종속 항목이 포함되며, 다른 저장소는 표시되지 않습니다. 각 저장소의 시각적 종속 항목은 다음과 같이 결정됩니다.

  • Bazel 모듈 저장소는 bazel_depuse_repo를 통해 MODULE.bazel 파일에 도입된 모든 저장소를 볼 수 있습니다.
  • 모듈 확장 저장소는 확장 프로그램을 제공하는 모듈의 보이는 모든 종속 항목과 동일한 모듈 확장 프로그램에서 생성한 기타 저장소를 볼 수 있습니다.

레지스트리

Bzlmod는 Bazel 레지스트리에 정보를 요청하여 종속 항목을 검색합니다. Bazel 레지스트리는 단순히 Bazel 모듈의 데이터베이스입니다. 지원되는 유일한 레지스트리 형식은 로컬 디렉터리 또는 특정 형식을 따르는 정적 HTTP 서버인 색인 레지스트리입니다. 향후에 프로젝트의 소스 및 기록이 포함된 git 저장소인 단일 모듈 레지스트리에 대한 지원을 추가할 계획입니다.

색인 레지스트리

색인 레지스트리는 홈페이지, 유지 관리자, 각 버전의 MODULE.bazel 파일, 각 모듈의 소스를 가져오는 방법 등 모듈 목록에 대한 정보가 포함된 로컬 디렉터리 또는 정적 HTTP 서버입니다. 있습니다. 특히 소스 보관 파일 자체를 게재할 필요는 없습니다.

색인 레지스트리는 아래 형식을 따라야 합니다.

  • /bazel_registry.json: 레지스트리의 메타데이터가 포함된 JSON 파일입니다. 현재는 소스 보관함에 사용할 미러 목록을 지정하는 mirrors 키 하나만 있습니다.
  • /modules: 이 레지스트리의 각 모듈에 대한 하위 디렉터리가 포함된 디렉터리입니다.
  • /modules/$MODULE: 이 모듈의 각 버전에 대한 하위 디렉터리와 다음 파일을 포함하는 디렉터리입니다.
    • metadata.json: 다음 필드에 대한 정보가 포함된 JSON 파일입니다.
      • homepage: 프로젝트 홈페이지의 URL입니다.
      • maintainers: JSON 객체의 목록으로, 각 목록은 레지스트리에 있는 모듈의 유지 관리 프로그램 정보에 해당합니다. 이는 프로젝트의 저자와 반드시 동일하지는 않습니다.
      • versions: 이 레지스트리에서 찾을 수 있는 이 모듈의 모든 버전 목록입니다.
      • yanked_versions: 이 모듈의 압축된 버전 목록입니다. 이 작업은 현재 작동하지 않지만 향후에는 건너 versions 버전을 건너뛰거나 오류가 발생합니다.
  • /modules/$MODULE/$VERSION: 다음 파일이 포함된 디렉터리입니다.
    • MODULE.bazel: 이 모듈 버전의 MODULE.bazel 파일입니다.
    • source.json이 모듈 버전의 소스를 가져오는 방법에 관한 정보가 포함된 JSON 파일로, 다음 필드가 있습니다.
      • url: 소스 보관 파일의 URL입니다.
      • integrity: 보관 파일의 하위 리소스 무결성 체크섬입니다.
      • strip_prefix: 소스 보관 파일을 추출할 때 제거할 디렉터리 프리픽스입니다.
      • patches: 추출된 아카이브에 적용할 패치 파일의 이름을 각각 지정하는 문자열 목록입니다. 패치 파일은 /modules/$MODULE/$VERSION/patches 디렉터리에 있습니다.
      • patch_strip: Unix 패치의 --strip 인수와 동일합니다.
    • patches/: 패치 파일이 포함된 선택적 디렉터리입니다.

Bazel Central 레지스트리

Bazel Central Registry (BCR)는 registry.bazel.build에 있는 색인 레지스트리입니다. 내용은 GitHub 저장소 bazelbuild/bazel-central-registry로 지원됩니다.

BCR은 Bazel 커뮤니티에서 관리합니다. pull 요청도 제출할 수 있습니다. Bazel 중앙 레지스트리 정책 및 절차를 참조하세요.

BCR은 일반 색인 레지스트리의 형식을 따르는 것 외에도 각 모듈 버전(/modules/$MODULE/$VERSION/presubmit.yml)에 대한 presubmit.yml 파일이 필요합니다. 이 파일은 이 모듈 버전의 유효성을 온전히 확인하는 데 사용할 수 있는 몇 가지 필수 빌드 및 테스트 대상을 지정하며 BCR의 CI 파이프라인에서 BCR의 모듈 간 상호 운용성을 보장하는 데 사용됩니다. 에서 확인할 수 있습니다.

레지스트리 선택

반복 가능한 Bazel 플래그 --registry를 사용하여 모듈을 요청할 레지스트리 목록을 지정할 수 있으므로 타사 또는 내부 레지스트리에서 종속 항목을 가져오도록 프로젝트를 설정할 수 있습니다. 이전 레지스트리가 우선합니다. 편의를 위해 프로젝트의 .bazelrc 파일에 --registry 플래그 목록을 추가할 수 있습니다.

모듈 확장 프로그램

모듈 확장 프로그램을 사용하면 종속 항목 그래프에서 입력 데이터를 읽고 종속 항목을 해결하는 데 필요한 로직을 실행하며 마지막으로 저장소 규칙을 호출하여 저장소를 만들어 모듈 시스템을 확장할 수 있습니다. 함수는 현재 WORKSPACE 매크로와 기능적으로는 유사하지만 모듈 및 전이 종속 항목에 더 적합합니다.

모듈 확장 프로그램은 저장소 규칙 또는 WORKSPACE 매크로와 마찬가지로 .bzl 파일에서 정의됩니다. 직접 호출되지 않습니다. 대신 각 모듈이 확장 프로그램이 읽을 태그라는 데이터를 지정할 수 있습니다. 그런 다음 모듈 버전 확인이 완료되면 모듈 확장 프로그램이 실행됩니다. 각 확장 프로그램은 모듈 확인 후 한 번 실행되며 (빌드가 실제로 발생하기 전까지) 전체 종속 항목 그래프에서 여기에 속한 모든 태그를 읽습니다.

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

위의 종속 항목 그래프에서 A 1.1B 1.2 등은 Bazel 모듈입니다. 각 모듈을 MODULE.bazel 파일로 생각할 수 있습니다. 각 모듈은 모듈 확장 프로그램에 일부 태그를 지정할 수 있습니다. 여기서는 확장 프로그램 'maven'에 대해 지정되고 'cargo'에 대해 지정됩니다. 이 종속 항목 그래프가 확정되면 (예: B 1.2가 실제로 D 1.3bazel_dep을 갖고 있지만 C로 인해 D 1.4로 업그레이드된 경우) 확장 프로그램 'maven'이 실행되고 이 파일에 있는 정보를 사용하여 모든 maven.* 태그를 읽어야 할 저장소를 만듭니다. 이는 'cargo' 확장 프로그램의 경우에도 마찬가지입니다.

확장 프로그램 사용

확장 프로그램은 Bazel 모듈 자체에서 호스팅되므로 모듈에 확장 프로그램을 사용하려면 먼저bazel_dep 그런 다음use_extension 기본 제공되는 함수로 범위를 확장할 수 있습니다. 다음은 rules_jvm_external 모듈에 정의된 가설적인 'maven' 확장을 사용하기 위한 MODULE.bazel 파일의 스니펫입니다.

bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

확장 프로그램을 범위로 가져온 다음 점 구문으로 태그를 지정할 수 있습니다. 태그는 해당하는 태그 클래스에서 정의한 스키마를 따라야 합니다 (아래 확장 프로그램 정의 참조). 다음은 maven.dep 태그와 maven.pom 태그를 지정하는 예시입니다.

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

확장 프로그램에서 모듈에 사용할 저장소를 생성하면 use_repo 지시문을 사용하여 선언합니다. 이는 엄격한 deps 조건을 충족하고 로컬 저장소 이름 충돌을 방지하기 위함입니다.

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

확장 프로그램에서 생성한 저장소는 API의 일부이므로 지정된 태그에서 'maven' 확장 프로그램이 'org_junit_junit'이라는 저장소와 'com_google_guava_guava'라는 저장소를 생성한다는 것을 알아야 합니다. ' use_repo을 사용하면 필요에 따라 모듈 범위에서 이름을 바꿀 수 있습니다(예: 'guava').

확장 프로그램 정의

모듈 확장 프로그램은 저장소 규칙과 유사하게 module_extension 함수를 사용하여 정의됩니다. 둘 다 구현 함수가 있음 저장소 규칙에는 많은 속성이 있지만 모듈 확장에는 여러 tag_class가 있으며 각 파일에는 여러 속성이 있습니다. 태그 클래스는 이 확장 프로그램에서 사용하는 태그의 스키마를 정의합니다. 위의 가상의 'maven' 확장 사례를 계속 진행합니다.

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

이러한 선언은 위에 정의된 속성 스키마를 사용하여 maven.depmaven.pom 태그를 지정할 수 있음을 명확히 나타냅니다.

구현 함수는 종속 항목 그래프 및 모든 관련 태그에 대한 액세스 권한을 부여하는 module_ctx 객체를 얻는다는 점을 제외하면 WORKSPACE 매크로와 유사합니다. 그런 다음 구현 함수가 저장소 규칙을 호출하여 저장소를 생성해야 합니다.

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

위의 예에서는 종속 항목 그래프(ctx.modules)의 모든 모듈을 살펴보겠습니다. 각 모듈은 tagsbazel_module 객체입니다. 필드는 모듈의 모든 maven.* 태그를 노출합니다. 그런 다음 CLI 유틸리티 Coursier를 호출하여 Maven에 연결하고 해결 작업을 수행합니다. 마지막으로 결정 결과를 사용하여 가상의 maven_single_jar 저장소 규칙을 사용하여 여러 저장소를 만듭니다.