Bzlmod는 Bazel 5.0에 도입된 새로운 외부 종속 항목 시스템의 코드명입니다. 이 시스템은 점진적으로 수정할 수 없는 이전 시스템의 여러 문제점을 해결하기 위해 도입되었습니다. 자세한 내용은 원래 설계 문서의 문제 설명 섹션을 참고하세요.
Bazel 5.0에서는 Bzlmod가 기본적으로 사용 설정되어 있지 않습니다. 다음이 적용되려면 --experimental_enable_bzlmod
플래그를 지정해야 합니다. 플래그 이름에서 알 수 있듯이 이 기능은 현재 실험용입니다. 기능이 공식적으로 출시될 때까지 API와 동작이 변경될 수 있습니다.
프로젝트를 Bzlmod로 이전하려면 Bzlmod 이전 가이드를 따르세요. examples 저장소에서도 Bzlmod 사용 예시를 확인할 수 있습니다.
Bazel 모듈
이전 WORKSPACE
기반 외부 종속 항목 시스템은 저장소 규칙 (또는 저장소 규칙)을 통해 생성된 저장소 (또는 저장소)를 중심으로 합니다.
새 시스템에서도 저장소는 여전히 중요한 개념이지만 모듈은 종속 항목의 핵심 단위입니다.
모듈은 기본적으로 여러 버전을 가질 수 있는 Bazel 프로젝트이며 각 버전은 종속된 다른 모듈에 관한 메타데이터를 게시합니다. 이는 Maven 아티팩트, npm 패키지, Cargo 크레이트, Go 모듈 등 다른 종속 항목 관리 시스템의 친숙한 개념과 유사합니다.
모듈은 WORKSPACE
의 특정 URL 대신 name
및 version
쌍을 사용하여 종속 항목을 지정합니다. 그런 다음 종속 항목이 Bazel 레지스트리(기본적으로 Bazel 중앙 레지스트리)에서 조회됩니다. 작업공간에서 각 모듈은 저장소로 변환됩니다.
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
파일에서 지원하는 지시어는 다음과 같습니다.
module
: 이름, 버전 등 현재 모듈에 관한 메타데이터를 지정합니다.bazel_dep
: 다른 Bazel 모듈의 직접 종속 항목을 지정합니다.- 재정의는 루트 모듈 (즉, 종속 항목으로 사용되는 모듈이 아님)에서만 특정 직접 또는 임시 종속 항목의 동작을 맞춤설정하는 데 사용할 수 있습니다.
- 모듈 확장 프로그램과 관련된 지시어:
버전 형식
Bazel에는 다양한 생태계가 있으며 프로젝트에서는 다양한 버전 관리 스키마를 사용합니다. 가장 인기 있는 버전 관리 체계는 SemVer이지만, 버전이 날짜 기반인 20210324.2
와 같은 Abseil과 같이 다른 체계를 사용하는 유명 프로젝트도 있습니다.
이러한 이유로 Bzlmod는 더 완화된 버전의 SemVer 사양을 채택합니다. 차이점은 다음과 같습니다.
- SemVer에서는 버전의 'release' 부분이
MAJOR.MINOR.PATCH
의 세그먼트로 구성되어야 한다고 규정합니다. Bazel에서는 이 요구사항이 완화되어 세그먼트 수가 허용됩니다. - SemVer에서 '버전' 부분의 각 세그먼트는 숫자만으로 구성되어야 합니다. Bazel에서는 문자도 허용하도록 완화되어 비교 의미 체계가 '사전 출시' 부분의 '식별자'와 일치합니다.
- 또한 주 버전, 부 버전, 패치 버전 증가의 시맨틱이 적용되지 않습니다. (하지만 이전 버전과의 호환성을 나타내는 방법에 관한 자세한 내용은 호환성 수준을 참고하세요.)
유효한 SemVer 버전은 유효한 Bazel 모듈 버전입니다. 또한 두 SemVer 버전 a
와 b
는 Bazel 모듈 버전으로 비교할 때 동일한 경우에만 a < b
를 비교합니다.
버전 해결
다이아몬드 종속 항목 문제는 버전이 지정된 종속 항목 관리 공간에서 흔히 볼 수 있습니다. 다음과 같은 종속 항목 그래프가 있다고 가정해 보겠습니다.
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
어떤 버전의 D를 사용해야 하나요? 이 문제를 해결하기 위해 Bzlmod는 Go 모듈 시스템에 도입된 최소 버전 선택(MVS) 알고리즘을 사용합니다. MVS는 모듈의 모든 새 버전이 이전 버전과 호환된다고 가정하므로 종속 항목 (예의 D 1.1)에서 지정한 가장 높은 버전을 선택합니다. 여기서 D 1.1은 Google의 요구사항을 충족할 수 있는 최소 버전이므로 '최소'라고 합니다. D 1.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
)
- Bazel 모듈 저장소의 경우:
명백한 저장소 이름: 저장소 내의
BUILD
및.bzl
파일에서 사용할 저장소 이름입니다. 동일한 종속 항목이 저장소마다 다른 이름으로 표시될 수 있습니다.
은 다음과 같이 결정됩니다.
모든 저장소에는 직접 종속 항목의 저장소 매핑 사전이 있습니다. 이는 명시적 저장소 이름에서 표준 저장소 이름으로의 매핑입니다.
라벨을 구성할 때 저장소 이름을 확인하기 위해 저장소 매핑을 사용합니다. 표준 저장소 이름은 충돌하지 않으며 MODULE.bazel
파일을 파싱하여 명시적 저장소 이름의 사용을 검색할 수 있으므로 다른 종속 항목에 영향을 주지 않고 충돌을 쉽게 포착하고 해결할 수 있습니다.
엄격한 종속 항목
새로운 종속 항목 사양 형식을 사용하면 더 엄격한 검사를 실행할 수 있습니다. 특히 이제 모듈은 직접 종속 항목에서 생성된 저장소만 사용할 수 있습니다. 이렇게 하면 전이 종속 항목 그래프의 항목이 변경될 때 실수로 발생하고 디버그하기 어려운 손상을 방지할 수 있습니다.
엄격한 종속 항목은 저장소 매핑을 기반으로 구현됩니다. 기본적으로 각 저장소의 저장소 매핑에는 모든 직접 종속 항목이 포함되며 다른 저장소는 표시되지 않습니다. 각 저장소의 표시되는 종속 항목은 다음과 같이 결정됩니다.
- Bazel 모듈 저장소는
bazel_dep
및use_repo
를 통해MODULE.bazel
파일에 도입된 모든 저장소를 볼 수 있습니다. - 모듈 확장 프로그램 저장소는 확장 프로그램을 제공하는 모듈의 표시되는 모든 종속 항목과 동일한 모듈 확장 프로그램에서 생성된 다른 모든 저장소를 볼 수 있습니다.
레지스트리
Bzlmod는 Bazel 레지스트리에서 정보를 요청하여 종속 항목을 검색합니다. Bazel 레지스트리는 Bazel 모듈의 데이터베이스일 뿐입니다. 지원되는 레지스트리 형식은 색인 레지스트리뿐이며, 이는 특정 형식을 따르는 로컬 디렉터리 또는 정적 HTTP 서버입니다. 향후에는 프로젝트의 소스와 기록이 포함된 git 저장소인 단일 모듈 레지스트리 지원을 추가할 계획입니다.
색인 레지스트리
색인 레지스트리는 홈페이지, 유지관리자, 각 버전의 MODULE.bazel
파일, 각 버전의 소스를 가져오는 방법 등 모듈 목록에 관한 정보가 포함된 로컬 디렉터리 또는 정적 HTTP 서버입니다. 특히 소스 보관 파일을 직접 제공할 필요는 없습니다.
색인 레지스트리는 아래 형식을 따라야 합니다.
/bazel_registry.json
: 다음과 같은 레지스트리의 메타데이터가 포함된 JSON 파일입니다.mirrors
: 소스 보관 파일에 사용할 미러 목록을 지정합니다.source.json
파일에서local_repository
유형의 모듈 기본 경로를 지정하는module_base_path
/modules
: 이 레지스트리의 각 모듈에 대한 하위 디렉터리가 포함된 디렉터리입니다./modules/$MODULE
: 이 모듈의 각 버전에 대한 하위 디렉터리와 다음 파일이 포함된 디렉터리입니다.metadata.json
: 모듈에 관한 정보가 포함된 JSON 파일입니다. 다음 필드가 있습니다.homepage
: 프로젝트 홈페이지의 URL입니다.maintainers
: JSON 객체 목록입니다. 각 객체는 등록처에 있는 모듈의 유지관리자 정보에 해당합니다. 이는 프로젝트의 작성자와 반드시 동일하지는 않습니다.versions
: 이 레지스트리에서 찾을 수 있는 이 모듈의 모든 버전 목록입니다.yanked_versions
: 이 모듈의 yanked 버전 목록입니다. 현재는 아무 작업도 하지 않지만 향후에는 차단된 버전이 건너뛰거나 오류가 발생합니다.
/modules/$MODULE/$VERSION
: 다음 파일이 포함된 디렉터리MODULE.bazel
: 이 모듈 버전의MODULE.bazel
파일입니다.source.json
: 이 모듈 버전의 소스를 가져오는 방법에 관한 정보가 포함된 JSON 파일입니다.- 기본 유형은 'archive'이며 다음 필드가 있습니다.
url
: 소스 보관 파일의 URL입니다.integrity
: 보관 파일의 하위 리소스 무결성 체크섬입니다.strip_prefix
: 소스 보관 파일을 추출할 때 삭제할 디렉터리 접두사입니다.patches
: 문자열 목록으로, 각 문자열은 추출된 보관 파일에 적용할 패치 파일의 이름을 지정합니다. 패치 파일은/modules/$MODULE/$VERSION/patches
디렉터리에 있습니다.patch_strip
: Unix 패치의--strip
인수와 동일합니다.
- 다음 필드를 사용하여 로컬 경로를 사용하도록 유형을 변경할 수 있습니다.
type
:local_path
path
: 저장소의 로컬 경로입니다. 다음과 같이 계산됩니다.- 경로가 절대 경로인 경우 그대로 사용됩니다.
- 경로가 상대 경로이고
module_base_path
이 절대 경로인 경우 경로는<module_base_path>/<path>
로 확인됩니다. - 경로와
module_base_path
가 모두 상대 경로인 경우 경로는<registry_path>/<module_base_path>/<path>
로 확인됩니다. 레지스트리는 로컬로 호스팅되어야 하며--registry=file://<registry_path>
에서 사용해야 합니다. 그렇지 않으면 Bazel에서 오류가 발생합니다.
- 기본 유형은 'archive'이며 다음 필드가 있습니다.
patches/
: 패치 파일이 포함된 선택적 디렉터리입니다.source.json
의 유형이 'archive'인 경우에만 사용됩니다.
Bazel 중앙 레지스트리
Bazel 중앙 레지스트리 (BCR)는 bcr.bazel.build에 있는 색인 레지스트리입니다. 이 콘텐츠는 GitHub 저장소 bazelbuild/bazel-central-registry
에서 지원합니다.
BCR은 Bazel 커뮤니티에서 유지관리합니다. 기여자는 풀 요청을 제출할 수 있습니다. 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.1
및 B 1.2
등은 Bazel 모듈입니다. 각 모듈을 MODULE.bazel
파일로 생각하면 됩니다. 각 모듈은 모듈 확장 프로그램의 태그를 지정할 수 있습니다. 여기서는 일부가 'maven' 확장 프로그램에 지정되고 일부는 'cargo'에 지정됩니다. 이 종속 항목 그래프가 완료되면 (예: B 1.2
에 실제로 D 1.3
의 bazel_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
지시문을 사용하여 선언합니다. 이는 엄격한 종속 항목 조건을 충족하고 로컬 저장소 이름 충돌을 방지하기 위한 것입니다.
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
es가 있으며 각 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.dep
및 maven.pom
태그를 지정할 수 있음이 명확해집니다.
구현 함수는 WORKSPACE
매크로와 비슷하지만 종속 항목 그래프와 모든 관련 태그에 대한 액세스 권한을 부여하는 module_ctx
객체를 가져옵니다. 그런 다음 구현 함수는 저장소 규칙을 호출하여 저장소를 생성해야 합니다.
# @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
)의 모든 모듈을 살펴봅니다. 각 모듈은 tags
필드가 모듈의 모든 maven.*
태그를 노출하는 bazel_module
객체입니다. 그런 다음 CLI 유틸리티 Coursier를 호출하여 Maven에 연결하고 해결을 실행합니다. 마지막으로 해결 결과를 사용하여 가상의 maven_single_jar
저장소 규칙을 사용하여 여러 저장소를 만듭니다.