Bzlmod è il nome in codice del nuovo sistema di dipendenza esterna introdotto in Bazel 5.0. È stato introdotto per affrontare diversi punti critici del vecchio sistema che non potevano essere risolti in modo incrementale; Consulta la sezione relativa alla definizione del problema nel documento di progettazione originale per maggiori dettagli.
In Bazel 5.0, Bzlmod non è attivo per impostazione predefinita; Per rendere effettivo il flag
--experimental_enable_bzlmod
, è necessario specificare il flag seguente. Come suggerisce il nome della segnalazione, questa funzionalità è attualmente sperimentale; API e comportamenti possono cambiare fino al lancio ufficiale della funzionalità.
Moduli Bazel
Il vecchio sistema di dipendenza esterno basato su WORKSPACE
è centrato sui
repository (o repos), creati tramite le regole di repository (o regole repository).
Mentre i repository sono ancora un concetto importante nel nuovo sistema, i moduli sono le unità principali di dipendenza.
Un modulo è essenzialmente un progetto Bazel che può avere più versioni, ognuna delle quali pubblica metadati su altri moduli da cui dipende. Si tratta di un concetto analogo a quello di altri sistemi di gestione delle dipendenze: un artefatto Maven, un pacchetto npm, una crate Cargo , un modulo Go e così via.
Un modulo specifica semplicemente le sue dipendenze utilizzando coppie name
e version
, anziché URL specifici in WORKSPACE
. Le dipendenze vengono quindi cercate in un
registro Baizel; per impostazione predefinita, Bazel Central Registry. Nell'area di lavoro, ogni modulo diventa quindi un repository.
MODULE.bazel
Ogni versione di ogni modulo ha un file MODULE.bazel
che ne dichiara le dipendenze e altri metadati. Ecco un esempio di base:
module(
name = "my-module",
version = "1.0",
)
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
Il file MODULE.bazel
deve trovarsi nella directory principale della directory dell'area di lavoro (accanto al file WORKSPACE
). A differenza del file WORKSPACE
, non è necessario specificare le dipendenze transitive; Devi invece specificare solo le dipendenze dirette e i file MODULE.bazel
delle tue dipendenze verranno elaborati per rilevare automaticamente le dipendenze transitorie.
Il file MODULE.bazel
è simile ai file BUILD
perché non supporta alcuna forma di flusso di controllo; vieta inoltre le istruzioni load
. Le istruzioni
MODULE.bazel
supportate dai file sono:
module
, per specificare i metadati relativi al modulo corrente, inclusi nome, versione e così via;bazel_dep
, per specificare le dipendenze dirette su altri moduli Bazel;- Override, che possono essere utilizzati solo dal modulo radice (vale a dire non da un modulo che viene utilizzato come dipendenza) per personalizzare il comportamento di una determinata dipendenza diretta o transitiva:
- Istruzioni relative alle estensioni modulo:
Formato della versione
Bazel ha un ecosistema diversificato e i progetti utilizzano diversi schemi di controllo delle versioni. Il
più popolare è in assoluto SemVer, ma esistono anche
progetti di spicco che utilizzano schemi diversi come
Abseil, le cui versioni
sono basato sulla data, ad esempio 20210324.2
).
Per questo motivo, Bzlmod adotta una versione più flessibile della specifica SemVer, in particolare consentendo un numero qualsiasi di sequenze di cifre nella parte "release" della versione (anziché esattamente tre come prescritto da SemVer: MAJOR.MINOR.PATCH
).
Inoltre, la semantica degli aumenti principali, minori e delle patch non viene
applicata. Tuttavia, consulta il livello di compatibilità per maggiori dettagli su come intendiamo la compatibilità con le versioni precedenti. Altre parti delle specifiche SemVer, ad esempio un trattino che indica una versione non definitiva, non vengono modificate.
Risoluzione delle versioni
Il problema della dipendenza del rombo è un elemento fondamentale nello spazio di gestione delle dipendenze con versione. Supponi di avere il seguente grafico di dipendenza:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
Quale versione di D deve essere utilizzata? Per risolvere questo problema, Bzlmod utilizza l'algoritmo Scelta versione minima (MVS) introdotta nel sistema del modulo Go. MVS presuppone che tutte le nuove versioni di un modulo siano compatibili con le versioni precedenti e quindi sceglie semplicemente la versione più alta specificata da qualsiasi dipendenza (D 1.1 nel nostro esempio). Si chiama "minimal" perché D 1.1 ecco la versione minimal in grado di soddisfare i nostri requisiti. Anche se esiste D 1.2 o successivo, non li selezioniamo. Questo offre il vantaggio aggiuntivo che la selezione della versione è alta fedeltà e riproducibile.
La risoluzione delle versioni viene eseguita localmente sul computer, non dal registro.
Livello di compatibilità
Tieni presente che l'ipotesi di MVS sulla compatibilità con le versioni precedenti è fattibile, perché considera semplicemente le versioni non compatibili con le versioni precedenti di un modulo come un modulo separato. In termini di SemVer, ciò significa che A 1.x e A 2.x sono considerati moduli distinti e possono coesistere nel grafico delle dipendenze risolto. Ciò è possibile a sua volta dal fatto che la versione principale è codificata nel percorso del pacchetto in Go, quindi non si verificano conflitti in fase di compilazione o in fase di collegamento.
Per Bazel non disponiamo di garanzie. Abbiamo quindi bisogno di un modo per indicare il numero di versione principale per rilevare le versioni incompatibili con le versioni precedenti. Questo numero è denominato livello di compatibilità ed è specificato da ogni versione del modulo nella relativa istruzione module()
. Con queste informazioni a portata di mano, possiamo generare un errore quando rileviamo che nel grafico delle dipendenze risolto esistono versioni dello stesso modulo con livelli di compatibilità diversi.
Nomi repository
In Bazel, ogni dipendenza esterna ha un nome di repository. A volte, la stessa dipendenza può essere utilizzata tramite nomi di repository diversi (ad esempio, @io_bazel_skylib
e @bazel_skylib
indicano Bazel skylib) o la stessa
Il nome del repository potrebbe essere utilizzato per diverse dipendenze in progetti diversi.
In Bzlmod, i repository possono essere generati dai moduli Bazel e dalle estensioni modulo. Per risolvere i conflitti relativi ai nomi dei repository, stiamo adottando il meccanismo di mappatura dei repository nel nuovo sistema. Di seguito sono riportati due aspetti importanti da ricordare.
Nome repository canonico: il nome univoco del repository a livello globale per ogni repository. Questo sarà il nome di directory in cui si trova il repository.
Il modello è strutturato come segue (Avviso: il formato del nome canonico non è un'API da cui dipende, è soggetto a modifiche in qualsiasi momento):- Per i repository modulo Bazel:
module_name.version
(esempio.@bazel_skylib.1.0.3
) - Per i repository delle estensioni modulo:
module_name.version.extension_name.repo_name
(Esempio.@rules_cc.0.0.1.cc_configure.local_config_cc
)
- Per i repository modulo Bazel:
Nome repository locale: il nome del repository da utilizzare nei file
BUILD
e.bzl
all'interno di un repository. La stessa dipendenza potrebbe avere nomi locali diversi per repository diversi.
È determinato come segue:
Ogni repository ha un dizionario di mappatura dei repository come dipendenze dirette,
che è una mappa dal nome del repository locale al nome del repository canonico.
Utilizziamo la mappatura del repository per risolvere il nome del repository durante la creazione di
un'etichetta. Tieni presente che non vi è alcun conflitto di nomi di repository canonici e l'utilizzo dei nomi di repository locali può essere rilevato mediante l'analisi del file MODULE.bazel
. Di conseguenza, i conflitti possono essere facilmente rilevati e risolti senza influire sui dati. altre dipendenze.
Scarte rigide
Il nuovo formato della specifica delle dipendenze ci consente di eseguire controlli più rigidi. In particolare, ora applichiamo che un modulo può utilizzare solo i repository creati dalle sue dipendenze dirette. In questo modo si evitano interruzioni accidentali e difficili da eseguire il debug quando cambia qualcosa nel grafico delle dipendenze transitorie.
I deployment di livello massimo sono implementati in base alla mappatura del repository. In pratica, la mappatura del repository per ogni repository contiene tutte le sue dipendenze dirette. Qualsiasi altro repository non è visibile. Le dipendenze visibili per ogni repository sono determinate come segue:
- Un repository modulo Bazel può vedere tutti i repository introdotti nel file
MODULE.bazel
tramitebazel_dep
euse_repo
. - Un repository di estensioni modulo può vedere tutte le dipendenze visibili del modulo che fornisce l'estensione, più tutti gli altri repository generati dalla stessa estensione modulo.
Registri
Bzlmod rileva le dipendenze richiedendo le informazioni dai registri di Bazel. Un registro Bazel è semplicemente un database di moduli Bazel. L'unica forma supportata dei registry è un registro degli indici, ovvero una directory locale o un server HTTP statico che segue un formato specifico. In futuro, prevediamo di aggiungere il supporto per i registri di moduli singoli, che sono semplicemente repository Git che contengono l'origine e la cronologia di un progetto.
Registro di indice
Un registro di indice è una directory locale o un server HTTP statico contenente informazioni su un elenco di moduli, inclusi home page, manutentori, file MODULE.bazel
di ogni versione e informazioni su come recuperare l'origine di ogni
version. In particolare, non è necessario pubblicare gli archivi di origine.
Un registry di indice deve avere il seguente formato:
/bazel_registry.json
: un file JSON contenente metadati per il Registro di sistema. Attualmente, ha una sola chiave,mirrors
, che specifica l'elenco di mirror da utilizzare per gli archivi di origine./modules
: una directory contenente una sottodirectory per ogni modulo di questo registro./modules/$MODULE
: una directory contenente una sottodirectory per ogni versione di questo modulo, nonché il seguente file:metadata.json
: un file JSON contenente informazioni sul modulo, con i seguenti campi:homepage
: l'URL della home page del progetto.maintainers
: un elenco di oggetti JSON, ognuno dei quali corrisponde alle informazioni di un gestore del modulo nel registro. Tieni presente che non si tratta necessariamente degli author del progetto.versions
: un elenco di tutte le versioni di questo modulo presenti nel registro.yanked_versions
: un elenco delle versioni yank di questo modulo. Al momento si tratta di un'operazione autonoma, ma in futuro le versioni con yak verranno ignorate e generano un errore.
/modules/$MODULE/$VERSION
: una directory contenente i seguenti file:MODULE.bazel
: il fileMODULE.bazel
di questa versione del modulo.source.json
: un file JSON contenente informazioni su come recuperare l'origine di questa versione del modulo, con i seguenti campi:url
: l'URL dell'archivio di origine.integrity
: checksum Integrità risorsa secondaria dell'archivio.strip_prefix
: un prefisso della directory da rimuovere durante l'estrazione dell'archivio di origine.patches
: un elenco di stringhe, ciascuna delle quali assegna un nome a un file di patch da applicare all'archivio estratto. I file patch si trovano nella directory/modules/$MODULE/$VERSION/patches
.patch_strip
: uguale all'argomento--strip
della patch Unix.
patches/
: una directory facoltativa contenente file patch.
Registro centrale Bazel
Bazel Central Registry (BCR) è un registro di indice situato all'indirizzo registry.bazel.build. I suoi contenuti sono supportati dal repository GitHub bazelbuild/bazel-central-registry
.
Il BCR è gestito dalla community di Bazel; i collaboratori sono invitati a inviare richieste di pull. Consulta Criteri e procedure del Registro di sistema centrale di Bazel.
Oltre a seguire il formato di un normale registro degli indici, il BCR richiede un file presubmit.yml
per ogni versione del modulo (/modules/$MODULE/$VERSION/presubmit.yml
). Questo file specifica alcune destinazioni essenziali di build e test che possono essere utilizzate per verificare la validità di questa versione del modulo e vengono utilizzate dalle pipeline CI di BCR per garantire l'interoperabilità tra i moduli nella BCR ,
Selezione dei registri
Il flag Bazel ripetibile --registry
può essere utilizzato per specificare l'elenco di registry da cui richiedere i moduli, in modo da poter configurare il progetto per recuperare le dipendenze da un registro di terze parti o interno. I registri precedenti hanno la precedenza. Per praticità, puoi inserire un elenco di flag --registry
nel file .bazelrc
del tuo progetto.
Estensioni modulo
Le estensioni modulo consentono di estendere il sistema dei moduli leggendo i dati di input dai moduli nel grafico delle dipendenze, eseguendo la logica necessaria per risolvere le dipendenze e infine creando repository utilizzando le regole repository. Sono simili
alle funzioni delle macro WORKSPACE
di oggi, ma sono più adatte nel mondo dei
moduli e delle dipendenze transitorie.
Le estensioni modulo vengono definite nei file .bzl
, proprio come le regole repository o le macro WORKSPACE
. Non vengono richiamati direttamente. Ogni modulo può invece specificare dati detti tag da far leggere alle estensioni. Successivamente, al termine della risoluzione della versione del modulo, le estensioni modulo vengono eseguite. Ogni estensione viene eseguita
dopo la risoluzione del modulo (ancora prima che venga creata una build) e
leggi tutti i tag che appartengono all'intera tabella di dipendenza.
[ 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) ]
Nel grafico delle dipendenze di esempio riportato sopra, A 1.1
, B 1.2
e così via sono moduli Bazel;
puoi considerare ciascuno di essi come un file MODULE.bazel
. Ogni modulo può specificare alcuni tag per le estensioni modulo. qui vengono specificati per l'estensione "maven" e altri per "cargo". Quando il grafico di dipendenza è finalizzato (ad
esempio, B 1.2
in realtà ha un bazel_dep
su D 1.3
ma è stato eseguito l'upgrade a
D 1.4
a causa di C
), viene eseguita ed è in grado di leggere tutti i tag maven.*
, utilizzando le informazioni al suo interno per decidere quali repository creare.
Analogamente all'estensione "cargo".
Utilizzo delle estensioni
Le estensioni sono ospitate nei moduli Bazel stessi, pertanto per utilizzare un'estensione nel
modulo, devi prima aggiungere un elemento bazel_dep
su quel modulo, quindi richiamare
use_extension
per integrarlo nell'ambito di applicazione. Prendiamo come esempio il seguente esempio, uno snippet da un file MODULE.bazel
per utilizzare un'ipotetica estensione "maven" definita nel modulo rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Dopo aver portato l'estensione nell'ambito, puoi utilizzare la sintassi punto per specificare i tag. Tieni presente che i tag devono seguire lo schema definito dalle
classi di tag corrispondenti (consulta la definizione delle estensioni
di seguito). Di seguito è riportato un esempio che specifica alcuni tag maven.dep
e 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")
Se l'estensione genera repository che vuoi utilizzare nel modulo, utilizza l'istruzione
use_repo
per dichiararli. In questo modo si soddisfano le rigide condizioni dei deep ed evitano conflitti tra i nomi dei repository locali.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
I repository generati da un'estensione fanno parte della sua API, quindi dai tag che hai specificato, dovresti sapere che l'estensione "maven" genererà un repository denominato "org_junit_junit" e un file chiamato "com_google_guava_guava ". Con use_repo
, se vuoi, puoi rinominarle nell'ambito del modulo, ad esempio in "guava" qui.
Definizione dell'estensione
Le estensioni modulo vengono definite in modo simile alle regole repository, utilizzando la funzione module_extension
.
Entrambi hanno una funzione di implementazione; Tuttavia, anche se le regole del repository hanno vari
attributi, le estensioni modulo hanno un numero di
tag_class
es, ognuno dei quali ha un
numero di attributi. Le classi di tag definiscono gli schemi per i tag utilizzati da questa estensione. Per continuare con il nostro esempio dell'ipotetica estensione "maven" descritta in precedenza:
# @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},
)
Queste dichiarazioni chiariscono che i tag maven.dep
e maven.pom
possono essere specificati utilizzando lo schema dell'attributo descritto in precedenza.
La funzione di implementazione è simile a una macro WORKSPACE
, con la differenza che ha un oggetto module_ctx
che concede l'accesso al grafico delle dipendenze e a tutti i tag pertinenti. La funzione di implementazione
deve quindi chiamare le regole repository per generare repository:
# @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]
Nell'esempio sopra, analizzeremo tutti i moduli del grafico delle dipendenze
(ctx.modules
), ognuno dei quali è un oggetto
bazel_module
il cui tags
mostra
tutti i tag maven.*
nel modulo. In seguito, richiamo l'utilità CLI
Coursier per contattare Maven ed eseguire la risoluzione. Infine, utilizziamo la risoluzione
per creare un numero di repository utilizzando l'ipotetica regola del
repository maven_single_jar
.
Link esterni
- Revisione revisione dipendenze Bazel (documento di progettazione originale Bzlmod)
- Criteri e procedure del Registro di sistema centrale di Bazel
- Repository GitHub di Bazel Central Registry
- BazelCon 2021 parla su Bzlmod