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 repo kurallarını çağırarak modül sistemini genişletmesine olanak tanır. Bu uzantılar, dosya G/Ç'si gerçekleştirme, ağ istekleri gönderme gibi işlemleri yapmalarını sağlayan, depo kurallarına benzer özelliklere sahiptir. Bu sistemler, Bazel modüllerinden oluşturulan bağımlılık grafiğine uymakla birlikte Bazel'in diğer paket yönetim sistemleriyle etkileşim kurmasına olanak tanır.

Modül uzantılarını, depo kuralları gibi .bzl dosyalarında 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 modülde uzantı kullanmak için önce uzantıyı barındıran modüle bir bazel_dep ekleyin, ardından kapsam içine almak için yerleşik use_extension işlevini çağırın. Aşağıdaki örneği ele alalım: 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 işlevinin dönüş 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 için:

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ı tarafından oluşturulan depoları mevcut modülün kapsamına dahil etmek için use_repo yönergesini kullanın.

use_repo(maven, "maven")

Uzantı tarafından oluşturulan depolar, uzantının API'sinin bir parçasıdır. Bu örnekte, "maven" modül uzantısı maven adlı bir depo oluşturmayı vaat ediyor. Yukarıdaki bildirimle, uzantı, @maven//:org_junit_junit gibi etiketleri "maven" uzantısı tarafından oluşturulan depoya yönlendirecek şekilde doğru şekilde çözümler.

Uzantı tanımı

Modül uzantılarını depo kurallarına benzer şekilde, module_extension işlevini kullanarak tanımlayabilirsiniz. Ancak depo kuralları çeşitli özelliklere sahipken modül uzantıları, her biri çeşitli özelliklere sahip olan tag_classes içerir. Etiket sınıfları, bu uzantı tarafından kullanılan etiketlerin şemalarını 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 bildirimler, maven.install ve maven.artifact etiketlerinin belirtilen özellik şeması kullanılarak belirtilebileceğini gösterir.

Modül uzantılarının uygulama işlevi, uzantıyı kullanan tüm modüllere ve ilgili tüm etiketlere erişim izni veren bir module_ctx nesnesi alması dışında, depo kurallarına benzer. Uygulama işlevi, depoları 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ı ile tanımlanır:

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

Bir uzantıyı farklı bir .bzl dosyasından yeniden dışa aktarmak, uzantıya yeni bir kimlik verir. Uzantının her iki sürümü de geçişli modül grafiğinde kullanılırsa ayrı ayrı değerlendirilir ve yalnızca söz konusu kimlikle ilişkili etiketleri görür.

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

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

Uzantılar tarafından oluşturulan depoların module_repo_canonical_name+extension_name+repo_name biçiminde standart adları vardır. Canonical name biçiminin, bağımlı olmanız gereken bir API olmadığını ve herhangi bir zamanda değişebileceğini unutmayın.

Bu adlandırma politikası, her uzantının kendi "repo ad alanına" sahip olduğu anlamına gelir. İki farklı uzantı, çakışma riski olmadan aynı ada sahip bir depo tanımlayabilir. Bu, repository_ctx.name komutunun, depo kuralı çağrısında belirtilen adla aynı olmayan deponun standart adını bildirdiği anlamına da gelir.

Modül uzantıları tarafından oluşturulan depoları göz önünde bulundurarak, çeşitli depo görünürlüğü kuralları vardır:

  • Bir Bazel modülü deposu, MODULE.bazel dosyasında bazel_dep ve use_repo aracılığıyla tanıtılan tüm depoları görebilir.
  • Bir modül uzantısı tarafından oluşturulan depo, uzantıyı barındıran modülün görebildiği tüm depoların yanı sıra aynı modül uzantısı tarafından oluşturulan diğer tüm depoları (depo kuralı çağrılarında belirtilen adları görünen adları olarak kullanarak) görebilir.
    • Bu durum çakışmaya neden olabilir. Modül deposu, foo adlı bir depo görebiliyorsa ve uzantı, belirtilen foo adlı bir depo oluşturuyorsa bu uzantı tarafından oluşturulan tüm depolar için foo, eski depoyu ifade eder.
  • Benzer şekilde, bir modül uzantısının uygulama işlevinde, uzantı tarafından oluşturulan depolar, oluşturulma sırasından bağımsız olarak, özelliklerdeki görünen adlarıyla birbirlerine başvurabilir.
    • Modül tarafından görülebilen bir depoyla çakışma olması durumunda, depo kuralı özelliklerine iletilen etiketler, aynı ada sahip uzantı tarafından oluşturulan depo yerine modül tarafından görülebilen depoyu referans aldıklarından emin olmak için Label çağrısına sarmalanabilir.

Modül uzantısı depolarını geçersiz kılma ve 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'nın java_tools ürününü tedarikçi tarafından sağlanan bir kopya ile 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: Bir Go bağımlılığını, sistem zlib'i yerine @zlib'ye bağlı olacak şekilde düzeltme

# 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ıları kolayca kullanılabilecek, bakımı yapılabilecek ve zaman içindeki değişikliklere iyi uyum sağlayacak şekilde yazmayla ilgili en iyi uygulamalar açıklanmaktadır.

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

Uzantılar farklı dosyalarda olduğunda bir uzantının başka bir uzantı tarafından oluşturulan depoları yüklemesine izin verilir. Bu işlevi kullanmasanız bile daha sonra ihtiyacınız olabileceği için bunları ayrı dosyalara yerleştirmeniz önerilir. Bunun nedeni, uzantının kimliğinin dosyasına dayanmasıdır. Bu nedenle, uzantıyı daha sonra başka bir dosyaya taşımak, herkese açık API'nizi değiştirir ve kullanıcılarınız için geriye dönük uyumsuz bir değişiklik olur.

Tekrarlanabilirliği belirtme ve gerçekleri kullanma

Uzantınız, aynı girişler (uzantı etiketleri, okuduğu dosyalar vb.) verildiğinde her zaman aynı depoları tanımlıyorsa ve özellikle sağlama toplamı ile korunmayan herhangi bir indirme işlemine dayanmıyorsa reproducible = True ile extension_metadata değerini döndürmeyi düşünebilirsiniz. Bu, Bazel'in MODULE.bazel kilit dosyasına yazarken bu uzantıyı atlamasına olanak tanır. Böylece kilit dosyası küçük tutulur ve birleştirme çakışması olasılığı azalır. Bazel'in, tekrarlanabilir uzantıların sonuçlarını sunucu yeniden başlatıldığında kalıcı olacak şekilde önbelleğe aldığını unutmayın. Bu nedenle, uzun süren bir uzantı bile performans cezası olmadan tekrarlanabilir olarak işaretlenebilir.

Uzantınız, derleme dışında (en yaygın olarak ağdan) elde edilen ve etkili bir şekilde değişmez olan verilere dayanıyorsa ancak indirmeyi korumak için bir sağlama toplamı kullanılamıyorsa bu tür verileri kalıcı olarak kaydetmek ve böylece uzantınızın yeniden üretilebilir olmasını sağlamak için extension_metadata öğesinin facts parametresini kullanmayı düşünebilirsiniz. facts, her zaman kilit dosyasında kalıcı hale getirilen ve module_ctx öğesinin facts alanı aracılığıyla uzantının gelecekteki değerlendirmelerinde kullanılabilen, dize anahtarlara ve rastgele JSON benzeri Starlark değerlerine sahip bir sözlük olmalıdır.

facts, modül uzantınızın kodu değişse bile geçersiz kılınmaz. Bu nedenle, facts yapısının değiştiği durumu işlemeye hazır olun. Bazel ayrıca aynı uzantının iki farklı değerlendirmesiyle oluşturulan iki farklı facts sözlüğün yüzeysel olarak birleştirilebileceğini (ör. iki sözlükte | operatörü kullanılıyormuş gibi) varsayar. Bu durum, module_ctx.facts tarafından kısmen uygulanır. Girişlerinin numaralandırılmasını desteklemez, yalnızca anahtara göre aramaları destekler.

facts kullanımına örnek olarak, bazı SDK'ların sürüm numaralarından bu sürümün indirme URL'sini ve sağlama toplamını içeren bir nesneye eşleme kaydetmek verilebilir. Uzantı ilk kez değerlendirildiğinde bu eşlemeyi ağdan getirebilir ancak daha sonraki değerlendirmelerde ağ isteklerini önlemek için facts eşlemesini kullanabilir.

İşletim sistemine ve mimariye bağımlılığı belirtme

Uzantı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 belirttiğinizden emin olun. Bu, her ikisinde de değişiklik olması durumunda Bazel'in yeniden değerlendirme yapması gerektiğini anlamasını sağlar.

Bu tür bir ana makine bağımlılığı, bu uzantının kilit dosyası girişini korumayı zorlaştırdığından mümkünse uzantıyı yeniden üretilebilir olarak işaretlemeyi düşünebilirsiniz.

Yalnızca kök modül, doğrudan depo adlarını etkilemelidir.

Uzantılar tarafından oluşturulan depoların, uzantının ad alanında oluşturulduğunu unutmayın. Bu, farklı modüller aynı uzantıyı kullanırsa ve aynı ada sahip bir depo oluşturursa çakışmaların meydana gelebileceği anlamına gelir. Bu durum genellikle bir modül uzantısının tag_class, bir depo kuralının name değeri olarak iletilen bir name bağımsız değişkenine sahip olması şeklinde kendini gösterir.

Örneğin, kök modül A, modül B'e bağlı olsun. Her iki modül de mylang modülüne bağlıdır. Hem A hem de B, mylang.toolchain(name="foo") işlevini çağırırsa her ikisi de mylang modülünde foo adlı bir depo oluşturmaya çalışır ve hata oluşur.

Bunu önlemek için ya doğrudan depo adını ayarlama özelliğini kaldırın ya da yalnızca kök modülün bunu yapmasına izin verin. Kök modülün bu özelliği kullanmasına izin verebilirsiniz. Çünkü hiçbir şey kök modüle bağlı olmayacağından başka bir modülün çakışan bir ad oluşturması konusunda endişelenmenize gerek kalmaz.