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

Bzlmod, Bazel 5.0'da kullanıma sunulan yeni dış bağımlılık sisteminin kod adıdır. Eski sistemde, aşamalı olarak düzeltilemeyen bazı sorunlu noktaların ele alınması amacıyla kullanıma sunulmuştur. Daha ayrıntılı bilgi için orijinal tasarım belgesinin Sorun Bildirimi 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şaretinin belirtilmesi gerekir. Bayrak 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 Kılavuzu'ndaki talimatları uygulayın. Örnek Bzlmod kullanımlarını examples deposunda da bulabilirsiniz.

Bazel Modülleri

WORKSPACE tabanlı eski harici bağımlılık sistemi, depo kuralları (veya depo kuralları) aracılığıyla oluşturulan depolar (veya depolar) etrafında toplanmıştır. Depolar yeni sistemde hâlâ önemli bir kavram olsa da modüller temel bağımlılık birimleridir.

Modül temelde birden çok sürüme sahip olabilen bir Bazel projesidir. Bu sürümlerin her biri, bağlı olduğu diğer modüller hakkında meta veriler yayınlar. Bu, diğer bağımlılık yönetimi sistemlerindeki bilindik kavramlara benzer: Maven yapısı, npm paketi, Cargo sandığı, Go modülü vb.

Modül, WORKSPACE içindeki belirli URL'ler yerine name ve version çiftlerini kullanarak bağımlılıklarını belirtir. Daha sonra bağımlılıklar bir Bazel kayıt otoritesinde (varsayılan olarak Bazel Central Registry) aranır. Çalışma alanınızdaki 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 verileri bildiren bir MODULE.bazel dosyası bulunur. Aşağıda temel bir örnek verilmiştir:

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 ve bağımlılıklarınızın MODULE.bazel dosyaları, geçişli bağımlılıkları otomatik olarak keşfedecek şekilde işlenir.

MODULE.bazel dosyası hiçbir kontrol akışı biçimini desteklemediği için BUILD dosyalarına benzer; ayrıca load ifadelerini de yasaklar. MODULE.bazel dosyalarının desteklediği yönergeler:

Sürüm biçimi

Bazel çeşitlilik içeren bir ekosisteme sahiptir ve projelerde çeşitli sürüm şemaları kullanılmaktadır. Açık farkla en popüler olan SemVer ama versiyonları tarih tabanlı olan Abseil gibi farklı şemaların kullanıldığı önemli projeler de vardır (ör. 20210324.2).

Bu nedenle Bzlmod, SemVer spesifikasyonunun daha rahat bir sürümünü kullanır. Aradaki farklar şunlardır:

  • SemVer, sürümün "yayınlama" bölümünün 3 segmentten oluşması gerektiğini belirtir: MAJOR.MINOR.PATCH. Bazel'de bu şart gevşetilerek istenilen sayıda segmente izin verilir.
  • SemVer'de, "sürüm" bölümündeki her segment yalnızca rakam içermelidir. Bazel'de bu durum harflere izin verilmesi için gevşetildi ve karşılaştırma anlamları, "yayın öncesi" bölümündeki "tanımlayıcılar"la eşleşiyor.
  • Ayrıca ana, küçük ve yama sürümü artışlarının anlamları uygulanmaz. (Bununla birlikte, geriye dönük uyumluluğu nasıl belirttiğ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. Buna ek olarak, iki Sunucu sürümü a ve b, Bazel modül sürümleri ile karşılaştırıldığında aynı muhafazalar varsa a < b'i karşılaştırır.

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

Elmas bağımlılık sorunu, sürümü tutulan bağımlılık yönetimi alanının temel unsurlarından biridir. 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 soruyu çözmek için Go modül sisteminde sunulan Minimal Sürüm Seçimi (MVS) algoritmasını kullanır. MVS, bir modülün tüm yeni sürümlerinin geriye dönük uyumlu olduğunu varsayar ve bu nedenle herhangi bir bağımlı (örneğimizde D 1.1) tarafından belirtilen en yüksek sürümü seçer. Buna "minimal" adı verilir çünkü D 1.1, gereksinimlerimizi karşılayabilecek en az sürümdür; D 1.2 veya daha yeni bir sürüm mevcut olsa bile bunları seçmeyiz. Bu uygulamanın avantajı, sürüm seçiminin yüksek kaliteli ve yeniden oluşturulabilir olmasıdır.

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

Uyumluluk düzeyi

MVS'nin bir modülün geriye dönük uyumsuz sürümlerini ayrı bir modül olarak ele aldığından, MVS'nin geriye dönük uyumluluk varsayımın makul 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ülmüş bağımlılık grafiğinde bir arada yer alabileceği anlamına gelir. Böylece ana sürümün Go'daki paket yolunda kodlanması sayesinde de derleme zamanı veya bağlantı süresi çakışması olmaz.

Bazel'de böyle bir garantimiz yoktur. Dolayısıyla, geriye dönük uyumsuz sürümleri tespit etmek için "ana sürüm" numarasını belirtecek bir yönteme ihtiyacımız vardır. Bu sayı uyumluluk düzeyi olarak adlandırılır ve her modül sürümü tarafından kendi module() yönergesinde belirtilir. Bu bilgilere sahip olduğumuzda, aynı modülün farklı uyumluluk seviyelerine sahip sürümlerinin çözülmüş bağımlılık grafiğinde yer aldığını tespit ettiğimizde hata verebiliriz.

Kod deposu adları

Bazel'de her dış bağımlılığın bir depo adı vardır. Bazen aynı bağımlılık farklı depo adları ile kullanılabilir (örneğin, hem @io_bazel_skylib hem de @bazel_skylib, Bazel skylib anlamına gelir) veya farklı projelerdeki farklı bağımlılıklar için aynı depo adı 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ı kullanmaya başlıyoruz. Aşağıda iki önemli kavram belirtilmiştir:

  • Standart depo adı: Her kod deposu için genel olarak benzersiz depo adı. Bu, deponun bulunduğu dizin adı olacaktır.
    Aşağıdaki şekilde oluşturulur (Uyarı: Standart ad biçimi, güvenmeniz gereken bir API değildir ve her an değiştirilebilir):

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

    • Bazel modül depoları için: varsayılan olarak module_name veya bazel_dep içindeki repo_name özelliğiyle belirtilen ad.
    • Modül uzantı depoları için: use_repo aracılığıyla eklenen depo adı.

Her deponun, doğrudan bağımlılıklarını içeren bir depo eşleme sözlüğü vardır. Bu, görünür kod deposu adından standart kod deposu adına bir haritadır. Etiket oluştururken depo adını çözümlemek için depo eşlemesini kullanırız. Standart kod deposu adları arasında çakışma olmadığını ve görünen depo adlarının kullanımlarının MODULE.bazel dosyası ayrıştırılarak keşfedilebileceğini unutmayın. Bu sayede çakışmalar, diğer bağımlılıkları etkilemeden kolayca tespit edilip çözülebilir.

Katı düzeyler

Yeni bağımlılık spesifikasyonu biçimi, daha sıkı denetimler gerçekleştirmemize olanak tanıyor. Özellikle, artık modüllerin yalnızca doğrudan bağımlılıklarından oluşturulan depoları kullanabileceğini zorunlu kılıyoruz. Bu sayede, geçişli bağımlılık grafiğindeki bir şey değiştiğinde, yanlışlıkla veya hata ayıklaması zor hatalardan korunabilirsiniz.

Katı yapılandırmalar, depo eşlemeye göre uygulanır. Esas olarak, her bir deponun kod deposu eşlemesi tüm doğrudan bağımlılıklarını içerir. Diğer herhangi bir depo görünmez. Her deponun görünür bağımlılıkları aşağıdaki şekilde belirlenir:

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

Kayıtlar

Bzlmod, Bazel kayıtlarından bilgi isteyerek bağımlılıkları keşfeder. Bazel kayıt otoritesi, Bazel modüllerinin bir veritabanıdır. Desteklenen tek kayıt defteri biçimi, yerel bir dizin veya belirli bir biçimi izleyen statik HTTP sunucusu olan dizin kayıt defteri'dir. Gelecekte tek modüllü kayıt otoriteleri için destek eklemeyi planlıyoruz. Bu kayıtlar, projenin kaynağını ve geçmişini içeren sadece git depolarıdır.

Dizin kaydı

Dizin kayıt defteri; ana sayfaları, düzenleyicileri, her sürümün MODULE.bazel dosyası ve her sürümün kaynağının nasıl getirileceği dahil olmak üzere bir modül listesi hakkında bilgi içeren yerel bir dizin veya statik HTTP sunucusudur. Özellikle kaynak arşivlerin kendisini sunması gerekmez.

Dizin kayıt defteri aşağıdaki biçime uymalıdır:

  • /bazel_registry.json: Kayıt defteri için meta verileri içeren bir JSON dosyasıdır. Örneğin:
    • mirrors, kaynak arşivleri için kullanılacak yansıtma listesini belirtir.
    • module_base_path öğesini seçerek source.json dosyasında local_repository türü olan modüllerin temel yolunu belirtin.
  • /modules: Bu kayıt defterindeki her bir modülün alt dizinini 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: Aşağıdaki alanlarla birlikte modülle ilgili bilgileri içeren bir JSON dosyası:
      • homepage: Projenin ana sayfasının URL'si.
      • maintainers: JSON nesnelerinin listesi. Her biri kayıt defterindeki modülün güncelleyicisinin bilgilerine karşılık gelir. Bunun, projenin yazarlarıyla aynı olması gerekmediğini unutmayın.
      • versions: Bu kayıt defterinde yer alacak bu modülün tüm sürümlerinin listesi.
      • yanked_versions: Bu modülün yandı olan sürümlerinin listesi. Bu, şu anda bir işlem değildir, ancak gelecekte, çekilen sürümler atlanacaktır veya hata verecektir.
  • /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ği hakkında bilgiler içeren bir JSON dosyası.
      • Varsayılan tür, aşağıdaki alanları içeren "arşiv" türü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ırken çıkarılacak bir dizin ön eki.
        • patches: Her biri, ayıklanan arşive uygulanacak yama dosyasını adlandıran bir dize listesidir. Yama dosyaları /modules/$MODULE/$VERSION/patches dizininde bulunur.
        • patch_strip: Unix yamasının --strip bağımsız değişkeniyle aynıdır.
      • Tür, şu alanlarla yerel bir yol kullanacak şekilde değiştirilebilir:
        • type: local_path
        • path: Deponun yerel yolu. Şu şekilde hesaplanır:
          • Yol mutlak bir yolsa olduğu gibi kullanılır.
          • Yol göreli bir yol 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 otoritesi yerel olarak barındırılmalı ve --registry=file://<registry_path> tarafından kullanılmalıdır. Aksi takdirde, Bazel hata verir.
    • patches/: Yama dosyalarını içeren isteğe bağlı bir dizindir. Yalnızca source.json "arşiv" türüne sahip olduğunda kullanılır.

Bazel Merkez Kaydı

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

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

BCR, normal dizin kayıt defterinin biçimine uymaya ek olarak her modül sürümü (/modules/$MODULE/$VERSION/presubmit.yml) için bir presubmit.yml dosyası gerektirir. Bu dosya, bu modül sürümünün doğruluğunu kontrol etmek için kullanılabilecek birkaç temel derleme ve test hedefi belirtir ve BCR'nin CI ardışık düzenleri tarafından BCR'deki modüller arasında birlikte çalışabilirliği sağlamak için kullanılır.

Kayıt otoritelerini seçme

Tekrarlanabilir Bazel işareti --registry, modül isteğinde bulunulacak kayıt otoritelerinin listesini belirtmek için kullanılabilir. Böylece projenizi, üçüncü taraf veya dahili kayıt otoritesinden bağımlılıkları getirecek şekilde ayarlayabilirsiniz. Daha erken kayıt otoriteleri önceliklidir. Kolaylık sağlaması açısından projenizin .bazelrc dosyasına --registry işaretlerinden oluşan bir liste 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 depo oluşturarak modül sistemini genişletmenize olanak tanır. İşlev açısından bugünün WORKSPACE makrolarına benzerler ancak daha çok modüller ve geçişli bağımlılıklar dünyasında daha uygun hale gelirler.

Modül uzantıları, tıpkı depo kuralları veya WORKSPACE makroları gibi .bzl dosyalarında tanımlanır. Bunlar doğrudan çağrılmaz. Her modül, uzantıların okunması için etiket adı verilen veri parçaları belirtebilir. Daha sonra, modül sürüm çözünürlüğü tamamlandıktan sonra modül uzantıları çalıştırılır. Her uzantı, modül çözünürlüğünden sonra bir kez (herhangi bir derleme gerçekten gerçekleşmeden önce) çalıştırılır ve bağımlılık grafiğinde ona ait tüm etiketleri okur.

          [ 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 vb. Bazel modülleridir. Her birini bir MODULE.bazel dosyası olarak düşünebilirsiniz. Her modül, modül uzantıları için bazı etiketler belirtebilir; burada bazıları "maven" uzantısı için, bazıları da "cargo" uzantısı için belirtilmiştir. Bu bağımlılık grafiği kesinleştiğinde (örneğin, belki B 1.2 aslında D 1.3 üzerinde bir bazel_dep sahibi olsa da C nedeniyle D 1.4 sürümüne geçirildiğinde), "maven" uzantıları çalıştırılır ve etiket hangi depoların oluşturulacağına karar vermek için buradaki bilgileri kullanarak tüm maven.* etiketlerini okur. Aynı şekilde "cargo" uzantısı için de.

Uzantı kullanımı

Uzantılar, Bazel modüllerinin kendilerinde 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 daha sonra, bunu kapsama almak için use_extension yerleşik işlevini çağırmanız gerekir. Aşağıdaki örneği, rules_jvm_external modülünde tanımlanmış varsayımsal bir "maven" uzantısının kullanılması için bir MODULE.bazel dosyasından alınan snippet'i ele alalım:

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

Uzantıyı kapsama aldıktan sonra, bunun için etiketleri belirtmek üzere nokta-söz dizimini kullanabilirsiniz. Etiketlerin, karşılık gelen 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 etiketlerini belirten 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. Bunun amacı, katı Deps koşulunu karşılamak ve yerel depo adı çakışmasını önlemektir.

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

Bir uzantı tarafından oluşturulan depolar, API'nin bir parçasıdır. Bu nedenle, "maven" uzantısının "org_junit_junit" ve "com_google_guava_guava" adlı bir depo oluşturacağını bilmeniz gerekir. use_repo kullanarak, isteğe bağlı olarak bunları modülünüzün kapsamında yeniden adlandırabilirsiniz (ör. "guava").

Uzantı tanımı

Modül uzantıları, depo kurallarına benzer şekilde module_extension işlevi kullanılarak tanımlanır. Her ikisinin de bir uygulama işlevi vardır ancak depo kurallarının çeşitli özellikleri olsa da modül uzantılarında tag_class sayısı bulunur. Bu tag_class'ların her biri farklı özelliklere sahiptir. Etiket sınıfları, bu uzantı tarafından kullanılan etiketler için şemaları tanımlar. Yukarıdaki varsayıma dayalı "maven" uzantısı örneğimizden 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 belirtir.

Uygulama işlevi WORKSPACE makrosuna benzer ancak tek fark, bağımlılık grafiğine ve ilgili tüm etiketlere erişim sağlayan bir module_ctx nesnesi almasıdır. Ardından uygulama işlevi, depo 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üllerin üzerinden geçiyoruz. Bu modüllerin her biri, tags alanında modüldeki tüm maven.* etiketlerini gösteren bir bazel_module nesnesidir. Daha sonra, Maven ile iletişime geçmek ve çözüm işlemi yapmak için CLI yardımcı programını çağırıyoruz. Son olarak, varsayıma dayalı maven_single_jar depo kuralını kullanarak bir dizi depo oluşturmak için çözüm sonucunu kullanırız.