모듈 확장 프로그램

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

repo 규칙과 마찬가지로 .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` 함수를 사용하여 repo 규칙과 유사하게 모듈 확장 프로그램을 정의할 수 있습니다.module_extension 그러나 repo 규칙에는 여러 속성이 있는 반면 모듈 확장 프로그램에는 각각 여러 속성이 있는 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 객체를 가져온다는 점을 제외하고 repo 규칙의 구현 함수와 유사합니다. 그런 다음 구현 함수는 repo 규칙을 호출하여 저장소를 생성합니다.

# @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이 MODULE.bazel 잠금 파일을 쓸 때 이 확장 프로그램을 건너뛸 수 있으므로 잠금 파일을 작게 유지하고 병합 충돌의 가능성을 줄일 수 있습니다. Bazel은 서버 재시작 간에 유지되는 방식으로 재현 가능한 확장 프로그램의 결과를 계속 캐시하므로 장기 실행 확장 프로그램도 성능 저하 없이 재현 가능한 것으로 표시될 수 있습니다.

확장 프로그램이 빌드 외부에서 가져온 효과적으로 변경 불가능한 데이터(일반적으로 네트워크에서 가져옴)에 의존하지만 다운로드를 보호하는 데 사용할 수 있는 체크섬이 없는 경우 facts 매개변수를 사용하여 이러한 데이터를 영구적으로 기록하고 확장 프로그램을 재현 가능하게 만드는 것이 좋습니다.extension_metadata facts는 항상 잠금 파일에 유지되고 module_ctxfacts 필드를 통해 확장 프로그램의 향후 평가에 사용할 수 있는 문자열 키와 임의의 JSON과 유사한 Starlark 값이 있는 딕셔너리여야 합니다.

facts는 모듈 확장 프로그램의 코드가 변경되더라도 무효화되지 않으므로 facts의 구조가 변경되는 경우를 처리할 준비를 하세요. Bazel은 또한 동일한 확장 프로그램의 두 가지 평가에서 생성된 두 개의 서로 다른 facts 딕셔너리를 얕게 병합할 수 있다고 가정합니다 (즉, 두 딕셔너리에 | 연산자를 사용하는 것처럼 ). 이는 module_ctx.facts 항목 열거를 지원하지 않고 키별 조회를 지원하지 않음으로써 부분적으로 적용됩니다.

facts 사용의 예는 일부 SDK의 버전 번호에서 해당 버전의 다운로드 URL과 체크섬을 포함하는 객체로의 매핑을 기록하는 것입니다. 확장 프로그램이 처음 평가될 때 네트워크에서 이 매핑 을 가져올 수 있지만 나중에 평가할 때는 facts 의 매핑을 사용하여 네트워크 요청을 피할 수 있습니다.

운영체제 및 아키텍처에 대한 종속 항목 지정

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

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

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

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

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

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