In molti casi sono sufficienti i comandi base zig build-exe, zig build-lib, zig build-obj e zig test. A volte però un progetto necessita di un ulteriore livello di astrazione per gestire la complessità del compilare da sorgente.
Per esempio, potrebbe verificarsi una di queste situazioni:
La riga di comando diventa troppo lunga e complessa, e vuoi trascriverla altrove.
Vuoi compilare molte cose, o il processo di compilazione ha molti passaggi.
Vuoi sfruttare processi paralleli e caching per ridurre il tempo di compilazione.
Vuoi esporre delle opzioni di configurazione per il progetto.
Il processo di compilazione è differente a seconda del sistema di destinazione e altre opzioni.
Il tuo progetto ha dipendenze da altri progetti.
Vuoi evitare dipendenze superflue da cmake, make, shell, msvc, python e altro, rendendo il progetto accessibile a più contributori.
Vuoi fornire un pacchetto usufruibile da terzi.
Vuoi mettere a disposizione a strumenti come gli IDE un modo standardizzato per comprendere semanticamente come compilare il progetto.
Se qualunque di queste è applicabile, il progetto trarrà beneficio dal Zig Build System.
Per iniziare
Eseguibile semplice
Questo build script crea un eseguibile a partire da un file Zig contenente una funzione main pubblica.
Installare artefatti di build
Il build system di Zig, come la maggior parte dei build system, è basato sul modellare il progetto come un grafo aciclico diretto (DAG) di "step", che vengono eseguiti in parallelo.
Di default, lo step principale nel grafo è lo step Install, il cui scopo è copiare gli artefatti di build nella loro destinazione finale. Lo step di installazione parte senza dipendenze, quindi eseguendo zig build non accade nulla. Il build script di un progetto deve aggiungere elementi all'insieme delle cose da installare, ed è ciò che fa la chiamata alla funzione installArtifact qui sopra.
In questo output sono presenti due cartelle generate: .zig-cache and zig-out. La prima contiene file che renderanno le compilazioni successive più rapide, ma questi file non sono pensati per essere tracciati in un sistema di controllo versione; questa cartella può essere completamente eliminata in qualunque momento senza conseguenze.
La seconda cartella generata, zig-out, è un "prefisso di installazione". Questo è correlato al concetto standard di gerarchia del file system. Questa cartella non è scelta dal progetto, ma dall'utente del comando zig build con la flag --prefix (-p per abbreviare).
Tu, come responsabile del progetto, scegli cosa conterrà questa cartella, ma l'utente sceglie dove installarla nel proprio sistema. Il build script non può definire percorsi di output assoluti perchè questo ostacolerebbe il caching, concorrenza e composibilità, oltre a infastidire l'utente finale.
Aggiungere uno step per eseguire l'applicazione
È pratica comune aggiungere uno step Run, per permettere di eseguire l'applicazione principale direttamente dalla riga di comando.
Le basi
Opzioni fornite dall'utente
Usa b.option per rendere il build script configurabile dagli utenti finali e anche da altri progetti che dipendono dal tuo progetto come pacchetto.
Nota la presenza di queste righe:
Project-Specific Options:
-Dwindows=[bool] Target Microsoft Windows
Questa parte del menù di aiuto è generata automaticamente in base alla logica in build.zig. Gli utenti possono scoprire le opzioni di configurazione del build script in questo modo.
Opzioni di configurazione standard
Nella sezione precedente abbiamo usato una flag booleana per indicare una compilazione per Windows. Possiamo fare ancora di meglio.
La maggior parte dei progetti vuole rendere possibile modificare la piattaforma di destinazione e le opzioni di ottimizzazione. Per incoraggiare una convenzione standard nella nomenclatura di queste opzioni, Zig fornisce le funzioni standardTargetOptions e standardOptimizeOption.
Le opzioni di destinazione standard permettono a chi esegue zig build di scegliere la piattaforma per cui compilare. Di default è permessa qualunque piattaforma, escluderle tutte significa compilare per il sistema host (quello usato per la compilazione). Sono disponibili ulteriori opzioni per restringere l'insieme di piattaforme supportate.
Le opzioni di ottimizzazione standard permettono a chi esegue zig build di scegliere tra Debug, ReleaseSafe, ReleaseFast e ReleaseSmall. Di default il build script non considera nessuna delle opzioni di rilascio come preferibile, quindi l'utente deve prendere una decisione per poter creare una build di rilascio.
Ora il nostro menù --help contiene più elementi:
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag)
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
È del tutto possibile creare queste opzioni usando direttamente b.option, ma questa API fornisce una convenzione comune per i nomi di queste opzioni, che sono utilizzate di frequente.
Nell'output del nostro terminale, osserva che abbiamo passato -Dtarget=x86_64-windows -Doptimize=ReleaseSmall. In confronto al primo esempio, ora vediamo file diversi nel prefisso di installazione:
zig-out/
└── bin
└── hello.exe
Opzioni per la compilazione condizionale
Per passare delle opzioni dal build script al codice Zig del progetto, usa lo step Options.
In questo esempio, i dati forniti da @import("config") sono noti in fase di compilazione, impedendo l'innesco di @compileError. Se avessimo passato -Dversion=0.2.3 oppure omesso l'opzione, avremmo visto la compilazione di app.zig fallire con l'errore "too old".
Libreria statica
Questo build script crea una libreria statica di codice Zig, e inoltre crea un eseguibile di altro codice Zig che usa la libreria.
In questo caso, viene installata solo la libreria statica:
zig-out/
└── lib
└── libfizzbuzz.a
Però, se guardi attentamente, il build script contiene un'opzione per installare anche la demo. Se passiamo anche l'opzione -Denable-demo, allora dovremmo vedere questo nel prefisso di installazione:
zig-out/
├── bin
│ └── demo
└── lib
└── libfizzbuzz.a
Nota che nonostante la chiamata non condizionale a addExecutable, il build system non perde tempo compilando l'eseguibile demo a meno che sia richiesto con -Denable-demo, perchè il build system è basato su un grafo aciclico diretto (DAG) in cui gli archi rappresentano dipendenze.
Libreria dinamica
Qui usiamo gli stessi file dell'esempio sulla libreria statica, eccetto il file build.zig che ha delle modifiche.
Come nell'esempio della libreria statica, per ottenere un link dell'eseguibile alla libreria, usa codice come questo:
exe.linkLibrary(libfizzbuzz);
Testing
I singoli file possono essere testati direttamente con zig test esempio.zig. Casi d'uso più complessi possono essere risolti orchestrando il testing mediante il build scriot.
Quando si usa il build script, gli unit test sono suddivisi in due step differenti nel grafo di compilazione, lo step Compile e lo step Run. Senza una chiamata a addRunArtifact, che definisce una dipendenza tra questi due step, gli unit test non verranno eseguiti.
Lo step Compile può essere configurato come qualunque eseguibile, libreria o file oggetto, per esempio mediante linking di librerie di sistema, impostando le opzioni di destinazione, o aggiungendo ulteriori unità di compilazione.
Lo step Run può essere configurato come qualunque altro step Run, per esempio si può evitare l'esecuzione quando il sistema host non può usare gli eseguibili.
Quando si utilizza il build system per eseguire gli unit test, il build runner e il test runner comunicano via stdin e stdout per eseguire più insiemi di test in parallelo, e riportare i fallimenti dei test in modo chiaro senza che i vari output siano mischiati tra loro. Questa è una delle ragioni per cui scrivere nello standard out nei test è problematico - interferisce con questo canale di comunicazione. D'altro canto, questo meccanismo permetterà di implementare una nuova funzionalità, ovvero un unit test può verificare che avvenga un panic.
In questo caso potrebbe essere utile abilitare skip_foreign_checks per gli unit test:
Per le necessità dei mantenitori di progetti upstream, ottenere queste librerie mediante il Zig Build System risulta in minori attriti e mette i poteri di configurazione in mano ai suddetti mantenitori. Chiunque compili in questo modo avrà risultati riproducibili, consistenti con ciò che ottengono gli altri, oltre a funzionare su ogni sistema operativo con persino supporto alla cross-compilazione. inoltre, questo permette al progetto di decidere con precisione chirurgica l'esatta versione dell'intero albero di dipendenze da usare per la compilazione. Questo è generalmente considerato il modo preferibile di dipendere da librerie esterne.
Invece, per le necessità di creare pacchetti software per repository come Debian, Homebrew o Nix, è obbligatorio il linking di librerie di sistema. Quindi, i build script devono rilevare la modalità di build e fare le opportune configurazioni.
Gli utenti di zig build possono usare --search-prefix per fornire ulteriori cartelle da considerare "directory di sistema" allo scopo di rilevare librerie statiche e dinamiche.
Generare file
Eseguire tool di sistema
Questa versione di "hello world" si aspetta di trovare un file word.txt in un certo percorso, e vogliamo usare un tool di sistema per generarlo a partire da un file JSON.
Tieni a mente che le dipendenze di sistema renderanno il tuo progetto più difficile da compilare per i tuoi utenti. Questo build script per esempio dipende da jq, che di default non è presente nella maggior parte delle distribuzioni Linux e potrebbe essere un tool poco familiare per gli utenti Windows.
La prossima sezione sostituirà jq con un tool scritto in Zig incluso nel codice sorgente del progetto, che è l'approccio preferibile.
words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
Output
zig-out
├── hello
└── word.txt
Nota come captureStdOut crea un file temporaneo con l'output dell'invocazione di jq.
Eseguire i tool del progetto
Questa versione di "hello world" si aspetta di trovare un file word.txt allo stesso percorso di prima, o vogliamo generarlo in fase di compilazione invocando un programma scritto in Zig per elaborare un file JSON.
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
Output
zig-out
├── hello
└── word.txt
Produrre asset per @embedFile
Questa versione di "hello world" vuole usare @embedFile su un asset generato in fase di compilazione, che produrremo usando un tool scritto in Zig.
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
Output
zig-out/
└── bin
└── hello
Generare codice Zig
Questo build script usa un programma scritto in Zig per generare un file Zig, per poi esporlo al programma principale (main.zig) come modulo/dipendenza.
Output
zig-out/
└── bin
└── hello
Gestire uno o più file autogenerati
Lo step WriteFiles permette di generare uno o più file che condividono una cartella padre. La cartella generata si troverà dentro la cartella .zig-cache locale, e ogni file generato è individualmente disponibile come std.Build.LazyPath. Anche la cartella padre stessa è disponibile come LazyPath.
Questa API supporta la scrittura di qualunque stringa nella cartella generata. oltre al copiare file al suo interno.
Output
zig-out/
└── project.tar.gz
Modificare e sovrascrivere i file sorgente
Non capita spesso, ma a volte un progetto traccia i file generati in un sistema di controllo versione. Questo può essere utile quando i file generati vengono modificati raramente e il processo di aggiornamento richiede dipendenze di sistema scomode, ma solo durante il processo di aggiornamento.
Fai molta attenzione con questa funzionalità; non dovrebbe essere utilizzata durante il normale processo di compilazione, ma come tool eseguito da uno sviluppatore con l'intenzione di aggiornare i file sorgente, che saranno dunque tracciati nel sistema di controllo versione. Se fatto durante il normale processo di compilazione, causerà bug di caching e concorrenza.
Dopo aver eseguito questo comando, src/protocol.zig verrà aggiornato e sovrascritto.
Altri esempi
Build per più piattaforme per creare un rilascio
In questo esempio cambieremo alcune opzioni di default durante la creazione di uno step InstallArtifact, allo scopo di mettere la build di ciascuna piattaforma in una sottocartella separata nel percorso di installazione.