Mengelola dependensi eksternal dengan Bzlmod

Laporkan masalah Lihat sumber Per Malam · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Bzlmod adalah nama kode sistem dependensi eksternal yang baru yang diperkenalkan di Bazel 5.0. Bagian ini diperkenalkan untuk mengatasi beberapa titik permasalahan pada sistem lama yang tidak dapat diperbaiki secara wajar secara bertahap; lihat Bagian Pernyataan Masalah di dokumen desain asli untuk mengetahui detail selengkapnya.

Di Bazel 5.0, Bzlmod tidak diaktifkan secara default; bendera --experimental_enable_bzlmod harus ditentukan agar hal berikut dapat diambil pengaruh tersebut. Seperti yang disarankan oleh nama tanda, fitur ini saat ini bersifat eksperimental; API dan perilaku dapat berubah hingga fitur ini diluncurkan secara resmi.

Untuk memigrasikan project Anda ke Bzlmod, ikuti Panduan Migrasi Bzlmod. Anda juga dapat menemukan contoh penggunaan Bzlmod di repositori contoh.

Modul Bazel

Sistem dependensi eksternal berbasis WORKSPACE lama berpusat pada repositori (atau repo), yang dibuat melalui aturan repositori (atau aturan repo). Meskipun repositori masih merupakan konsep penting dalam sistem baru, modul adalah unit inti dependensi.

Modul pada dasarnya adalah project Bazel yang dapat memiliki beberapa versi, masing-masing yang memublikasikan metadata tentang modul lain yang menjadi dependensinya. Ini adalah analog dengan konsep yang sudah dikenal dalam sistem manajemen dependensi lainnya: Maven artefak, paket npm, peti Cargo, modul Go, dll.

Modul hanya menentukan dependensinya menggunakan pasangan name dan version, bukan URL tertentu di WORKSPACE. Dependensi tersebut kemudian dicari pada registry Bazel; secara {i>default<i}, Bazel Central Registry. Di ruang kerja Anda, masing-masing kemudian berubah menjadi repositori.

MODULE.bazel

Setiap versi dari setiap modul memiliki file MODULE.bazel yang mendeklarasikan dependensi dan {i>metadata<i} lainnya. Berikut adalah contoh dasarnya:

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

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

File MODULE.bazel harus berada di root direktori Workspace (di samping file WORKSPACE). Berbeda dengan file WORKSPACE, Anda tidak perlu untuk menentukan dependensi transitif Anda; sebagai gantinya, Anda sebaiknya hanya menentukan dependensi direct, dan file MODULE.bazel dependensi diproses untuk menemukan dependensi transitif secara otomatis.

File MODULE.bazel mirip dengan file BUILD karena tidak mendukung bentuk alur kontrol; kebijakan ini juga melarang pernyataan load. Perintah File MODULE.bazel yang didukung adalah:

Format versi

Bazel memiliki ekosistem yang beragam dan proyek menggunakan berbagai skema pembuatan versi. Tujuan yang paling populer sejauh ini adalah SemVer, tetapi ada juga proyek terkemuka menggunakan berbagai skema seperti Abseil, yang versi berbasis tanggal, misalnya 20210324.2).

Karena alasan ini, Bzlmod mengadopsi versi spesifikasi SemVer yang lebih longgar. Tujuan perbedaan tersebut meliputi:

  • SemVer menentukan bahwa "rilis" bagian dari versi harus terdiri dari 3 segmen: MAJOR.MINOR.PATCH. Pada Bazel, persyaratan ini dilonggarkan sehingga jumlah segmen yang diizinkan.
  • Di SemVer, setiap segmen dalam "rilis" bagian harus dalam angka saja. Pada Bazel, ini dilonggarkan untuk memungkinkan huruf, dan perbandingan semantik cocok dengan "ID" dalam "prarilis" bagian.
  • Selain itu, semantik peningkatan versi besar, minor, dan patch tidak diterapkan. (Namun, lihat tingkat kompatibilitas untuk detail tentang cara kami menunjukkan kompatibilitas mundur).

Semua versi SemVer yang valid adalah versi modul Bazel yang valid. Selain itu, dua Versi SemVer a dan b membandingkan a < b jika memiliki kesamaan yang berlaku saat dibandingkan dengan versi modul Bazel.

Resolusi versi

Masalah dependensi {i>diamond<i} adalah masalah utama dalam dependensi berversi ruang manajemen proyek. Misalkan Anda memiliki grafik dependensi berikut:

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

Versi D mana yang harus digunakan? Untuk menjawab pertanyaan ini, Bzlmod menggunakan Pilihan Versi Minimal (MVS) yang diperkenalkan dalam sistem modul Go. MVS mengasumsikan bahwa semua versi modulnya kompatibel dengan versi sebelumnya, sehingga hanya memilih jenis yang ditentukan oleh dependen apa pun (D 1.1 dalam contoh kita). Namanya "minimal" karena D 1.1 di sini adalah versi minimal yang dapat memenuhi persyaratan; meskipun ada D 1.2 atau yang lebih baru, kita tidak memilihnya. Cara ini memiliki manfaat tambahan bahwa pemilihan versi berakurasi tinggi dan dapat direproduksi.

Resolusi versi dilakukan secara lokal di komputer Anda, bukan oleh registry.

Tingkat kompatibilitas

Perhatikan bahwa asumsi MVS tentang kompatibilitas mundur dapat diterima karena hanya memperlakukan versi modul yang tidak kompatibel dengan versi sebelumnya sebagai modul terpisah. Dalam hal SemVer, itu berarti A 1.x dan A 2.x dianggap sebagai modul yang berbeda, dan dapat berdampingan dalam grafik dependensi yang di-resolve. Hal ini kemudian membuat dimungkinkan oleh fakta bahwa versi utama dienkode dalam jalur paket di Lanjutkan, sehingga tidak ada konflik waktu kompilasi atau waktu penautan.

Untuk Bazel, kami tidak memiliki jaminan seperti itu. Dengan demikian, kita butuh cara untuk menunjukkan "utama versi" untuk mendeteksi versi yang tidak kompatibel dengan versi sebelumnya. Nomor ini disebut tingkat kompatibilitas, dan ditentukan oleh setiap versi modul di direktif module()-nya. Dengan informasi ini, kita bisa menampilkan {i>error<i} ketika kami mendeteksi bahwa versi modul yang sama dengan kompatibilitas berbeda ada di grafik dependensi yang di-resolve.

Nama repositori

Di Bazel, setiap dependensi eksternal memiliki nama repositori. Terkadang, hal yang sama dependensi mungkin digunakan melalui nama repositori yang berbeda (misalnya, Rata-rata @io_bazel_skylib dan @bazel_skylib Bazel skylib), atau model nama repositori dapat digunakan untuk dependensi yang berbeda dalam project yang berbeda.

Di Bzlmod, repositori dapat dihasilkan oleh modul Bazel dan ekstensi modul. Untuk menyelesaikan konflik nama repositori, kami mulai menangani pemetaan repositori mekanisme atensi dalam sistem baru. Berikut adalah dua konsep penting:

  • Nama repositori kanonis: Nama repositori yang unik secara global untuk setiap repositori resource. Nama ini akan menjadi nama direktori tempat repositori berada.
    Disusun sebagai berikut (Peringatan: format nama kanonis: bukan API yang harus Anda andalkan, API ini dapat berubah sewaktu-waktu):

    • Untuk repositori modul Bazel: module_name~version
      (Contoh. @bazel_skylib~1.0.3)
    • Untuk repositori ekstensi modul: module_name~version~extension_name~repo_name
      (Contoh. @rules_cc~0.0.1~cc_configure~local_config_cc)
  • Nama repositori yang jelas: Nama repositori yang akan digunakan di BUILD dan .bzl dalam repo. Dependensi yang sama dapat menimbulkan di berbagai repositori.
    Ditentukan sebagai berikut:

    • Untuk repositori modul Bazel: module_name dengan default, atau nama yang ditentukan oleh atribut repo_name dalam bazel_dep
    • Untuk repositori ekstensi modul: nama repositori diperkenalkan melalui use_repo

Setiap repositori memiliki kamus pemetaan repositori untuk dependensi langsungnya, yang merupakan peta dari nama repositori yang jelas ke nama repositori kanonis. Kita menggunakan pemetaan repositori untuk me-resolve nama repositori saat membangun label. Perlu diperhatikan bahwa tidak ada konflik nama repositori kanonis, dan penggunaan nama repositori yang jelas dapat ditemukan dengan menguraikan MODULE.bazel sehingga konflik dapat dengan mudah ditemukan dan diselesaikan tanpa memengaruhi dependensi lainnya.

Dependensi ketat

Format spesifikasi dependensi baru memungkinkan kita melakukan pemeriksaan yang lebih ketat. Di beberapa kita sekarang menegakkan bahwa modul hanya dapat menggunakan repo yang dibuat dari dependensi langsung. Hal ini membantu mencegah kerusakan yang tidak disengaja dan sulit di-debug ketika sesuatu dalam grafik dependensi transitif berubah.

Dependensi ketat diimplementasikan berdasarkan pemetaan repositori. Pada dasarnya, fungsi pemetaan repositori untuk setiap repo berisi semua dependensi langsung, repositori lain tidak terlihat. Dependensi yang terlihat untuk setiap repositori ditentukan sebagai berikut:

  • Repositori modul Bazel dapat melihat semua repositori yang diperkenalkan dalam file MODULE.bazel melalui bazel_dep dan use_repo.
  • Repo ekstensi modul bisa melihat semua dependensi modul yang terlihat menyediakan ekstensi, serta semua repositori lain yang dihasilkan oleh modul yang sama .

Registry

Bzlmod menemukan ketergantungan dengan meminta informasi dari Bazel registribusi. {i>Registry<i} Bazel hanyalah sebuah {i>database<i} dari modul Bazel. Satu-satunya bentuk registry yang didukung adalah registry indeks, yang direktori lokal atau server HTTP statis yang mengikuti format tertentu. Di kolom di masa mendatang, kami berencana untuk menambahkan dukungan untuk registry modul tunggal, yang hanya git repo yang berisi sumber dan histori project.

Registry indeks

{i>Registry indeks<i} adalah direktori lokal atau server HTTP statis yang berisi informasi tentang daftar modul, termasuk beranda, pengelola, MODULE.bazel file dari setiap versi, dan cara mengambil sumber setiap versi . Secara khusus, server ini tidak perlu menayangkan arsip sumber itu sendiri.

Registry indeks harus mengikuti format berikut:

  • /bazel_registry.json: File JSON yang berisi metadata untuk registry seperti:
    • mirrors, yang menentukan daftar duplikat yang akan digunakan untuk arsip sumber.
    • module_base_path, yang menentukan jalur dasar untuk modul dengan Jenis local_repository dalam file source.json.
  • /modules: Direktori yang berisi subdirektori untuk setiap modul dalam ini {i>registry<i}.
  • /modules/$MODULE: Direktori yang berisi subdirektori untuk setiap versi di modul ini, serta file berikut:
    • metadata.json: File JSON yang berisi informasi tentang modul, dengan kolom berikut:
      • homepage: URL halaman beranda project.
      • maintainers: Daftar objek JSON, yang masing-masing sesuai dengan informasi pengelola modul di registry. Perhatikan bahwa ini belum tentu sama dengan penulis proyek.
      • versions: Daftar semua versi modul ini yang dapat ditemukan di {i>registry<i} ini.
      • yanked_versions: Daftar versi yang ditarik dari modul ini. Ini saat ini tanpa pengoperasian, tetapi di masa mendatang, versi yang ditarik akan dilewati atau menghasilkan error.
  • /modules/$MODULE/$VERSION: Direktori yang berisi file berikut:
    • MODULE.bazel: File MODULE.bazel versi modul ini.
    • source.json: File JSON yang berisi informasi tentang cara mengambil versi modul ini.
      • Jenis defaultnya adalah "arsip" dengan kolom berikut:
        • url: URL arsip sumber.
        • integrity: Integritas Subresource {i>checksum<i} arsip.
        • strip_prefix: Awalan direktori yang akan dihapus saat mengekstrak arsip sumber.
        • patches: Daftar string, yang masing-masing menamai file patch untuk diterapkan ke arsip yang diekstrak. File {i>patch<i} terletak di direktori /modules/$MODULE/$VERSION/patches.
        • patch_strip: Sama dengan argumen --strip pada patch Unix.
      • Jenis ini dapat diubah untuk menggunakan jalur lokal dengan kolom berikut:
        • type: local_path
        • path: Jalur lokal ke repo, yang dihitung sebagai berikut:
          • Jika jalur adalah jalur absolut, jalur tersebut akan digunakan sebagaimana adanya.
          • Jika jalur adalah jalur relatif dan module_base_path adalah jalur absolut, jalur diselesaikan menjadi <module_base_path>/<path>
          • Jika jalur dan module_base_path merupakan jalur relatif, jalur adalah diselesaikan menjadi <registry_path>/<module_base_path>/<path>. Registry harus dihosting secara lokal dan digunakan oleh --registry=file://<registry_path>. Jika tidak, Bazel akan menampilkan pesan error.
    • patches/: Direktori opsional yang berisi file patch, hanya digunakan jika source.json memiliki "arsip" .

Bazel Central Registry

Bazel Central Registry (BCR) adalah {i>registry<i} indeks yang terletak di bcr.bazel.build. Isinya didukung oleh repositori GitHub bazelbuild/bazel-central-registry

BCR dikelola oleh komunitas Bazel; kontributor dipersilakan untuk mengirimkan permintaan pull. Lihat Kebijakan dan Prosedur Bazel Central Registry.

Selain mengikuti format {i>registry<i} indeks normal, BCR memerlukan file presubmit.yml untuk setiap versi modul (/modules/$MODULE/$VERSION/presubmit.yml). File ini menentukan beberapa hal penting membangun dan menguji target yang dapat digunakan untuk memeriksa validitas modul, dan digunakan oleh pipeline CI BCR untuk memastikan interoperabilitas antar-modul dalam BCR.

Memilih registry

Tanda Bazel berulang --registry dapat digunakan untuk menentukan daftar registry untuk meminta modul, jadi Anda dapat menyiapkan project yang akan diambil dependensi dari pihak ketiga atau {i>registry<i} internal. Registri sebelumnya mengambil prioritas tinggi. Untuk memudahkan, Anda dapat menempatkan daftar tanda --registry di .bazelrc dari project Anda.

Ekstensi Modul

Ekstensi modul memungkinkan Anda memperluas sistem modul dengan membaca data input dari modul di seluruh grafik dependensi, melakukan logika yang diperlukan untuk dependensi, dan terakhir membuat repositori dengan memanggil aturan repo. Mereka serupa dalam fungsi ke makro WORKSPACE saat ini, tetapi lebih cocok di dunia modul dan dependensi transitif.

Ekstensi modul ditentukan dalam file .bzl, seperti aturan repo atau makro WORKSPACE. Mereka tidak dipanggil secara langsung; setiap modul dapat menentukan bagian data yang disebut tag untuk dibaca ekstensi. Kemudian, setelah modul resolusi versi selesai, ekstensi modul akan dijalankan. Setiap ekstensi dijalankan sekali setelah resolusi modul (masih sebelum build apa pun benar-benar terjadi), dan membaca semua tag yang memilikinya di seluruh grafik dependensi.

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

Pada contoh grafik dependensi di atas, A 1.1 dan B 1.2 dll. adalah modul Bazel; Anda dapat menganggap masing-masing sebagai file MODULE.bazel. Setiap modul dapat menentukan beberapa tag untuk ekstensi modul; di sini beberapa ditentukan untuk ekstensi "{i>maven<i}", dan beberapa ditentukan untuk "{i>cargo<i}". Ketika grafik dependensi ini diselesaikan (untuk misalnya, mungkin B 1.2 benar-benar memiliki bazel_dep di D 1.3 tetapi diupgrade menjadi D 1.4 karena C), ekstensi "maven" dijalankan, dan skrip itu dapat membaca semua maven.*, menggunakan informasi di dalamnya untuk menentukan repositori yang akan dibuat. Demikian pula untuk "{i>cargo<i}" .

Penggunaan ekstensi

Ekstensi di-{i>host<i} di modul Bazel sendiri, jadi untuk menggunakan ekstensi modul Anda, Anda harus menambahkan bazel_dep terlebih dahulu pada modul tersebut, lalu memanggil use_extension bawaan fungsi untuk membawanya ke dalam ruang lingkup. Perhatikan contoh berikut, cuplikan dari file MODULE.bazel untuk menggunakan "maven" fiktif ekstensi yang ditentukan dalam Modul rules_jvm_external:

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

Setelah memasukkan ekstensi ke dalam cakupan, Anda dapat menggunakan sintaksis titik untuk dan menentukan tag untuk resource tersebut. Perhatikan bahwa tag harus mengikuti skema yang ditetapkan oleh class tag yang sesuai (lihat definisi ekstensi di bawah). Berikut adalah contoh yang menentukan beberapa tag maven.dep dan maven.pom.

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

Jika ekstensi menghasilkan repositori yang ingin Anda gunakan dalam modul, gunakan Perintah use_repo yang akan dideklarasikan mereka. Ini untuk memenuhi kondisi dependensi yang ketat dan menghindari nama repo lokal konflik.

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

Repositori yang dihasilkan oleh ekstensi adalah bagian dari API-nya, jadi dari tag yang ditentukan, Anda harus tahu bahwa "maven" akan menghasilkan repositori bernama "org_junit_junit", dan satu lagi bernama "com_google_guava_guava". Dengan use_repo, Anda dapat mengganti namanya dalam cakupan modul, seperti "jambu biji" di sini.

Definisi ekstensi

Ekstensi modul didefinisikan mirip dengan aturan repo, menggunakan Fungsi module_extension. Keduanya memiliki fungsi implementasi; tetapi meskipun aturan repo memiliki sejumlah ekstensi modul memiliki sejumlah tag_class, yang masing-masing memiliki sejumlah atribut. Class tag menentukan skema untuk tag yang digunakan . Melanjutkan contoh "maven" fiktif ekstensi di atas:

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

Deklarasi ini memperjelas bahwa tag maven.dep dan maven.pom dapat ditentukan, dengan menggunakan skema atribut yang didefinisikan di atas.

Fungsi implementasi ini mirip dengan makro WORKSPACE, hanya saja mendapatkan objek module_ctx, yang memberikan akses ke grafik dependensi dan semua tag yang terkait. Implementasi fungsi ini kemudian memanggil aturan repo untuk menghasilkan repositori:

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

Pada contoh di atas, kita membahas semua modul dalam grafik dependensi (ctx.modules), yang masing-masing merupakan Objek bazel_module dengan kolom tags mengekspos semua tag maven.* pada modul. Kemudian kita panggil utilitas CLI Coursier untuk menghubungi Maven dan melakukan resolusi. Terakhir, kita gunakan model untuk membuat sejumlah repositori, menggunakan hipotesis maven_single_jar repo.