Build Systems

Esercizi e soluzioni: https://github.com/unibo-lptsi-pss/pss-lab/releases/latest/

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

versione stampabile

Riconoscimenti

  • Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.

  • Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.

Quando il gioco si fa duro, i duri automatizzano

La gestione della compilazione potrebbe diventare complicata:

  • Sorgenti in molte cartelle
  • Divisi in molti package
  • Alcune librerie binarie di cui non abbiamo il sorgente

Comandi molto lunghi! Facile sbagliarsi, tedioso da fare a mano!

Soluzione: costruire degli script!

Script per compilare?

In fin dei conti, è solo software che, di mestiere, compila software

…a sua volta, da compilare o interpretare.

Fare uno script personalizzato è molto flessibile, ma se abbiamo molti progetti?

  • Copiarlo da tutte le parti vuol dire doverlo modificare in molti posti in caso di errore!
  • Viola un principio importante: Don’t Repeat Yourself (DRY)

La compilazione è ripetitiva!

Potremmo costruire un sistema che:

  1. Si aspetta che i sorgenti siano in un posto standard
    • Se li trova, allora compila, mettendo il risultato in un posto standard
  2. Se sono in un posto non standard, si configura per funzionare

Principio convention over configuration

Build systems

Sono software che si occupano di aiutare con le varie fasi della costruzione del software:

  • Ricerca e scaricamento di librerie (vediamo in futuro)
  • Compilazione (vediamo oggi)
  • Testing (vediamo in futuro)
  • Packaging (vediamo in futuro)
  • Generazione di documentazione (vediamo in futuro)
  • Delivery degli artefatti (non oggetto del corso)

Gradle

  • Un moderno build system
  • Supporta Java (oltre a C/C++, Scala, Kotlin…)
    • In tutto l’ecosistema, che include Android
  • Ne vedremo solo i rudimenti
    • Per noi è strumentale a costruire software Java
    • Impareremo come sfruttarlo per automatizzare le operazioni di cui sopra

La costruzione del software

Costruire sistemi software non è solo programmare. Dipendentemente dal sistema in esame, potrebbero servire:

  • Manipolazione e pre-processing del sorgente (inclusa generazione)
  • Verifica della qualità del sorgente
  • Gestione delle dipendenze
    • Ricerca, scaricamento, e importazione delle librerie
  • Compilazione
  • Manipolazione del binario compilato
  • Esecuzione dei test
  • Misurazione della qualità dei test (e.g., coverage)
  • Generazione della documentazione

In principio, si può anche fare a mano

  • ma richiederebbe molto tempo
  • …e gli umani si stancano presto di lavori noiosi e ripetitivi

Build automation

Automatizzazione del processo di costruzione del software

  • Di fatto, scrivere software che di lavoro fa manutenzione di altro software

Stili

  • Imperativo/Personalizzato
    • Tipicamente realizzato tramite script in qualche linguaggio di programmazione
    • Flessibile e configurabile
    • Difficile da adattare e riusare
  • Dichiarativo/Standardizzato
    • Tipicamente realizzato tramite un file di configurazione di un software dedicato alla build automation
    • Portabile e di semplice comprensione
    • Limitato dalle opzioni di configurazioni disponibili, e quindi poco flessibile

Convention over configuration

Principio per cui un certo sistema software ha una configurazione “ragionevole” di default, che può essere sovrascritta in caso di necessità

  • Induce la creazione di standard di fatto
    • La convenzione tende a diventare il modo “normale” di fare le cose per minimizzare la configurazione
  • Riduce le ripetizioni
  • Aumenta la portabilità!

Automatori ibridi

Sono sistemi che cercano di unire il meglio dei sistemi dichiarativi e imperativi

  • Il file di configurazione è in realtà uno script in un linguaggio di programmazione vero e proprio
  • Aprendolo sembra un file di testo con la configurazione
  • In realtà è uno script valido!
  • Quanto non specificato si assume come da convenzione
  • Quando si vuole personalizzare qualcosa, si ha a disposizione la “potenza di fuoco” di un linguaggio di programmazione vero e proprio

Esempi

  • Sbt, che si appoggia su Scala
  • Gradle, che si appoggia su Kotlin o Groovy

Il linguaggio host deve consentire di costruire dei Domain-Specific languages

  • ossia, essere così flessibile da permettere di costruire un “linguaggio nel linguaggio”

Gradle

  • Un moderno build system ibrido
    • Pilotato in Kotlin (preferibile) o in Groovy
  • Supporta Java (oltre a C/C++, Scala, Kotlin…)
    • In tutto l’ecosistema, che include Android
  • Ne vedremo solo le basi di utilizzo
    • Per noi è strumentale a costruire software Java
    • Impareremo come sfruttarlo per automatizzare le operazioni di cui sopra

Concetti base in Gradle: task, progetto, plugin

Progetto

Una directory contenente il file speciale build.gradle.kts e/o settings.gradle.kts, detti build file. La loro presenza segnala a Gradle che la cartella rappresenta un progetto

Plugin

Componente software contentente task pronti all’uso. Gradle contiene diversi plugin pronti all’uso (per i linguaggi più comuni, come Java).

Task

Un task in Gradle rappresenta una singola operazione atomica del processo di costruzione del sofware

  • singola $\rightarrow$ un task fa una sola cosa (Single Responsibility Principle)
  • atomica $\rightarrow$ indivisibile: un task comincia e finisce senza interruzione

Qualunque esecuzione di Gradle richiede di specificare uno o più task, ad esempio:

  • gradle tasks (elenca i task disponibili, escludendo quelli non categorizzati)
  • gradle tasks --all (elenca tutti i task disponibili)
  • gradle compileJava (compila i sorgenti java)

Gradle è in grado capire le dipendenze fra task ed eseguirli nell’ordine corretto.

Gradle: configurazione minimale per Java

  • Gradle viene pilotato con due file:
    • settings.gradle.kts
      • Per i nostri scopi, serve solo a dare un nome al progetto
    • build.gradle.kts
      • Conterrà tutta la logica di costruzione del software
      • Ma noi sfrutteremo le convenzioni, configurando ben poco!
  • Al momento, ci basta una sola riga di codice per ciascuno!

settings.gradle.kts

rootProject.name = "minimal-build"

build.gradle.kts

plugins { java }

Così configurato, Gradle autonomamente:

  • cerca e compila i sorgenti java dalla cartella: src/main/java
  • produce i binari dentro: build/classes/java/main

Vogliamo percorsi diversi? Va configurato.

Gradle: Hello World in Java, struttura

├── build.gradle.kts
├── settings.gradle.kts
└── src
    └── main
        └── java
            └── HelloWorld.java

build.gradle.kts

plugins { java }

settings.gradle.kts (opzionale)

rootProject.name = "hello-world"

HelloWorld.java

public class HelloWorld {

    public static void main(String... args) {
        System.out.println("Hello, world!");
    }
    
}

Gradle: Hello World in Java, task e loro utilizzo

  • elencare i task disponibili:
    • gradle tasks --all
  • compilazione:
    • gradle compileJava
  • pulizia (cancellazione della directory build dove Gradle lavora):
    • gradle clean
  • esecuzione (non responsabilità di Gradle):
    • java -cp build/classes/java/main HelloWorld

Gradle wrapper

(Non) installare Gradle: il wrapper

Se da una versione all’altra di Gradle dovesse cambiare la convenzione, cosa succederebbe?

  • Il nostro software smette di funzionare se aggiorniamo il build system!

E se avessimo progetti diversi che richiedono versioni diverse di Gradle?

Ci serve un modo per portarci dietro la versione di Gradle che ci serve

Gradle wrapper

Un insieme di script con un software minimale che:

  1. Scarica la versione di Gradle indicata in un file di configurazione
    • se non già disponibile nel sistema
  2. Usa quella versione per costruire il nostro sistema!

Il wrapper può (deve) esser copiato in ogni progetto che gestiamo con Gradle

Dato che si auto-scarica, non occorre installare Gradle!

  • Anche se è comodo, la versione di Gradle installata può generare le versioni wrapper

Progetti Gradle con wrapper

  1. Script bash eseguibile (/): gradlew
  2. Script batch eseguibile (): gradlew.bat
  3. File di configurazione con indicata la versione di Gradle:
    gradle/wrapper/gradle-wrapper.properties
  4. Software Java che scarica la versione di Gradle descritta nel file di configurazione:
    gradle/wrapper/gradle-wrapper.jar

Wrapper pronto per esser scaricato:

Utilizzo di Gradle in un progetto Java

Se abbiamo il Gradle wrapper configurato in un progetto, possiamo usarlo attraverso uno dei due script:

  • gradlew (/) o gradlew.bat (), a seconda della nostra piattaforma
  • seguito da un elenco di comandi detti task

In Gradle, un task è una particolare attività del processo di costruzione del software

Esiste un task che elenca i task, chiamato tasks.

Quando configurato per compilare Java fra i vari task troviamo anche compileJava

Ottenere l’elenco dei task disponibili

  • ./gradlew tasks (sistemi e )
  • gradlew.bat tasks (sistemi )

Compilazione Java

  • ./gradlew compileJava (sistemi e )
  • gradlew.bat compileJava (sistemi )

È sempre comunque possibile lanciare i nostri eseguibili a mano dal terminale!

java -cp build/classes/java/main nome.qualificato.della.ClasseDaEseguire

Appendice – Varianza

Formula per il calcolo della varianza

Sia $n$ il numero di elementi dell’array ed $x_i$ l’elemento all’indice $i$ dell’array, e $\mu$ la media dei valori del suddetto array. La varianza $\sigma^2$ può essere calcolata come:

$$\sigma^2 = \frac{\displaystyle\sum_{i=0}^{n-1}(x_i - \mu)^2} {n}$$