• Non ci sono risultati.

Un esempio di plugin in Maven

 1 < p l u g i n > 2 < g r o u p I d > com . m y c o m p a n y . e x a m p l e < / g r o u p I d > 3 < a r t i f a c t I d > display - maven - p l u g i n < / a r t i f a c t I d > 4 <v e r s i o n> 1.0 < /v e r s i o n> 5 < e x e c u t i o n s > 6 < e x e c u t i o n > 7 < p h a s e > process - test - r e s o u r c e s < / p h a s e > 8 < g o a l s >

9 < goal > time < / goal > 10 < / g o a l s >

11 < / e x e c u t i o n > 12 < / e x e c u t i o n s > 13 < / p l u g i n >

 

Ci sono poi ulteriori plugins chiamati Reporting plugins, che verranno ese- guiti durante la generazione del sito e dovrebbero essere configurati nella sezione <reporting/> del POM.

Build Lifecycle

Maven mette a disposizione 3 Build Lifecycle standard: 1. default: gestisce la distribuzione del progetto. 2. clean: gestisce la pulizia del progetto.

3. site: crea la documentazione del sito del progetto.

Ognuno di questi lifecycles `e definito da un elenco di fasi che al suo interno rappresentano uno stato del ciclo.

Ad esempio il lifecycle default comprende le seguenti fasi21:

• validate: validare la correttezza del progetto. • compile: compilare il codice sorgente.

• test: testare il codice sorgente compilato utilizzando un adeguato fra- mework di unit test.

2.2. DICHIARATIVI 21 • package: prende il codice compilato e lo impacchetta in un suo formato

distribuibile, ad esempio un file JAR.

• verify: esegue eventuali controlli sui risultati dei test di integrazione per garantire il rispetto dei criteri di qualit`a.

• install: installa il package nel repository locale, per utilizzarlo localmente come dipendenza in altri progetti.

• deploy: distribuisce il package finale, copiandolo nel repository remoto per condividerlo con altri sviluppatori e progetti.

Queste fasi vengono eseguite in sequenza per completare il build lifecycle di default. Lo sviluppatore esegue da riga di comando la fase che gli interessa svolgere per il proprio progetto; ad esempio se si vuole eseguire il comando mvn verify, ven- gono eseguite in ordine sequenziale, tutte le fasi precedenti (validate, compile, package, ect.) a verify ed in pi`u altre fasi intermedie che qui non sono citate22. Si possono inoltre eseguire insieme lifecycles differenti come ad esempio per la chiamata mvn clean deploy che prima pulisce il progetto e poi lo ricrea con la distribuzione finale nel repository condiviso. Il comando funziona anche nello scenario di un progetto formato da pi`u sottoprogetti, Maven attraverser`a ogni sot- toprogetto eseguendo prima clean e poi deploy comprese tutte le fasi antecedenti.

2.2.3

Bazel

Bazel `e un build system open-source parte di Blaze, il sistema di build utilizzato da Google. Esso `e stato concepito per operare su progetti Java, Android, iOS, C++, Go. Questo build system ricostruisce solo ci`o che `e necessario sfruttando le proprie caratteristiche di poter lavorare con l’esecuzione parallela, il caching locale e distribuito facendo anche uso di un’ottimizzata analisi delle dipendenze23. Nello specifico, alcune delle caratteristiche di Bazel comprendono24:

• orientamento all’alto livello. A differenza di altri build systems, evita chia- mate a compilatori o linker esterni.

• mantenimento della storia passata. Mantiene lo storico dei comandi utilizzati per le build precedenti.

• multiplatform-based. Pu`o costruire file binari e packages distribuibili nei pi`u comuni sistemi operativi come Windows, macOS, Linux.

22https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html 23https://www.bazel.build/

Bazel definisce il proprio file di configurazione principale chiamato BUILD che comunica quali elementi compilare, quali sono le loro dipendenze e come costruir- li25.

Il risultato `e quello di generare il grafo delle dipendenze (action graph) e deter- minare le azioni che vengono svolte per creare l’output finale. `E descritto tramite un domain specific language (DSL) chiamato Starlark, un dialetto di Python destinato all’uso come linguaggio di configurazione. Viene utilizzato sia come no- tazione per i file BUILD che per estendere Bazel come logica personalizzata per supportare nuovi linguaggi e compilatori. Nel codice 1 si ha un esempio di file script di Bazel in Starlark26.

Bazel utilizza come elementi principali la build rule e i build target. Rules

Una rule `e l’implementazione di una funzione che registra le azioni da eseguire sugli artefatti di input per produrre una serie di artefatti di output. Un’azione `e responsabile della reale operazione di build, per tale motivazione utilizza la maggior parte delle risorse necessarie per portarla a termine.

Bazel offre allo sviluppatore alcune rules di default che forniscono il supporto per certi linguaggi. Come si nota nel codice 1 la rule (cc library) per C++ definisce una libreria che prevede una lista variabile di argomenti. Nell’esempio considerato sono presenti il nome in name, un file sorgente in srcs e un header in hdrs. Una rule pu`o anche essere implementata dallo sviluppatore con la parola chiave rule27.

Una rule definisce al suo interno un file target che rappresenta un’unit`a di costruzione, rappresentata in maniera univoca dal valore assegnato a name. Ogni rule `e in grado, a partire dall’elaborazione dei propri file di input, di generare file di output chiamati generated files o anche file derivati. Tali file possono essere a loro volta utilizzati come input per ulteriori rules definendo vere e proprie catene di rules.

Per le rules dei file binari (cc binary) e dei test (cc test) si prevedono analo- gamente nome e sorgente, con l’aggiunta dell’argomento deps che rappresenta la lista delle dipendenze.

Targets

Un target `e l’unit`a di costruzione che viene generata da Bazel utilizzando file sorgenti ed eventualmente file derivati in accordo a quanto specificato nella relativa

25https://docs.bazel.build/versions/4.0.0/build-ref.html

26https://docs.bazel.build/versions/4.0.0/skylark/language.html 27https://docs.bazel.build/versions/4.0.0/skylark/rules.html

2.2. DICHIARATIVI 23 package(default_visibility = ["//visibility:public"]) cc_library( name = "hello-lib", srcs = ["hello-lib.cc"], hdrs = ["hello-lib.h"], ) cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [":hello-lib"], ) cc_test( name = "hello-success_test", srcs = ["hello-world.cc"], deps = [":hello-lib"], ) cc_test( name = "hello-fail_test", srcs = ["hello-fail.cc"], deps = [":hello-lib"], )

Listing 1: Un file script di Bazel

rule.

Questo consente di determinare la catena delle dipendenze relative ad un progetto attraverso il grafo dei targets (target graph)28.

Fasi di sviluppo

Quando viene eseguita una build o un test, Bazel si avvale di un modello di valutazione che `e formato da 3 fasi da seguire29:

1. Caricamento

28https://docs.bazel.build/versions/4.0.0/build-ref.html

2. Analisi 3. Esecuzione

Caricamento Nella fase di caricamento (loading phase) vengono caricati e va- lutati tutti i BUILD file di script che fanno parte della build di un progetto.

La concretezza della fase avviene attraverso l’istanziamento delle rules alla cui chiamata ne scaturir`a l’aggiunta al grafo delle dipendenze (target graph) che qui viene prodotto30.

Analisi La fase di analisi (analysis phase) esegue il codice delle rules e istanzia le actions da loro registrate.

Processa il target graph costruito nella fase di caricamento e genera un grafo delle azioni (action graph) che determina l’ordine della loro esecuzione. Il grafo delle azioni pu`o includere sia gli artefatti che esistono come codice sorgente, sia quelli che in questa fase vengono generati ma che non sono menzionati nel build file31

Esecuzione La fase di esecuzione (execution phase) `e quella dove vengono ese- guite le actions nell’ordine stabilito dal grafo prodotto nella fase di analisi.

Le azioni invocano elementi eseguibili (compilatori, scripts) per leggere e scri- vere gli artefatti di output. Se un file richiesto viene perduto oppure un comando non riesce a generare un output, la build fallisce.

In questa fase vengono anche eseguiti i test31

2.3

Funzionali

2.3.1

Gradle

Gradle nato nel 2008 e sviluppato da Gradleware, `e un build system concepito con l’idea di prendere i migliori aspetti dei build systems imperativi come Ant e quelli dichiarativi come Maven e Ivy. Combina flessibilit`a ed estensibilit`a se- guendo la filosofia “convention over configuration”, supportando la gestione delle dipendenze [7].

Le principali caratteristiche di Gradle sono32:

• il supporto alle alte performance andando ad eseguire solamente i task di cui gli input e output sono stati cambiati.

30https://docs.bazel.build/versions/4.0.0/skylark/concepts.html 31https://docs.bazel.build/versions/4.0.0/glossary.html

2.3. FUNZIONALI 25 • l’utilizzo di una build cache per abilitare il riutilizzo degli output di un task

da esecuzioni precedenti o da macchine differenti.

• `e facilmente estendibile per consentire lo sviluppo di task e modelli di build personalizzati.

• attraverso una build scan si forniscono informazioni sull’esecuzione di build per identificare i problemi, anche da poter condividere con altri sviluppatori. • viene eseguito sulla Java Virtual Machine (JVM) ed `e un vantaggio per gli

utenti della piattaforma Java potendo usare le API nella logica di build. • non si limita ai soli progetti JVM ma fornisce anche il supporto per quelli

nativi (C/C++) ed `e il build system ufficialmente utilizzato per Android. Pur facendo propri i concetti presi dai suoi predecessori, Gradle definisce un suo approccio basato sulla scrittura attraverso un Domain Specific Language (DSL) che permette ad un build script di poter essere espresso come codice testabile e superare l’impiego di un file XML che ha un punto debole: `e ottimo per descrivere i dati gerarchici, ma non riesce a esprimere il flusso del programma e la logica condizionale. Quando un build script cresce in complessit`a, mantenere il codice di build diventa un incubo [7].

Gradle mette a disposizione 2 linguaggi DSL per poter sviluppare i propri build scripts:

• Groovy:33 linguaggio dinamico (con anche aspetti statici) per la Java plat- form, usato maggiormente per la scrittura degli scripts. Un build file `e identificato dall’estensione .gradle.

• Kotlin:34 linguaggio multiplatform object-oriented, staticamente tipizzato, che aggiunge nuove funzionalit`a al predecessore Java, da cui prende spunto. Un build file `e identificato dall’estensione .gradle.kts.

In particolare, l’impiego di Kotlin con l’introduzione delle lambda-expressions, dei nullable-types e delle extension functions permette allo sviluppatore che `e gi`a a co- noscenza del linguaggio Java, di utilizzare elementi di programmazione funzionale che rendono Gradle un build system di tale tipologia.

33https://groovy-lang.org/

Un build script di Gradle `e formato principalmente da 3 elementi35:

1. Project: rappresenta ci`o che si vuole fare con Gradle. Non necessariamente deve essere una cosa che si vuol costruire.

2. Task: `e l’unit`a atomica che rappresenta una parte del lavoro che viene svolto. Ogni progetto (project) `e formato da uno o pi`u task.

3. Plugin: estende la capacit`a di un progetto consentendo un pi`u alto grado di modularizzazione, incapsulando la logica imperativa permettendo di essere il pi`u dichiarativo possibile36.

Project

Ogni build di Gradle `e fatto da uno o pi`u project. L’elemento Project `e il tipo di un particolare oggetto che viene assemblato per ogni progetto che compone la build e delegato (delegate object ) all’atto di esecuzione dello script che lo rappresenta. Ha una relazione uno a uno con il file di script che lo descrive a seconda del DSL scelto per la sua implementazione (Groovy o Kotlin)37. Un esempio di build file in Kotlin viene mostrato nel codice 2.5.

Dipendenze Il blocco dependencies dichiara le dipendenze che in Gradle ven- gono applicate per ambiti (scope) specifici. Un ambito viene anche chiamato con- figuration che viene identificato con un nome univoco ed `e a sua volta definito da un plugin che ne d`a un proprio set di dipendenze38.

il plugin application applica implicitamente anche il Java plugin di base che definisce le seguenti configurazioni di dipendenze39:

• compileOnly dove vengono dichiarate le dipendenze che sono richieste a tempo di compilazione e non a runtime.

• runtimeOnly dove vengono dichiarate solamente le dipendenze richeste a runtime.

• implementation dove vengono dichiarate le dipendenze richieste in entrambi i tempi di sviluppo del progetto.

35https://docs.gradle.org/current/userguide/tutorial using tasks.html 36https://docs.gradle.org/current/userguide/plugins.html

37https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

38https://docs.gradle.org/current/userguide/declaring dependencies.html 39https://docs.gradle.org/current/userguide/java plugin.html

2.3. FUNZIONALI 27

Listato 2.5: Un esempio di build file di Gradle

 1 p l u g i n s { 2 a p p l i c a t i o n 3 k o t l i n (" jvm ") v e r s i o n " 1 . 3 . 5 0 " 4 } 5 r e p o s i t o r i e s { 6 m a v e n C e n t r a l () 7 } 8 d e p e n d e n c i e s { 9 i m p l e m e n t a t i o n ( k o t l i n (" s t d l i b ") ) 10 i m p l e m e n t a t i o n (" com . o m e r t r o n : t h e t v d b a p i :[1.6 , 1.7] ") 11 r u n t i m e O n l y (" j a v a x . xml . ws : jaxws - api :+ ") 12 } 13 a p p l i c a t i o n { 14 m a i n C l a s s N a m e = " it . u n i b o . ci . P r i n t S e r i e s K t " 15 }  

testCompileOnly, testRuntimeOnly, testImplementation sono le configurazio- ni che hanno lo stesso comportamento precedentemente descritto, ma per dichia- rare le dipendenze dei test.

In Gradle ci sono diversi tipi di dipendenze che si possono dichiarare, quella pi`u comunemente utilizzata `e il modulo. Un modulo `e una parte di software che evolve nel tempo, di solito memorizzato in un repository40. L’utilizzo di una dipendenza pu`o essere una dipendenza dichiarata nel build file o una dipendenza transitiva presente in un grafo delle dipendenze con la corrispondente configurazione. Gradle offre entrambe le capacit`a di visualizzazione delle dipendenze tramite scansioni delle build (Build scans) o come istruzioni da riga di comando41.

La risoluzione delle dipendenze `e un processo che consiste in 2 fasi che vengono ripetute fino al completamento del grafo delle dipendenze42:

1. quando una nuova dipendenza viene aggiunta al grafo, viene eseguita la ri- soluzione dei conflitti per determinare quale versione deve essere aggiunta al grafo.

2. quando una specifica dipendenza viene identificata come parte del grafo, se ne recuperano i metadati in modo che le sue dipendenze possano essere aggiunte a loro volta.

40https://docs.gradle.org/current/userguide/dependency management terminology.html 41https://docs.gradle.org/current/userguide/viewing debugging dependencies.html 42https://docs.gradle.org/current/userguide/dependency resolution.html

Listato 2.6: I vincoli delle dipendenze di Gradle  1 d e p e n d e n c i e s { 2 i m p l e m e n t a t i o n (" org . a p a c h e . h t t p c o m p o n e n t s : h t t p c l i e n t ") 3 c o n s t r a i n t s { 4 i m p l e m e n t a t i o n (" org . a p a c h e . h t t p c o m p o n e n t s : h t t p c l i e n t : 4 . 5 . 3 ") { 5 b e c a u s e (" p r e v i o u s v e r s i o n s have a bug i m p a c t i n g this a p p l i c a t i o n ") 6 } 7 i m p l e m e n t a t i o n (" commons - c o d e c : commons - c o d e c : 1 . 1 1 ") { 8 b e c a u s e (" v e r s i o n 1.9 p u l l e d from h t t p c l i e n t

has bugs a f f e c t i n g this a p p l i c a t i o n ")

9 }

10 } 11 }

 

Quando si esegue la risoluzione delle dipendenze, Gradle gestisce due tipi di conflitti43:

• conflitti di versione: quando due o pi`u dipendenze richiedono una determi- nata dipendenza ma con versioni diverse.

• conflitti di implementazione: quando il grafo delle dipendenze contiene un modulo che fornisce la stessa implementazione o capacit`a.

`

E abbastanza comune che i problemi con la gestione delle dipendenze riguardino le dipendenze transitive, quelle di cui ne ha bisogno un componente soltanto perch`e un’altra sua dipendenza ne ha il bisogno. Gradle risolve questo problema con l’introduzione dei vincoli sulle dipendenze (dependency constraints) come mostrato nel codice 2.644.

Un vincolo permette di definire la versione o un range di versioni sia per le dipendenze dichiarate che per quelle transitive, quando Gradle prova a risolvere una dipendenza nella versione di un modulo, tiene in considerazione tutti questi aspetti che trova, selezionando la versione di numero maggiore (highest version) che soddisfa tutte le condizioni, contrariamente a Maven che invece ne prende

43https://docs.gradle.org/current/userguide/dependency resolution.html 44https://docs.gradle.org/current/userguide/dependency constraints.html

Documenti correlati