Nell'articolo precedente, Introduzione a Maven, abbiamo visto le potenzialità di questo strumento di compilazione. È implementato in Java e lo rende indipendente dalla piattaforma. Vediamo in qusto tutorial come preparare l'ambiente adatto a questo strumento e come utilizzarlo.
Prima di tutto devi avere un SDK Java installato nella macchina e impostare la variabile d'ambiente JAVA_HOME in modo che punti a quest'ultimo. Una delle tante versione è Amazon Corretto con le relative istruzioni di installazione.
Successivamente bisogna installare Maven. Per un'installazione manuale è possibile scaricare Maven dalla pagina ufficiale. Estrai la distribuzione scaricata in una cartella selezionata sul tuo computer e aggiungi il percorso della directory MAVEN_HOME/bin, che si trova all'interno della directory maven, nelle variabili d'ambiente di windows. Questo per poter utilizzare maven da riga di comando a partire da qualsiasi punto del computer. Consulta l'installazione ufficiale di Maven per una guida dettagliata all'installazione.
Per verificare se Maven sia stato installato correttamente, apri la console ed esegui il seguente comando:
mvn -version
Se tutto è configurato correttamente riceverai la seguente response:
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: C:\Program Files (x86)\apache-maven-3.6.3\bin\..
Java version: 1.8.0_282, vendor: Amazon.com Inc., runtime: C:\Program Files\Amazon Corretto\jdk1.8.0_282\jre
Default locale: it_IT, platform encoding: UTF8
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
Per impostazione predefinita, la configurazione di un progetto Maven viene eseguita tramite uno o più file di configurazione pom.xml. Maven usa uno stile dichiarativo per descrivere la build, cioè descrive cosa dovrebbe essere costruito non come è costruito. Ciò consente a Maven di utilizzare l'impostazione predefinita per eseguire i passaggi di creazione.
Il file pom definisce:
- identificatori per il progetto da costruire
- proprietà rilevanti per la configurazione della build
- plugin che forniscono funzionalità per la compilazione tramite una sezione di compilazione.
- librerie e dipendenze del progetto tramite la sezione delle dipendenze
Ogni progetto ha il proprio file pom. Puoi configurare Maven per creare un progetto o più progetti. Un multiprogetto tipicamente definisce un file pom nella radice della directory ed elenca i singoli progetti in una sezione definita come moduli.
Il risultato di una compilazione è chiamato artefatto e può essere, ad esempio, un file JAR eseguibile o un file zip.
Un file pom minimo per un singolo progetto non definisce proprietà, dipendenze né plug-in aggiuntivi. Vediamo un'esempio:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> // 4.0.0 è l'ultima versione della struttura del file pom, utilizzabile per Maven 2.x o 3.x
<groupId>it.saverioriotto.maven.gruppo</groupId> // Identificatore univoco dell'organizzazione o del progetto
<artifactId>it.saverioriotto.maven.esempio</artifactId> // Nome del progetto che viene realizzato
<version>1.0-SNAPSHOT</version> // Definisce la versione del progetto
<name>it.saverioriotto.maven.esempio</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source> // Imposta il livello del compilatore Java per il progetto
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
Un progetto Maven utilizza groupId, artifactId, version (noto anche come GAV) e la proprietà packaging per identificare in modo univoco un componente Maven. Vediamo nel dettagllio questi attributi:
groupId: definisce un nome di base univoco dell'organizzazione o del gruppo che ha creato il progetto. Questo è normalmente un nome di dominio inverso o il nome di un progetto open source. Per la generazione di nuovi progetti, il groupId definisce anche il pacchetto della classe principale.
artifactId: definisce il nome univoco del progetto all'interno di groupId. Se generi un nuovo progetto tramite Maven, questo viene utilizzato anche come cartella principale per il progetto.
version: definisce la versione del progetto. Se viene compilata una nuova versione del progetto, questa versione dovrebbe cambiare in modo che gli utilizzatore possano distinguere le diverse versioni.
packaging: definisce il tipo di pacchetto. Questo potrebbe essere ad esempio un file jar, war o ear. Se il tipo di packaging è pom, Maven non crea nulla e lo utilizza solo come metadati.
Ad esempio, puoi utilizzare GAV per specificare quale versione esatta di una libreria desideri utilizzare.
Le coordinate Maven complete sono spesso scritte nel seguente formato: groupId:artifactId:packaging:version.
Durante la fase iniziale di una build, Maven verifica se disponi della versione specificata di tutte le dipendenze degli artefatti richieste e dei plug-in Maven. Se necessario, li recupera da un repository Maven.
Scarica questi artefatti e plug-in in un repository locale. Il repository locale predefinito si trova nella home directory dell'utente nella cartella .m2/repository. Se un artefatto o un plug-in è disponibile nel repository locale, Maven lo utilizza per la compilazione per evitare traffico di rete non necessario. Vedi Introduzione a Maven.
Ogni progetto può definire dipendenze utilizzando l'identificatore univoco (GAV) della libreria che richiede. Vediamo come inserire le dipendenze all'interno del file pom:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
Durante una compilazione, Maven cerca di risolvere le dipendenze dei moduli che vengono compilati. Per risolvere le dipendenze, Maven utilizza le seguenti fonti nell'ordine indicato:
- Progetti che sono inclusi nella stessa sezione Maven (il cosiddetto Maven reactor)
- Repository locale
- Repository centrale di Maven
Maven supporta dipendenze dirette e transitive. Le dipendenze dirette sono quelle esplicitamente incluse nel file pom del progetto. Le dipendenze transitive sono dipendenze richieste a sua volta dalle dipendenze dirette.
Quando specifichi nel file pom le librerie esterne da cui dipende il tuo progetto usando il loro GAV (groupId, artifactId e versione), Maven le scarica, le inserisce nel tuo repository locale e le rende disponibili per la build del progetto. Verranno inoltre scaricate e rese disponibili le dipendenze transitive delle librerie richieste.
È inoltre possibile definire una posizione (esterna a un repository Maven) per una libreria.
<dependency>
<groupId>filesystemlib</groupId>
<artifactId>filesystemlib</artifactId>
<scope>system</scope> // Richiede che la libreria sia posizionata su un percorso di sistema
<version>1.0</version>
<systemPath>${basedir}\libs\filesystemlib.jar</systemPath>
</dependency>
Possiamo definire anche le dipendenze dello snapshot. Queste sono librerie che sono costantemente in sviluppo. Maven scarica queste istantanee su ogni build, anche se una versione corrispondente è già disponibile nel repository locale. Per specificare tale lib bisogna settare la versione nel seguente modo:
1.0-SNAPSHOT
A volte le tue dipendenze hanno dipendenze transitive in conflitto. Ad esempio, la libreria A richiede la libreria C nella versione 1.0.0 e la libreria B la richiede nella versione 1.1.0. Per utilizzare la libreria C nella versione 1.0.0 è possibile escludere C dalle dipendenze di A. Vediamo come:
<dependency>
<groupId>it.saverioriotto.maven.group</groupId>
<artifactId>it.saverioriotto.maven.esempio</artifactId>
<version>1.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>c.groupId</groupId>
<artifactId>c.artifactId</artifactId>
</exclusion>
</exclusions>
</dependency>
I plugin Maven forniscono le funzionalità utilizzate nella build Maven. Ogni plugin Maven fornisce uno o più obiettivi. Un obiettivo è una "unità di lavoro" in Maven. È possibile eseguire gli obiettivi in modo indipendente o come parte di una più ampia catena di obiettivi.
Possono definire parametri che possono avere valori predefiniti. Vengono eseguiti in base alle informazioni trovate nel pom del progetto, ad esempio, il goal compiler:compile controlla il pom per i parametri inseriti. Gli obiettivi possono essere associati a una fase del ciclo di vita.
Puoi aggiungere obiettivi alle fasi del ciclo di vita configurando più plug-in Maven e aggiungendoli a un ciclo di vita nel tuo file pom. È necessario specificare quale obiettivo deve essere eseguito. Se il plug-in non specifica il ciclo di vita predefinito da eseguire, è necessario specificare anche la fase del ciclo di vita da eseguire.
<plugin>
<groupId>it.saverioriotto.maven</groupId>
<artifactId>saverioriotto-maven-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>checklinks</goal>
</goals>
</execution>
</executions>
</plugin>
Maven genera il suo output nella cartella di destinazione, "target/", di ciascun progetto. Questo output di compilazione non deve essere incluso nel sistema di controllo versione.
Quindi aggiungi questa directory alle tue risorse da ignorare. Ad esempio, se utilizzi Git come sistema di controllo versione, aggiungi la voce "target/" al file .gitignore nella radice di ogni progetto.
Vediamo adesso come creare un progetto Java con gli strumenti da riga di comando Maven. Crea un directory separata e posizionati all'interno tramite riga di comando.
Per evitare la modalità interattiva, tutte le proprietà richieste vengono passate direttamente al comando. Altrimenti Maven richiede tutti i parametri richiesti. Quindi inserisci il comando seguente in una riga nella shell ed esegui:
mvn archetype:generate -DgroupId=it.saverioriotto.maven.esempio \
-DartifactId=com.vogella.maven.first \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
Con questo comando Maven genera un progetto Java basato su un modello predefinito. Maven ha creato una classe App.java nella cartella ./src/main/, che è solo un semplice programma "Hello World". Ha anche creato una classe di test di esempio in ./src/test/. Nella cartella principale noterai il file pom.xml.
Adesso puoi compilare il tuo sorgente Java. Per questo posizionati da riga di comando nella directory principale del progetto ed esegui il comando Maven :
mvn compile
Maven in questa fase attraversa tutte le fasi del ciclo di vita, necessarie per la compilazione. Risolve le dipendenze, carica l'artefatto JUnit e crea i sorgenti. Esegue anche il test JUnit.
Ora puoi creare un file JAR eseguibile del progetto. L' obiettivo package crea un file JAR distribuibile. Per assicurarti che gli artefatti della build precedente vengano rimossi, puoi utilizzare l'obiettivo clean. Possono essere utilizzati anche in sequenza. Vediamo come:
mvn clean package
Una volta terminata la fase di package verrà generato il file JAR eseguibile all'interno della directory target/ del progetto. Vediamo come eseguirlo direttamete da riga di comando:
java -cp target/it.saverioriotto.maven.esempio-1.0-SNAPSHOT.jar it.saverioriotto.maven.esempio.App
Se tutto ha funzionato correttamente, l'output sulla riga di comando dovrà essere: Hello World!
Esecuzione della fase test
Se vogliamo eseguire solo il test del codice invece di eseguire una build completa con il packaging, è anche possibile eseguire solo le fasi di test del ciclo di vita di Maven tramite il seguente comando:
mvn test
Come accennato in precedenza, se vogliamo eseguire una fase pulita possiamo rimuovere tutti gli artefatti della build precedente (cartella ./target/ ), utilizzando il comando clean.
mvn clean
Nell'esecuzione del progetto in precedenza abbiamo dovuto specificare, da riga di comando, oltre che il file anche la classe principale di esecuzione. Per omettere quest'ultima è possibile inserirla all'interno del MANIFEST nel seguente modo:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>it.saverioriotto.maven.esempio.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
Successivamente puoi avviare direttamente la tua applicazione nel seguente modo:
java -jar target/it.saverioriotto.maven.esempio-1.0-SNAPSHOT.jar
Maven fornisce un plug-in, che può essere utilizzato per visualizzare un albero delle dipendenze sulla console o su un file di output. Ecco i comandi per le due soluzioni:
# Visualizzazione delle dipendenze direttamente in console
mvn dependency:tree -Dverbose
# Scrittura delle dipendenze in un file
mvn dependency:tree -Dverbose -DoutputFile=/home/saverioriotto/maven-dependencies
Ant utilizza uno stile imperativo per dichiarare le azioni che dovrebbe eseguire la build Ant. Specifica i comandi che Ant dovrebbe eseguire e la sequenza in cui dovrebbero avvenire. Ant non ha strumenti di gestione delle dipendenze per impostazione predefinita, devi utilizzare altri strumenti come Ivy o gestire manualmente il download delle dipendenze.
Maven usa uno stile dichiarativo che definisce cosa costruire e non come costruire. Se utilizza un layout predefinito per i progetti e fornisce il supporto integrato per la gestione delle dipendenze.
Invece un'altro strumento oggi molto diffuso, sia per velocità di utilizzo che performance, è Gradle. Fornisce le stesse funzionalità di Maven ma utilizza una sintassi concisa. Gradle supporta anche uno stile dichiarativo e imperativo oltre all'approccio dichiarativo di Maven.
Vedremo anche Gradle nel dettaglio in un successivo articolo.