모듈 확장 프로그램

모듈 확장 프로그램을 사용하면 사용자가 종속 항목 그래프의 모듈에서 입력 데이터 를 읽고, 종속 항목을 해결하는 데 필요한 로직을 실행하고, 마지막으로 저장소 규칙을 호출하여 저장소 를 만들어 모듈 시스템을 확장할 수 있습니다. 이러한 확장 프로그램은 저장소 규칙과 유사한 기능을 제공하므로 파일 I/O를 실행하고, 네트워크 요청을 전송하는 등의 작업을 실행할 수 있습니다. 특히 Bazel 모듈로 빌드된 종속 항목 그래프를 준수하면서 Bazel이 다른 패키지 관리 시스템과 상호작용할 수 있도록 지원합니다.

저장소 규칙과 마찬가지로 .bzl 파일에서 모듈 확장 프로그램을 정의할 수 있습니다. 직접 호출되지 않고 각 모듈은 확장 프로그램이 읽을 수 있는 태그라는 데이터 조각을 지정합니다. Bazel은 확장 프로그램을 평가하기 전에 모듈 해결을 실행합니다. 확장 프로그램은 전체 종속 항목 그래프에서 확장 프로그램에 속한 모든 태그를 읽습니다.

확장 프로그램 사용

확장 프로그램은 Bazel 모듈 자체에서 호스팅됩니다. 모듈에서 확장 프로그램을 사용하려면 먼저 확장 프로그램을 호스팅하는 모듈에 bazel_dep을 추가한 다음 use_extension 기본 제공 함수를 호출하여 범위 내로 가져옵니다. 다음 예를 참고하세요. MODULE.bazel 파일의 스니펫으로, rules_jvm_external 모듈에 정의된 "maven" 확장 프로그램을 사용합니다.

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

이렇게 하면 use_extension의 반환 값이 변수에 바인딩되므로 사용자가 점 구문을 사용하여 확장 프로그램의 태그를 지정할 수 있습니다. 태그는 확장 프로그램 정의에 지정된 상응하는 태그 클래스 에 의해 정의된 스키마를 따라야 합니다. 확장 프로그램 정의 일부 maven.installmaven.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_classes가 있으며, 각각 여러 속성이 있습니다. 태그 클래스는 이 확장 프로그램에서 사용하는 태그의 스키마를 정의합니다. 예를 들어 위의 '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.installmaven.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

모듈 확장 프로그램은 이름과 .bzl 파일로 식별됩니다. use_extension 호출에 표시되는 다음 예에서 확장 프로그램 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 모듈 저장소는 MODULE.bazel 파일 에서 bazel_depuse_repo를 통해 도입된 모든 저장소를 볼 수 있습니다.
  • 모듈 확장 프로그램에서 생성된 저장소는 확장 프로그램을 호스팅하는 모듈에 표시되는 모든 저장소 뿐만 아니라 동일한 모듈 확장 프로그램에서 생성된 다른 모든 저장소 (저장소 규칙 호출에 지정된 이름을 표시 이름으로 사용)를 볼 수 있습니다.
    • 이로 인해 충돌이 발생할 수 있습니다. 모듈 저장소에서 표시 이름이 foo인 저장소를 볼 수 있고 확장 프로그램에서 지정된 이름이 foo인 저장소를 생성하는 경우 해당 확장 프로그램에서 생성된 모든 저장소에 대해 foo는 전자를 가리킵니다.
  • 마찬가지로 모듈 확장 프로그램의 구현 함수에서 확장 프로그램에서 생성된 저장소는 생성 순서와 관계없이 속성의 표시 이름으로 서로를 참조할 수 있습니다.
    • 모듈에 표시되는 저장소와 충돌하는 경우 저장소 규칙 속성에 전달된 라벨 을 Label 호출로 래핑하여 확장 프로그램에서 생성된 동일한 이름의 저장소 대신 모듈에 표시되는 저장소 를 참조하도록 할 수 있습니다.

모듈 확장 프로그램 저장소 재정의 및 삽입

루트 모듈은 override_repoinject_repo를 사용하여 모듈 확장 프로그램 저장소를 재정의하거나 삽입할 수 있습니다.

예: rules_java's 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가 변경되고 사용자에게 이전 버전과 호환되지 않는 변경사항이 발생하기 때문입니다.

재현 가능성 지정

확장 프로그램이 항상 동일한 입력 (확장 프로그램 태그, 읽는 파일 등)을 지정하고 특히 체크섬으로 보호되지 않는 다운로드에 의존하지 않는 경우 어떤 다운로드도 체크섬으로 보호되지 않는 경우 다음과 함께 반환하는 것이 좋습니다. extension_metadata reproducible = True. 이렇게 하면 Bazel이 잠금 파일에 쓸 때 이 확장 프로그램을 건너뛸 수 있습니다.

운영체제 및 아키텍처 지정

확장 프로그램이 운영체제 또는 아키텍처 유형에 의존하는 경우 확장 프로그램 정의에 이를 표시해야 합니다. os_dependentarch_dependent 불리언 속성을 사용하여 이렇게 하면 Bazel이 둘 중 하나가 변경될 경우 재평가의 필요성을 인식할 수 있습니다.

이러한 호스트 종속 항목은 이 확장 프로그램의 잠금 파일 항목을 유지보수하기 더 어렵게 만들므로 가능한 경우 확장 프로그램을 재현 가능으로 표시하는 것이 좋습니다.

루트 모듈만 저장소 이름에 직접 영향을 미쳐야 함

확장 프로그램이 저장소를 만들 때 저장소는 확장 프로그램의 네임스페이스 내에 생성됩니다. 즉, 서로 다른 모듈이 동일한 확장 프로그램을 사용하고 동일한 이름으로 저장소를 생성하는 경우 충돌이 발생할 수 있습니다. 이는 모듈 확장 프로그램의 tag_class에 저장소 규칙의 name 값으로 전달되는 name 인수가 있는 것으로 자주 나타납니다.

예를 들어 루트 모듈 A이 모듈 B에 종속된다고 가정해 보겠습니다. 두 모듈 모두 mylang 모듈에 종속됩니다. AB가 모두 mylang.toolchain(name="foo")를 호출하면 둘 다 foo라는 저장소를 mylang 모듈 내에 만들려고 시도하고 오류가 발생합니다.

이를 방지하려면 저장소 이름을 직접 설정하는 기능을 삭제하거나 루트 모듈만 이 작업을 실행하도록 허용합니다. 루트 모듈은 아무것도 종속되지 않으므로 충돌하는 이름을 만드는 다른 모듈에 대해 걱정할 필요가 없으므로 루트 모듈에 이 기능을 허용해도 됩니다.