Modül uzantıları

Modül uzantıları, kullanıcıların bağımlılık grafiğindeki modüllerden giriş verilerini okuyarak, bağımlılıkları çözmek için gerekli mantığı uygulayarak ve son olarak kod deposu kurallarını çağırarak depolar oluşturarak modül sistemini genişletmesine olanak tanır. Bu uzantılar, depo kurallarına benzer özelliklere sahiptir. Bu özellikler, dosya G/Ç işlemlerini gerçekleştirmelerine, ağ istekleri göndermelerine vb. olanak tanır. Ayrıca, Bazel'in diğer paket yönetimi sistemleriyle etkileşim kurmasına ve Bazel modüllerinden oluşturulan bağımlılık grafiğine uymasına olanak tanır.

Depo kuralları gibi .bzl dosyalarında modül uzantılarını tanımlayabilirsiniz. Doğrudan çağrılmazlar. Bunun yerine, her modül uzantıların okuması için etiket adı verilen veri parçalarını belirtir. Bazel, uzantıları değerlendirmeden önce modül çözümlemesini çalıştırır. Uzantı, bağımlılık grafiğinin tamamında kendisine ait tüm etiketleri okur.

Uzantı kullanımı

Uzantılar Bazel modüllerinde barındırılır. Bir uzantıyı modülde kullanmak için önce uzantıyı barındıran modüle bir bazel_dep ekleyin, ardından kapsama almak için use_extension yerleşik işlevini çağırın. Şu örneği inceleyelim: rules_jvm_external modülünde tanımlanan "maven" uzantısını kullanmak için MODULE.bazel dosyasından bir snippet:

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

Bu, use_extension değerini bir değişkene bağlar. Böylece kullanıcı, uzantı için etiketleri belirtmek üzere nokta söz dizimini kullanabilir. Etiketler, uzantı tanımında belirtilen ilgili etiket sınıfları tarafından tanımlanan şemaya uymalıdır. Bazı maven.install ve maven.artifact etiketlerini belirten bir örnek:

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"])

Uzantının oluşturduğu depoları mevcut modülün kapsamına almak için use_repo yönergesini kullanın.

use_repo(maven, "maven")

Bir uzantı tarafından oluşturulan depolar, bu uzantının API'sinin parçasıdır. Bu örnekte, "maven" modül uzantısı maven adlı bir depo oluşturmayı vaat etmektedir. Yukarıdaki beyanla uzantı, "maven" uzantısı tarafından oluşturulan deposu işaretlemek için @maven//:org_junit_junit gibi etiketleri doğru şekilde çözer.

Uzantı tanımı

Modül uzantılarını, module_extension işlevini kullanarak depo kurallarına benzer şekilde tanımlayabilirsiniz. Bununla birlikte, depo kuralları birkaç özelliğe sahipken modül uzantılarında her birinin bir dizi özelliği olan tag_class özellikleri bulunur. Etiket sınıfları, bu uzantı tarafından kullanılan etiketler için şemaları tanımlar. Örneğin, yukarıdaki "maven" uzantısı şu şekilde tanımlanabilir:

# @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},
)

Bu beyanlar, maven.install ve maven.artifact etiketlerinin belirtilen özellik şeması kullanılarak belirtilebileceğini gösterir.

Modül uzantılarının uygulama işlevi, kod deposu kurallarının uygulama işlevine benzer. Ancak bu işlev, module_ctx nesnesini alır. Bu nesne, uzantıyı ve ilgili tüm etiketleri kullanan tüm modüllere erişim izni verir. Ardından uygulama işlevi, depo oluşturmak için depo kurallarını çağırır.

# @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)

Uzantı kimliği

Modül uzantıları, use_extension çağrısında görünen ad ve .bzl dosyasıyla tanımlanır. Aşağıdaki örnekte maven uzantısı, .bzl dosyası @rules_jvm_external//:extension.bzl ve maven adıyla tanımlanmıştır:

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

Bir uzantı farklı bir .bzl dosyasından yeniden dışa aktarıldığında ona yeni bir kimlik verilir. Geçişli modül grafiğinde uzantının her iki sürümü de kullanılıyorsa bunlar ayrı olarak değerlendirilir ve yalnızca söz konusu kimlikle ilişkilendirilen etiketleri görür.

Uzantı yazarı olarak, kullanıcıların modül uzantınızı yalnızca tek bir .bzl dosyasından kullanacağından emin olmanız gerekir.

Depo adları ve görünürlük

Uzantılar tarafından oluşturulan kod depoları module_repo_canonical_name~extension_name~repo_name biçiminde standart adlara sahiptir. Kök modülde barındırılan uzantılarda module_repo_canonical_name kısmı _main dizesiyle değiştirilir. Standart ad biçiminin kullanmanız gereken bir API olmadığını unutmayın. Bu biçim herhangi bir zamanda değiştirilebilir.

Bu adlandırma politikası, her uzantının kendi "depo ad alanına" sahip olduğu anlamına gelir. İki farklı uzantı, çakışma riski olmadan aynı ada sahip bir depo tanımlayabilir. Ayrıca, repository_ctx.name komutu deponun standart adını bildirir. Bu ad, depo kuralı çağrısında belirtilen adla aynı değildir.

Modül uzantıları tarafından oluşturulan depoları dikkate alan birkaç depo görünürlük kuralı vardır:

  • Bazel modül deposu, bazel_dep ve use_repo aracılığıyla MODULE.bazel dosyasında sunulan tüm depoları görebilir.
  • Bir modül uzantısı tarafından oluşturulan bir depo, uzantıyı barındıran modülün görebildiği tüm depoları ve aynı zamanda aynı modül uzantısı tarafından oluşturulan diğer tüm depoları görebilir (repo kural çağrılarında belirtilen adları görünür adları olarak kullanır).
    • Bu durum, çakışmaya neden olabilir. Modül deposu foo görünen adına sahip bir depo görebiliyorsa ve uzantı, foo adlı bir depo oluşturuyorsa bu uzantı tarafından oluşturulan tüm depolar için foo, önceki depoyu ifade eder.
  • Benzer şekilde, bir modül uzantısının uygulama işlevinde, uzantı tarafından oluşturulan depolar, oluşturuldukları sıraya bakılmaksızın, özelliklerdeki görünen adlarıyla birbirlerine referans verebilir.
    • Modül tarafından görülebilen bir depoyla çakışan durumlarda, depo kuralı özelliklerine iletilen etiketler, aynı ada sahip uzantı tarafından oluşturulan depo yerine modül tarafından görülebilen depoya atıfta bulunmaları için Label çağrısına sarmalanabilir.

Modül uzantısı depolarını geçersiz kılma ve bunlara kod ekleme

Kök modül, modül uzantısı depolarını geçersiz kılmak veya eklemek için override_repo ve inject_repo kullanabilir.

Örnek: rules_java'nin java_tools öğesini tedarikçi firma tarafından sağlanan bir kopyayla değiştirme

# 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")

Örnek: Sistem zlib yerine @zlib bağımlılığı için bir Go bağımlılığına yama uygulayın

# 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"],
 )

En iyi uygulamalar

Bu bölümde uzantı yazarken kullanılan en iyi uygulamalar açıklanmaktadır. Bu uygulamalar kolayca kullanılabilir, korunabilir ve zaman içindeki değişikliklere iyi uyum sağlar.

Her uzantıyı ayrı bir dosyaya yerleştirin

Uzantılar farklı bir dosyada yer aldığında, bir uzantının başka bir uzantı tarafından oluşturulan depoları yüklemesine olanak tanır. Bu işlevi kullanmıyor olsanız bile, daha sonra ihtiyaç duymanız ihtimaline karşı bunları ayrı dosyalara yerleştirmek en iyi seçenektir. Bunun nedeni, uzantının kimliğinin kendi dosyasına dayalı olmasıdır. Dolayısıyla, uzantıyı daha sonra başka bir dosyaya taşımak genel API'nizi değiştirir ve kullanıcılarınız için geriye dönük olarak uyumsuz bir değişikliktir.

Tekrarlanabilirliği belirtme

Uzantı, aynı girişler (uzantı etiketleri, okuduğu dosyalar vb.) verildiğinde her zaman aynı depoları tanımlıyorsa ve özellikle de sağlama toplamıyla korunmayan indirme işlemlerini kullanmıyorsa reproducible = True ile extension_metadata döndürmeyi düşünebilirsiniz. Bu, kilit dosyasına yazarken Bazel'in bu uzantıyı atlamasına olanak tanır.

İşletim sistemini ve mimariyi belirtin

Uzantılarınız işletim sistemine veya mimari türüne bağlıysa os_dependent ve arch_dependent Boole özelliklerini kullanarak bunu uzantı tanımında belirtmeyi unutmayın. Böylece, ikisinden birinde değişiklik olursa Bazel’in yeniden değerlendirme gerektiğini fark etmesini sağlayabilirsiniz.

Barındırıcıya olan bu tür bir bağımlılık, bu uzantının kilit dosyası girişinin korunmasını zorlaştırdığından, mümkünse uzantıyı yeniden üretilebilir olarak işaretleyebilirsiniz.

Kod deposu adlarını yalnızca kök modül doğrudan etkilemelidir

Bir uzantı depolama alanı oluşturduğunda bu depolama alanlarının uzantının ad alanında oluşturulduğunu unutmayın. Bu, farklı modüller aynı uzantıyı kullanıp aynı ada sahip bir depo oluşturması durumunda çakışmalar olabileceği anlamına gelir. Bu durum genellikle modül uzantısının tag_class öğesinin, depo kuralının name değeri olarak iletilen name bağımsız değişkenine sahip olması şeklinde kendini gösterir.

Örneğin, kök modülün (A) B modülüne bağlı olduğunu varsayalım. Her iki modül de mylang modülüne bağlıdır. Hem A hem de B, mylang.toolchain(name="foo") çağrısı yaparsa mylang modülünde foo adlı bir depo oluşturmaya çalışır ve hata oluşur.

Bunu önlemek için depo adını doğrudan ayarlama özelliğini kaldırın veya yalnızca kök modülün bunu yapmasına izin verin. Kök modülün bu özelliğe sahip olmasında sakınca yoktur çünkü hiçbir şey ona bağlı değildir. Bu nedenle, başka bir modülün çakışan bir ad oluşturması konusunda endişelenmenize gerek yoktur.