Harici bağımlılıkları Bzlmod ile yönetin

Bzlmod, Bazel 5.0'da kullanıma sunulan yeni harici bağımlılık sisteminin kod adıdır. Bu sistem, eski sistemin kademeli olarak düzeltilmesi mümkün olmayan çeşitli sorunlarını gidermek için kullanıma sunulmuştur. Daha fazla bilgi için orijinal tasarım belgesinin Sorun Açıklaması bölümüne bakın.

Bazel 5.0'da Bzlmod varsayılan olarak etkin değildir. Aşağıdakilerin geçerli olması için --experimental_enable_bzlmod işareti belirtilmelidir. İşaret adından da anlaşılacağı gibi bu özellik şu anda deneyseldir. Özellik resmi olarak kullanıma sunulana kadar API'ler ve davranışlar değişebilir.

Projenizi Bzlmod'a taşımak için Bzlmod Taşıma Rehberi'ni inceleyin. Örnek Bzlmod kullanımlarını examples deposunda da bulabilirsiniz.

Bazel Modülleri

Eski WORKSPACE tabanlı harici bağımlılık sistemi, depo kuralları (veya repo kuralları) aracılığıyla oluşturulan depolar (veya repolar) etrafında şekillenir. Yeni sistemde depolar hâlâ önemli bir kavram olsa da modüller, bağımlılığın temel birimleridir.

Modül, temelde birden fazla sürümü olabilen bir Bazel projesidir. Bu sürümlerin her biri, bağlı olduğu diğer modüllerle ilgili meta veriler yayınlar. Bu, diğer bağımlılık yönetimi sistemlerindeki benzer kavramlara (ör. Maven artifact, npm package, Cargo crate, Go module) benzer.

Bir modül, WORKSPACE içindeki belirli URL'ler yerine name ve version çiftlerini kullanarak bağımlılıklarını belirtir. Bağımlılıklar daha sonra Bazel kayıt defterinde (varsayılan olarak Bazel Merkezi Kayıt Defteri) aranır. Çalışma alanınızda her modül bir depoya dönüştürülür.

MODULE.bazel

Her modülün her sürümünde, bağımlılıklarını ve diğer meta verilerini bildiren bir MODULE.bazel dosyası bulunur. Temel bir örnek:

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

MODULE.bazel dosyası, çalışma alanı dizininin kök dizininde (WORKSPACE dosyasının yanında) bulunmalıdır. WORKSPACE dosyasının aksine, geçişli bağımlılıklarınızı belirtmeniz gerekmez. Bunun yerine yalnızca doğrudan bağımlılıkları belirtmeniz gerekir. Bağımlılıklarınızın MODULE.bazel dosyaları, geçişli bağımlılıkları otomatik olarak bulmak için işlenir.

MODULE.bazel dosyası, herhangi bir kontrol akışını desteklemediği için BUILD dosyalarına benzer. Ayrıca load ifadelerini de yasaklar. Desteklenen yönergeler şunlardır: MODULE.bazel

Sürüm biçimi

Bazel'in çeşitli bir ekosistemi vardır ve projelerde çeşitli sürüm oluşturma şemaları kullanılır. En popüler olanı SemVer olsa da Abseil gibi farklı şemalar kullanan önemli projeler de vardır. Abseil'in sürümleri tarih tabanlıdır (ör. 20210324.2).

Bu nedenle Bzlmod, SemVer spesifikasyonunun daha esnek bir sürümünü kullanır. Farklılıklar şunlardır:

  • SemVer, sürümün "yayın" bölümünün 3 segmentten oluşması gerektiğini belirtir: MAJOR.MINOR.PATCH. Bazel'de bu şart gevşetilir ve herhangi bir sayıda segmente izin verilir.
  • SemVer'de "yayın" bölümündeki segmentlerin her biri yalnızca rakamlardan oluşmalıdır. Bazel'de bu kısıtlama, harflere de izin verecek şekilde gevşetilir ve karşılaştırma semantiği, "ön sürüm" bölümündeki "tanımlayıcılarla" eşleşir.
  • Ayrıca, ana, alt ve yama sürümü artışlarının semantiği zorunlu tutulmaz. (Ancak geriye dönük uyumluluğu nasıl gösterdiğimizle ilgili ayrıntılar için uyumluluk düzeyine bakın.)

Geçerli herhangi bir SemVer sürümü, geçerli bir Bazel modül sürümüdür. Ayrıca, iki SemVer sürümü a ve b, Bazel modül sürümleri olarak karşılaştırıldıklarında aynı durum geçerliyse a < b karşılaştırılır.

Sürüm çözünürlüğü

Elmas bağımlılığı sorunu, sürümlendirilmiş bağımlılık yönetimi alanında temel bir sorundur. Aşağıdaki bağımlılık grafiğine sahip olduğunuzu varsayalım:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

D'nin hangi sürümü kullanılmalıdır? Bzlmod, bu sorunu çözmek için Go modül sisteminde kullanılan Minimal Version Selection (MVS) algoritmasını kullanır. MVS, bir modülün tüm yeni sürümlerinin geriye dönük olarak uyumlu olduğunu varsayar ve bu nedenle, bağımlı olan herhangi bir öğe (örneğimizde D 1.1) tarafından belirtilen en yüksek sürümü seçer. Buradaki D 1.1, şartlarımızı karşılayabilecek en küçük sürüm olduğundan bu sürüme "minimal" adı verilir. D 1.2 veya daha yeni sürümler olsa bile bunları seçmeyiz. Bu, sürüm seçiminin yüksek doğrulukta ve tekrar edilebilir olması gibi ek bir avantaj sağlar.

Sürüm çözümü, kayıt defteri tarafından değil, makinenizde yerel olarak gerçekleştirilir.

Uyumluluk düzeyi

MVS'nin geriye dönük uyumlulukla ilgili varsayımının, geriye dönük uyumlu olmayan modül sürümlerini ayrı bir modül olarak ele alması nedeniyle uygulanabilir olduğunu unutmayın. SemVer açısından bu, A 1.x ve A 2.x'in ayrı modüller olarak kabul edildiği ve çözümlenen bağımlılık grafiğinde birlikte bulunabileceği anlamına gelir. Bu da Go'daki paket yolunda ana sürümün kodlanması sayesinde mümkün olur. Böylece derleme zamanı veya bağlantı zamanı çakışmaları olmaz.

Bazel'de bu tür garantiler yoktur. Bu nedenle, geriye dönük olarak uyumsuz sürümleri tespit etmek için "ana sürüm" numarasını belirtmemiz gerekir. Bu sayıya uyumluluk düzeyi denir ve her modül sürümü tarafından module() yönergesinde belirtilir. Bu bilgiler sayesinde, çözümlenen bağımlılık grafiğinde farklı uyumluluk düzeylerine sahip aynı modülün sürümlerinin bulunduğunu tespit ettiğimizde hata verebiliriz.

Depo adları

Bazel'de her harici bağımlılığın bir depo adı vardır. Bazen aynı bağımlılık farklı depo adları aracılığıyla (örneğin, hem @io_bazel_skylib hem de @bazel_skylib, Bazel skylib anlamına gelir) veya aynı depo adı farklı projelerdeki farklı bağımlılıklar için kullanılabilir.

Bzlmod'da depolar, Bazel modülleri ve modül uzantıları tarafından oluşturulabilir. Depo adı çakışmalarını çözmek için yeni sistemde depo eşleme mekanizmasını kullanıyoruz. Burada iki önemli kavram söz konusudur:

  • Kurallı depo adı: Her depo için genel olarak benzersiz depo adı. Bu, deponun bulunduğu dizinin adı olacaktır.
    Aşağıdaki şekilde oluşturulur (Uyarı: Standart ad biçimi, bağımlı olmanız gereken bir API değildir ve herhangi bir zamanda değişebilir):

    • Bazel modülü depoları için: module_name~version
      (Örnek. @bazel_skylib~1.0.3)
    • Modül uzantısı depoları için: module_name~version~extension_name~repo_name
      (Örnek. @rules_cc~0.0.1~cc_configure~local_config_cc)
  • Görünen depo adı: Bir depodaki BUILD ve .bzl dosyalarında kullanılacak depo adı. Aynı bağımlılık, farklı depolarda farklı görünen adlara sahip olabilir.
    Şu şekilde belirlenir:

    • Bazel modülü depoları için: module_name varsayılan olarak veya bazel_dep içinde repo_name özelliği tarafından belirtilen ad.
    • Modül uzantısı depoları için: use_repo aracılığıyla tanıtılan depo adı.

Her deponun, doğrudan bağımlılıklarının depo eşleme sözlüğü vardır. Bu sözlük, görünen depo adından kanonik depo adına giden bir haritadır. Bir etiket oluştururken depo adını çözmek için depo eşlemesini kullanırız. Kanonik depo adları arasında çakışma olmadığını ve görünen depo adlarının kullanımının MODULE.bazel dosyası ayrıştırılarak bulunabileceğini unutmayın. Bu nedenle, çakışmalar diğer bağımlılıkları etkilemeden kolayca tespit edilebilir ve çözülebilir.

Katı bağımlılıklar

Yeni bağımlılık belirtme biçimi, daha katı kontroller yapmamıza olanak tanır. Özellikle, artık bir modülün yalnızca doğrudan bağımlılıklarından oluşturulan depoları kullanmasını zorunlu kılıyoruz. Bu, geçişli bağımlılık grafiğinde bir şey değiştiğinde yanlışlıkla oluşan ve hata ayıklaması zor olan bozulmaları önlemeye yardımcı olur.

Strict deps, depo eşlemeye göre uygulanır. Temel olarak, her depo için depo eşlemesi, doğrudan bağımlılıklarının tümünü içerir. Diğer depolar görünmez. Her depo için görünür bağımlılıklar aşağıdaki gibi belirlenir:

  • Bir Bazel modül deposu, MODULE.bazel dosyasında tanıtılan tüm depoları bazel_dep ve use_repo aracılığıyla görebilir.
  • Bir modül uzantısı deposu, uzantıyı sağlayan modülün tüm görünür bağımlılıklarını ve aynı modül uzantısı tarafından oluşturulan diğer tüm depoları görebilir.

Kayıtlar

Bzlmod, Bazel kayıtlarından bilgilerini isteyerek bağımlılıkları keşfeder. Bazel kayıt defteri, Bazel modüllerinin bulunduğu bir veritabanıdır. Kayıtların desteklenen tek biçimi, belirli bir biçime uyan yerel bir dizin veya statik bir HTTP sunucusu olan dizin kaydıdır. Gelecekte, bir projenin kaynağını ve geçmişini içeren git depoları olan tek modüllü kayıtlar için destek eklemeyi planlıyoruz.

Dizin kaydı

Dizin kayıt otoritesi, bir modül listesiyle ilgili bilgileri (ana sayfası, bakımını yapanlar, her sürümün MODULE.bazel dosyası ve her sürümün kaynağının nasıl alınacağı dahil) içeren yerel bir dizin veya statik bir HTTP sunucusudur. Özellikle, kaynak arşivlerin kendisini sunması gerekmez.

Dizin kaydı aşağıdaki biçimde olmalıdır:

  • /bazel_registry.json: Kayıt defterinin meta verilerini içeren bir JSON dosyası (örneğin):
    • mirrors, kaynak arşivler için kullanılacak yansıtma listesini belirtir.
    • module_base_path, source.json dosyasında local_repository türündeki modüller için temel yolu belirtir.
  • /modules: Bu kayıt defterindeki her modül için bir alt dizin içeren bir dizin.
  • /modules/$MODULE: Bu modülün her sürümü için bir alt dizin ve aşağıdaki dosyayı içeren bir dizin:
    • metadata.json: Modülle ilgili bilgileri içeren bir JSON dosyası. Aşağıdaki alanları içerir:
      • homepage: Projenin ana sayfasının URL'si.
      • maintainers: Her biri kayıt defterindeki modülün bakım sorumlusunun bilgilerine karşılık gelen bir JSON nesneleri listesi. Bunun, projenin yazarlarıyla aynı olması gerekmediğini unutmayın.
      • versions: Bu kayıt defterinde bulunacak bu modülün tüm sürümlerinin listesi.
      • yanked_versions: Bu modülün kaldırılmış sürümlerinin listesi. Bu özellik şu anda etkisizdir ancak gelecekte geri çekilen sürümler atlanır veya hata verir.
  • /modules/$MODULE/$VERSION: Aşağıdaki dosyaları içeren bir dizin:
    • MODULE.bazel: Bu modül sürümünün MODULE.bazel dosyası.
    • source.json: Bu modül sürümünün kaynağının nasıl getirileceğiyle ilgili bilgileri içeren bir JSON dosyası.
      • Varsayılan tür, aşağıdaki alanları içeren "archive"dır:
        • url: Kaynak arşivin URL'si.
        • integrity: Arşivin Alt Kaynak Bütünlüğü sağlama toplamı.
        • strip_prefix: Kaynak arşivi çıkarılırken kaldırılacak bir dizin öneki.
        • patches: Her biri çıkarılan arşive uygulanacak bir yama dosyasını adlandıran bir dizeler listesi. Yama dosyaları /modules/$MODULE/$VERSION/patches dizininde bulunur.
        • patch_strip: Unix patch'in --strip bağımsız değişkeniyle aynıdır.
      • Tür, aşağıdaki alanlarla yerel bir yol kullanacak şekilde değiştirilebilir:
        • type: local_path
        • path: Deponun yerel yolu, aşağıdaki gibi hesaplanır:
          • Yol mutlak bir yol ise olduğu gibi kullanılır.
          • Yol göreli bir yolsa ve module_base_path mutlak bir yolsa yol <module_base_path>/<path> olarak çözümlenir.
          • Hem yol hem de module_base_path göreli yolsa yol, <registry_path>/<module_base_path>/<path> olarak çözümlenir. Kayıt defteri yerel olarak barındırılmalı ve --registry=file://<registry_path> tarafından kullanılmalıdır. Aksi takdirde Bazel hata verir.
    • patches/: Yalnızca source.json "arşiv" türündeyken kullanılan, yama dosyalarını içeren isteğe bağlı bir dizin.

Bazel Central Registry

Bazel Central Registry (BCR), bcr.bazel.build adresinde bulunan bir dizin kayıt defteridir. İçeriği, GitHub deposu bazelbuild/bazel-central-registry tarafından desteklenir.

BCR, Bazel topluluğu tarafından yönetilir. Katkıda bulunanlar, çekme istekleri gönderebilir. Bazel Merkezi Kayıt Politikaları ve Prosedürleri'ne bakın.

BCR, normal bir dizin kaydının biçimine uymanın yanı sıra her modül sürümü için bir presubmit.yml dosyası (/modules/$MODULE/$VERSION/presubmit.yml) gerektirir. Bu dosya, bu modül sürümünün geçerliliğini kontrol etmek için kullanılabilecek birkaç temel derleme ve test hedefi belirtir ve BCR'deki modüller arasında birlikte çalışabilirliği sağlamak için BCR'nin CI işlem hatları tarafından kullanılır.

Kayıtları seçme

Modül isteğinde bulunulacak kayıt listesini belirtmek için tekrarlanabilir Bazel işareti --registry kullanılabilir. Böylece projenizi, bağımlılıkları üçüncü taraf veya dahili bir kayıt defterinden getirecek şekilde ayarlayabilirsiniz. Daha önceki kayıtlar önceliklidir. Kolaylık sağlaması için --registry işaretlerinin listesini projenizin .bazelrc dosyasına ekleyebilirsiniz.

Modül Uzantıları

Modül uzantıları, bağımlılık grafiğindeki modüllerden giriş verilerini okuyarak, bağımlılıkları çözmek için gerekli mantığı uygulayarak ve son olarak depo kurallarını çağırarak depolar oluşturarak modül sistemini genişletmenize olanak tanır. Günümüzdeki WORKSPACE makrolarıyla benzer işlevlere sahiptir ancak modüller ve geçişli bağımlılıklar dünyasında daha uygundur.

Modül uzantıları, depo kuralları veya WORKSPACE makroları gibi .bzl dosyalarında tanımlanır. Doğrudan çağrılmazlar. Bunun yerine, her modül, uzantıların okuması için etiket adı verilen veri parçalarını belirtebilir. Ardından, modül sürümü çözümü tamamlandıktan sonra modül uzantıları çalıştırılır. Her uzantı, modül çözümlendirmesinden sonra (ancak herhangi bir derleme gerçekleşmeden önce) bir kez çalıştırılır ve bağımlılık grafiğinin tamamında kendisine ait tüm etiketleri okuyabilir.

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

Yukarıdaki örnek bağımlılık grafiğinde, A 1.1 ve B 1.2 gibi öğeler Bazel modülleridir. Bunların her birini MODULE.bazel dosyası olarak düşünebilirsiniz. Her modül, modül uzantıları için bazı etiketler belirtebilir. Burada, "maven" uzantısı için bazı etiketler, "cargo" için ise bazı etiketler belirtilmiştir. Bu bağımlılık grafiği tamamlandığında (örneğin, B 1.2 aslında D 1.3 üzerinde bazel_dep'ye sahip olabilir ancak C nedeniyle D 1.4'ye yükseltilmiştir) "maven" uzantıları çalıştırılır ve oluşturulacak depoları belirlemek için bu bilgilerden yararlanarak tüm maven.* etiketlerini okur. Aynı durum "cargo" uzantısı için de geçerlidir.

Uzantı kullanımı

Uzantılar, Bazel modüllerinin kendisinde barındırılır. Bu nedenle, modülünüzde bir uzantı kullanmak için önce bu modüle bir bazel_dep eklemeniz ve ardından kapsam içine almak için yerleşik use_extension işlevini çağırmanız gerekir. rules_jvm_external modülünde tanımlanan varsayımsal bir "maven" uzantısını kullanmak için MODULE.bazel dosyasından alınan aşağıdaki snippet'i inceleyin:

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

Uzantıyı kapsam içine aldıktan sonra, nokta söz dizimini kullanarak bunun için etiket belirtebilirsiniz. Etiketlerin, ilgili etiket sınıfları tarafından tanımlanan şemaya uyması gerektiğini unutmayın (aşağıdaki uzantı tanımına bakın). Aşağıda, bazı maven.dep ve maven.pom etiketlerinin belirtildiği bir örnek verilmiştir.

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

Uzantı, modülünüzde kullanmak istediğiniz depolar oluşturuyorsa bunları bildirmek için use_repo yönergesini kullanın. Bu, katı bağımlılık koşulunu karşılamak ve yerel depo adı çakışmasını önlemek içindir.

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

Bir uzantı tarafından oluşturulan depolar, uzantının API'sinin bir parçasıdır. Bu nedenle, belirttiğiniz etiketlerden "maven" uzantısının "org_junit_junit" ve "com_google_guava_guava" adlı birer depo oluşturacağını anlamanız gerekir. use_repo ile bunları isteğe bağlı olarak modülünüz kapsamında yeniden adlandırabilirsiniz. Örneğin, burada "guava" olarak adlandırılmıştır.

Uzantı tanımı

Modül uzantıları, module_extension işlevi kullanılarak depo kurallarına benzer şekilde tanımlanır. Her ikisinde de bir uygulama işlevi vardır. Ancak depo kurallarında bir dizi özellik bulunurken modül uzantılarında bir dizi tag_class bulunur ve bunların her birinde bir dizi özellik vardır. Etiket sınıfları, bu uzantı tarafından kullanılan etiketlerin şemalarını tanımlar. Yukarıdaki varsayımsal "maven" uzantısı örneğimize devam edelim:

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

Bu bildirimler, yukarıda tanımlanan özellik şeması kullanılarak maven.dep ve maven.pom etiketlerinin belirtilebileceğini açıkça gösterir.

Uygulama işlevi, WORKSPACE makrosuna benzer ancak bağımlılık grafiğine ve ilgili tüm etiketlere erişim sağlayan bir module_ctx nesnesi alır. Uygulama işlevi, depoları oluşturmak için depo kurallarını çağırmalıdır:

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

Yukarıdaki örnekte, bağımlılık grafiğindeki (ctx.modules) tüm modülleri inceliyoruz. Bunların her biri, tags alanı modüldeki tüm maven.* etiketlerini gösteren bir bazel_module nesnesidir. Ardından, Maven ile iletişime geçmek ve çözümleme gerçekleştirmek için CLI yardımcı programı Coursier'i çağırırız. Son olarak, varsayımsal maven_single_jar depo kuralını kullanarak bir dizi depo oluşturmak için çözümleme sonucunu kullanırız.