모듈 확장을 사용하면 종속 항목 그래프에서 모듈의 입력 데이터를 읽고, 종속 항목을 해결하는 데 필요한 로직을 실행하고, 마지막으로 저장소 규칙을 호출하여 저장소를 생성하여 모듈 시스템을 확장할 수 있습니다. 이러한 확장 프로그램에는 파일 I/O를 실행하고 네트워크 요청을 전송하는 등의 작업을 할 수 있는 저장소 규칙과 유사한 기능이 있습니다. 무엇보다도 Bazel을 사용하면 Bazel에서 다른 패키지 관리 시스템과 상호작용할 수 있으며 Bazel 모듈로 빌드된 종속 항목 그래프를 준수할 수 있습니다.
저장소 규칙과 마찬가지로 .bzl
파일에서 모듈 확장 프로그램을 정의할 수 있습니다. 직접 호출되지는 않습니다. 대신 각 모듈은 확장 프로그램이 읽을 수 있도록 태그라는 데이터 조각을 지정합니다. Bazel은 확장 프로그램을 평가하기 전에 모듈 확인을 실행합니다. 확장 프로그램은 전체 종속 항목 그래프에서 확장 프로그램에 속한 모든 태그를 읽습니다.
확장 프로그램 사용
확장 프로그램은 Bazel 모듈 자체에서 호스팅됩니다. 모듈에서 확장 프로그램을 사용하려면 먼저 확장 프로그램을 호스팅하는 모듈에 bazel_dep
를 추가한 다음 use_extension
내장 함수를 호출하여 범위로 가져옵니다. rules_jvm_external
모듈에 정의된 'maven' 확장 프로그램을 사용하는 MODULE.bazel
파일의 스니펫을 예로 들어 보겠습니다.
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
이렇게 하면 use_extension
의 반환 값이 변수에 바인딩되므로 사용자가 점 문법을 사용하여 확장 프로그램의 태그를 지정할 수 있습니다. 태그는 확장 프로그램 정의에 지정된 상응하는 태그 클래스에 의해 정의된 스키마를 따라야 합니다. 일부 maven.install
및 maven.artifact
태그를 지정하는 예는 다음과 같습니다.
maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
artifact = "guava",
version = "27.0-jre",
exclusions = ["com.google.j2objc:j2objc-annotations"])
use_repo
지시어를 사용하여 확장 프로그램에서 생성된 저장소를 현재 모듈의 범위로 가져옵니다.
use_repo(maven, "maven")
확장 프로그램에서 생성된 저장소는 API의 일부입니다. 이 예에서 'maven' 모듈 확장 프로그램은 maven
라는 저장소를 생성한다고 약속합니다. 위의 선언을 사용하면 확장 프로그램이 @maven//:org_junit_junit
와 같은 라벨을 올바르게 확인하여 'maven' 확장 프로그램에서 생성된 저장소를 가리킵니다.
확장 프로그램 정의
module_extension
함수를 사용하여 저장소 규칙과 마찬가지로 모듈 확장 프로그램을 정의할 수 있습니다. 그러나 저장소 규칙에는 여러 속성이 있는 반면 모듈 확장 프로그램에는 각각 여러 속성이 있는 tag_class
가 있습니다. 태그 클래스는 이 확장 프로그램에서 사용하는 태그의 스키마를 정의합니다. 예를 들어 위의 'maven' 확장 프로그램은 다음과 같이 정의될 수 있습니다.
# @rules_jvm_external//:extensions.bzl
_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
implementation = _maven_impl,
tag_classes = {"install": _install, "artifact": _artifact},
)
이러한 선언은 지정된 속성 스키마를 사용하여 maven.install
및 maven.artifact
태그를 지정할 수 있음을 보여줍니다.
모듈 확장 프로그램의 구현 함수는 저장소 규칙의 구현 함수와 유사하지만, 확장 프로그램을 사용하는 모든 모듈과 모든 관련 태그에 대한 액세스 권한을 부여하는 module_ctx
객체를 가져온다는 점을 제외하고는 동일합니다.
그런 다음 구현 함수는 저장소 규칙을 호출하여 저장소를 생성합니다.
# @rules_jvm_external//:extensions.bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # a repo rule
def _maven_impl(ctx):
# This is a fake implementation for demonstration purposes only
# collect artifacts from across the dependency graph
artifacts = []
for mod in ctx.modules:
for install in mod.tags.install:
artifacts += install.artifacts
artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]
# call out to the coursier CLI tool to resolve dependencies
output = ctx.execute(["coursier", "resolve", artifacts])
repo_attrs = _process_coursier_output(output)
# call repo rules to generate repos
for attrs in repo_attrs:
http_file(**attrs)
_generate_hub_repo(name = "maven", repo_attrs)
확장 프로그램 ID
모듈 확장 프로그램은 이름과 use_extension
호출에 표시되는 .bzl
파일로 식별됩니다. 다음 예에서 확장자 maven
는 .bzl
파일 @rules_jvm_external//:extension.bzl
및 이름 maven
로 식별됩니다.
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
다른 .bzl
파일에서 확장 프로그램을 다시 내보내면 새로운 ID가 생성되며 두 버전의 확장 프로그램이 전이 모듈 그래프에서 사용되는 경우 별도로 평가되며 해당 ID와 연결된 태그만 표시됩니다.
확장 프로그램 작성자는 사용자가 단일 .bzl
파일에서만 모듈 확장 프로그램을 사용하도록 해야 합니다.
저장소 이름 및 공개 상태
확장 프로그램에서 생성한 저장소의 정규 이름은 module_repo_canonical_name+extension_name+repo_name
형식입니다. 표준 이름 형식은 종속해야 하는 API가 아니며 언제든지 변경될 수 있습니다.
이 이름 지정 정책은 각 확장 프로그램에 자체 '저장소 네임스페이스'가 있음을 의미합니다. 두 개의 서로 다른 확장 프로그램은 충돌 위험 없이 동일한 이름의 저장소를 각각 정의할 수 있습니다. 또한 repository_ctx.name
는 저장소의 표준 이름을 보고하며, 이는 저장소 규칙 호출에 지정된 이름과 다릅니다.
모듈 확장 프로그램에서 생성한 저장소를 고려할 때 다음과 같은 몇 가지 저장소 공개 상태 규칙이 있습니다.
- Bazel 모듈 저장소는
bazel_dep
및use_repo
를 통해MODULE.bazel
파일에 도입된 모든 저장소를 볼 수 있습니다. - 모듈 확장 프로그램에서 생성된 저장소는 확장 프로그램을 호스팅하는 모듈에 표시되는 모든 저장소와 더해 동일한 모듈 확장 프로그램에서 생성된 다른 모든 저장소를 볼 수 있습니다(저장소 규칙 호출에 지정된 이름을 표시 이름으로 사용).
- 이로 인해 충돌이 발생할 수 있습니다. 모듈 저장소에서 표시 이름이
foo
인 저장소를 볼 수 있고 확장 프로그램에서 지정된 이름foo
으로 저장소를 생성하는 경우 해당 확장 프로그램에서 생성된 모든 저장소에서foo
은 전자를 참조합니다.
- 이로 인해 충돌이 발생할 수 있습니다. 모듈 저장소에서 표시 이름이
- 마찬가지로 모듈 확장 프로그램의 구현 함수에서 확장 프로그램에서 만든 저장소는 생성 순서와 관계없이 속성의 겉보기 이름으로 서로 참조할 수 있습니다.
- 모듈에 표시되는 저장소와 충돌하는 경우 저장소 규칙 속성에 전달된 라벨을
Label
호출로 래핑하여 동일한 이름의 확장 프로그램 생성 저장소 대신 모듈에 표시되는 저장소를 참조하도록 할 수 있습니다.
- 모듈에 표시되는 저장소와 충돌하는 경우 저장소 규칙 속성에 전달된 라벨을
모듈 확장 프로그램 저장소 재정의 및 삽입
루트 모듈은 override_repo
및 inject_repo
를 사용하여 모듈 확장 프로그램 저장소를 재정의하거나 삽입할 수 있습니다.
예: rules_java
의 java_tools
를 벤더형 사본으로 대체
# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
name = "my_java_tools",
path = "vendor/java_tools",
)
bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
예: 시스템 zlib 대신 @zlib
에 종속되도록 Go 종속 항목 패치
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
patches = [
"//patches:my_module_zlib.patch",
],
path = "example.com/my_module",
)
use_repo(go_deps, ...)
inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
go_binary(
name = "my_module",
importpath = "example.com/my_module",
srcs = ["my_module.go"],
- copts = ["-lz"],
+ cdeps = ["@zlib"],
)
권장사항
이 섹션에서는 확장 프로그램을 간편하게 사용하고 유지 관리할 수 있으며 시간이 지남에 따라 변경사항에 잘 적응할 수 있도록 작성할 때의 권장사항을 설명합니다.
각 확장 프로그램을 별도의 파일에 넣기
확장 프로그램이 다른 파일에 있으면 한 확장 프로그램이 다른 확장 프로그램에서 생성한 저장소를 로드할 수 있습니다. 이 기능을 사용하지 않더라도 나중에 필요할 수 있으므로 별도의 파일에 저장하는 것이 가장 좋습니다. 확장 프로그램의 ID는 파일을 기반으로 하므로 나중에 확장 프로그램을 다른 파일로 이동하면 공개 API가 변경되며 이는 사용자에게 하위 호환되지 않는 변경사항입니다.
재현성 지정
확장 프로그램이 동일한 입력(확장 프로그램 태그, 읽는 파일 등)을 고려할 때 항상 동일한 저장소를 정의하고 특히 체크섬으로 보호되지 않는 다운로드를 사용하지 않는 경우 reproducible = True
와 함께 extension_metadata
를 반환하는 것이 좋습니다. 이렇게 하면 Bazel이 잠금 파일에 쓸 때 이 확장 프로그램을 건너뛸 수 있습니다.
운영체제 및 아키텍처 지정
확장 프로그램이 운영체제 또는 아키텍처 유형을 사용하는 경우 os_dependent
및 arch_dependent
불리언 속성을 사용하여 확장 프로그램 정의에서 이를 표시해야 합니다. 이렇게 하면 두 가지 중 하나가 변경되면 Bazel에서 재평가의 필요성을 인식합니다.
이러한 호스트 종속 항목으로 인해 이 확장 프로그램의 잠금 파일 항목을 유지하기가 더 어려워지므로 가능하면 확장 프로그램을 재현 가능으로 표시하는 것이 좋습니다.
루트 모듈만 저장소 이름에 직접 영향을 주어야 합니다.
확장 프로그램이 저장소를 만들면 확장 프로그램의 네임스페이스 내에 저장소가 생성됩니다. 즉, 서로 다른 모듈에서 동일한 확장자를 사용하고 동일한 이름의 저장소를 만들면 충돌이 발생할 수 있습니다. 이는 저장소 규칙의 name
값으로 전달되는 name
인수가 있는 모듈 확장 프로그램의 tag_class
로 나타나는 경우가 많습니다.
예를 들어 루트 모듈 A
가 모듈 B
에 종속된다고 가정해 보겠습니다. 두 모듈 모두 mylang
모듈에 종속됩니다. A
와 B
가 모두 mylang.toolchain(name="foo")
를 호출하면 둘 다 mylang
모듈 내에 foo
라는 저장소를 만들려고 시도하여 오류가 발생합니다.
이를 방지하려면 저장소 이름을 직접 설정하는 기능을 삭제하거나 루트 모듈만 이를 허용하도록 합니다. 루트 모듈에 이 기능을 허용해도 괜찮습니다. 아무것도 루트 모듈에 종속되지 않으므로 다른 모듈에서 충돌하는 이름을 만드는 것에 대해 걱정할 필요가 없기 때문입니다.