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. Eski sistemde aşamalı olarak düzeltilmesi mümkün olmayan çeşitli sorunları gidermek için kullanıma sunulmuştur. Daha fazla bilgi için orijinal tasarım dokümanındaki Sorun Beyanıyla ilgili bölüme 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. İş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 Kılavuzu'nu uygulayın. examples deposunda örnek Bzlmod kullanımlarını da bulabilirsiniz.

Bazel Modülleri

WORKSPACE tabanlı eski harici bağımlılık sisteminin merkezinde depo kuralları (veya depo kuralları) aracılığıyla oluşturulan depolar (veya depolar) yer alıyordu. Yeni sistemde de önemli bir kavram olan depoların aksine modüller, bağımlılıkların temel birimleridir.

Modül, temelde birden fazla sürümü olabilecek bir Bazel projesidir. Her sürüm, bağlı olduğu diğer modüller hakkında meta veriler yayınlar. Bu, diğer bağımlılık yönetimi sistemlerindeki bilinen kavramlara benzer: Maven öğesi, npm paketi, Cargo kutusu, Go modülü vb.

Bir modül, WORKSPACE içindeki belirli URL'ler yerine bağımlılıkları name ve version çiftleri kullanarak belirtir. Daha sonra bağımlılıklar bir Bazel kayıt otoritesinde (varsayılan olarak Bazel Merkez Kayıt Otoritesi) aranır. Ardından, ç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 verileri tanımlayan 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ünde (WORKSPACE dosyasının yanında) bulunmalıdır. WORKSPACE dosyasından farklı olarak, 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 keşfetmek için işlenir.

MODULE.bazel dosyası, herhangi bir kontrol akışı biçimini desteklemediği için BUILD dosyalarına benzer. Ayrıca load ifadelerini de yasaklar. MODULE.bazel dosyaları aşağıdaki yönergeleri destekler:

Sürüm biçimi

Bazel'in çeşitli bir ekosistemi vardır ve projelerde çeşitli sürümlendirme şemaları kullanılır. Şu ana kadar en popüler olan SemVer olsa da farklı şemaların kullanıldığı önemli projeler de vardır. Örneğin 20210324.2 gibi versiyonları tarih tabanlı olan Abseil).

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

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

Geçerli tüm SemVer sürümleri geçerli Bazel modülü sürümüdür. Ayrıca, iki SemVer sürümü a ve b, Bazel modül sürümleriyle karşılaştırıldığında aynı muhafazaların a < b olarak karşılaştırmasını yapar.

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

Elmas bağımlılığı sorunu, sürümlü 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 tanıtılan Minimum 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ı tarafından belirtilen en yüksek sürümü (örnekteki D 1.1) seçer. Bu işlev "minimal" olarak adlandırılır çünkü D 1.1, gereksinimlerimizi karşılayabilecek minimal sürümdür; D 1.2 veya daha yeni bir sürüm mevcut olsa bile bunları seçmeyiz. Bu, sürüm seçiminin yüksek kaliteli ve tekrarlanabilir olması avantajını da beraberinde getirir.

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

Uyumluluk seviyesi

MVS'nin geriye dönük uyumlulukla ilgili varsayımını, bir modülün geriye dönük uyumlu olmayan sürümlerini ayrı bir modül olarak ele aldığı için uygulanabilir olduğunu unutmayın. SemVer açısından bu, A 1.x ve A 2.x'in farklı modüller olarak kabul edildiği ve çözülmüş bağımlılık grafiğinde birlikte bulunabileceği anlamına gelir. Bu da büyük sürümün Go'da paket yolunda kodlanmış olması sayesinde mümkün olur. Böylece derleme veya bağlantı sırasında herhangi bir çakışma yaşanmaz.

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ı göstermenin bir yolunu bulmamız gerekir. Bu sayı uyumluluk seviyesi olarak adlandırılır ve module() yönergesinde her modül sürümü tarafından belirtilir. Bu bilgilerle, çözülmüş bağımlılık grafiğinde aynı modülün farklı uyumluluk düzeylerine sahip sürümlerinin bulunduğunu tespit ettiğimizde hata mesajı gösterebiliriz.

Kod deposu adları

Bazel'de her harici bağımlılık için bir depo adı bulunur. Bazen aynı bağımlılık farklı depo adları aracılığıyla kullanılabilir (ö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ı ile 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 deponun küresel olarak benzersiz depo adıdır. Bu, deposunun bulunduğu dizin adıdır.
    Aşağıdaki gibi oluşturulmuştur (Uyarı: Standart ad biçimi kullanmanız gereken bir API değildir, her an 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.
    Aşağıdaki şekilde belirlenir:

    • Bazel modül depoları için: Varsayılan olarak module_name veya bazel_dep içindeki repo_name özelliği tarafından belirtilen ad.
    • Modül uzantı depoları için: use_repo ile oluşturulan 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 bir eşlemedir. Etiket oluştururken depo adını çözmek için depo eşlemesini kullanırız. Resmi depo adlarında çakışma olmadığını ve görünen depo adlarının kullanımlarının MODULE.bazeldosyası ayrıştırılarak bulunabileceğini unutmayın. Bu nedenle, diğer bağımlılıkları etkilemeden çakışmalar kolayca yakalanıp çözülebilir.

Katı bağımlılık

Yeni bağımlılık spesifikasyonu biçimi, daha sıkı kontroller gerçekleştirmemize olanak tanır. Özellikle, bir modülün yalnızca doğrudan bağımlılıklarından oluşturulan depoları kullanabileceğini zorunlu kılıyoruz. Bu, geçişli bağımlılık grafiğinde bir şey değiştiğinde yanlışlıkla yapılan ve hata ayıklamanın zor olduğu kesintileri önlemeye yardımcı olur.

Katı bağımlılık, depo eşlemeye göre uygulanır. Temel olarak, her deponun depo eşlemesi, doğrudan bağımlılıklarının tümünü içerir. Diğer depolar 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 tanıtılan tüm depoları görebilir.
  • Modül uzantısı deposu, uzantıyı sağlayan modülün tüm görünür bağımlılıkları ve 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ını keşfeder. Bazel kayıt defteri, Bazel modüllerinin veritabanıdır. Yalnızca dizin kayıt defteri desteklenir. Bu, yerel bir dizin veya belirli bir biçimi izleyen statik bir HTTP sunucusudur. Gelecekte, bir projenin kaynağını ve geçmişini içeren git depoları olan tek modüllü kayıt defteri desteğini eklemeyi planlıyoruz.

Dizin kayıt otoritesi

Dizin kaydı; ana sayfa, bakım sorumluları, her sürümün MODULE.bazel dosyası ve her sürümün kaynağının nasıl alınacağı dahil olmak üzere bir modül listesi hakkında bilgi içeren yerel bir dizin veya statik HTTP sunucusudur. Kaynak arşivlerin kendisini sunması gerekmez.

Dizin sicil dairesi aşağıdaki biçime uygun olmalıdır:

  • /bazel_registry.json: Aşağıdaki gibi kayıt otoritesinin meta verilerini içeren bir JSON dosyası:
    • mirrors, kaynak arşivler için kullanılacak aynaların listesini belirtir.
    • module_base_path, source.json dosyasında local_repository türüne sahip modüllerin temel yolunu 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ül hakkında bilgi içeren bir JSON dosyası. Aşağıdaki alanlara sahiptir:
      • homepage: Projenin ana sayfasının URL'si.
      • maintainers: Her biri kayıt defterindeki modülün sorumlusunun bilgilerine karşılık gelen JSON nesnelerinin listesi. Bu kullanıcıların, projenin yazarlarıyla aynı olmayabileceğini unutmayın.
      • versions: Bu kayıt otoritesinde bu modülün tüm sürümlerinin listesi.
      • yanked_versions: Bu modülün geri çekilen sürümlerinin listesi. Bu işlem şu anda hiçbir işlem yapmaz ancak gelecekte, yayından kaldırılan 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 alınacağıyla ilgili bilgileri içeren bir JSON dosyası.
      • Varsayılan tür, aşağıdaki alanlara sahip "arşiv" türüdür:
        • url: Kaynak arşivin URL'si.
        • integrity: Arşivin alt öğe bütünlüğü sağlama toplamı.
        • strip_prefix: Kaynak arşiv çıkarıldığında kaldırılacak bir dizin ön eki.
        • patches: Her biri, ayıklanan arşive uygulanacak bir yamalı dosya adını içeren dize listesi. 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, aşağıdaki alanlarla yerel bir yol kullanacak şekilde değiştirilebilir:
        • type: local_path
        • path: Deponun yerel yolu. Aşağıdaki ş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 yollarsa 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 bir hata oluşturur.
    • patches/: Yalnızca source.json "arşiv" türüne sahip olduğunda kullanılan, yama dosyalarını içeren isteğe bağlı bir dizin.

Bazel Merkezi Kayıt Otoritesi

Bazel Merkezi Kaydı (BCR), bcr.bazel.build adresinde bulunan bir dizin kaydıdır. İç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 Sicil Dairesi Politikaları ve Prosedürleri'ne bakın.

BCR, normal dizin kaydı biçimine 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 geçerliliğini kontrol etmek için kullanılabilecek birkaç temel derleme ve test hedefini 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 defteri listesini belirtmek için kullanılabilir. Böylece projenizi, bağımlılıklarını üçüncü taraf veya dahili bir kayıt defterinden alacak şekilde ayarlayabilirsiniz. Önceki kayıtlar önceliklidir. Kolaylık sağlamak için projenizin .bazelrc dosyasına --registry işaretlerinin listesini 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. İşlevsel olarak bugünün WORKSPACE makrolarına benzerler ancak modül ve geçişli bağımlılıklar dünyasına daha uygundurlar.

Modül uzantıları, repo kuralları veya WORKSPACE makroları gibi .bzl dosyalarında tanımlanır. Doğrudan çağrılmazlar. Bunun yerine, her modül uzantıların okuyabileceği 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ümlemesinden sonra bir kez çalıştırılır (ancak herhangi bir derleme gerçekleşmeden önce) 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, B 1.2 vb. Bazel modülleridir. Her birini bir MODULE.bazel dosyası olarak düşünebilirsiniz. Her modülde, modül uzantıları için bazı etiketler belirtilebilir. Burada bazıları "maven" uzantısı için, bazıları ise "cargo" uzantısı için belirtilir. Bu bağımlılık grafiği son haline geldiğinde (örneğin, belki B 1.2 D 1.3 alanında bazel_dep içeriyor, ancak C nedeniyle D 1.4 sürümüne yükseltildiğinde) "maven" uzantısı çalıştırılır ve tüm maven.* etiketleri, hangi depoların oluşturulacağına karar vermek için buradaki bilgilerden yararlanılarak okunur. "kargo" uzantısı için de benzer bir durum söz konusudur.

Uzantı kullanımı

Uzantıların barındırıldığı yer Bazel modülleridir. Bu nedenle, modülünüzde bir uzantı kullanmak için önce söz konusu modüle bir bazel_dep eklemeniz, ardından uzantıyı kapsama almak için use_extension yerleşik işlevini çağırmanız gerekir. Aşağıdaki örneği inceleyin. Bu örnek, rules_jvm_external modülünde tanımlanan varsayımsal bir "maven" uzantısını kullanmak için bir MODULE.bazel dosyasından alınmış bir snippet'tir:

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

Uzantıyı kapsama aldıktan sonra, etiketlerini belirtmek için nokta söz dizimini kullanabilirsiniz. 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). Bazı maven.dep ve maven.pom etiketlerinin belirtildiği bir örneği burada bulabilirsiniz.

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ı beyan etmek için use_repo yönergesini kullanın. Bu, sıkı 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, API'nin bir parçasıdır. Bu nedenle, belirttiğiniz etiketlerden "maven" uzantısının "org_junit_junit" ve "com_google_guava_guava" adlı bir depo oluşturacağını bilmeniz gerekir. use_repo ile bunları isteğe bağlı olarak modülünüzün kapsamında yeniden adlandırabilirsiniz (burada "guava" olarak).

Uzantı tanımı

Modül uzantıları, module_extension işlevi kullanılarak depo kurallarına benzer şekilde 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ının her biri farklı özelliklere sahip bir dizi tag_class özelliği vardır. Etiket sınıfları, bu uzantı tarafından kullanılan etiketler için şemaları tanımlar. Yukarıdaki varsayımsal "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, bir WORKSPACE makrosuna benzer. Tek farkı, bağımlılığı grafiğine ve ilgili tüm etiketlere erişim izni veren bir module_ctx nesnesi almasıdır. Ardından uygulama işlevi, kod depoları oluşturmak için depo kurallarını çağırı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 inceleriz. Bu modüllerin her biri, tags alanı modüldeki tüm maven.* etiketlerini gösteren bir bazel_module nesnesi olur. Ardından, Maven ile iletişime geçip çözümü gerçekleştirmek için CLI yardımcı programı Coursier'ı çağırırız. Son olarak, çözüm sonucunu kullanarak varsayımsal maven_single_jar depo kuralını kullanarak bir dizi depo oluştururuz.