Skip to content
This repository has been archived by the owner on Mar 12, 2024. It is now read-only.

Commit

Permalink
Capitolo 4: Generazione, test
Browse files Browse the repository at this point in the history
  • Loading branch information
Zimbrando committed Feb 17, 2024
1 parent ffc0abe commit 70f349e
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 11 deletions.
4 changes: 2 additions & 2 deletions chapters/2 - Analisi.tex
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ \section{Requisiti}
\begin{itemize}
\item \textbf{Pacchettizzazione}: il simulatore deve essere distribuito in pacchetti auto-contenuti, ossia comprendenti di tutto il necessario per l'utilizzo di esso.
\item \textbf{Multi-piattaforma}: Alchemist deve essere installabile sui maggiori sistemi operativi in circolazione come Windows, MacOS e le principali distribuzioni Linux.
\item \textbf{Pronto all'uso}: l'installazione non deve richiedere configurazioni complesse, l'applicativo deve essere pronto all'uso non appena installato.
\item \textbf{Plug and play}: l'installazione non deve richiedere configurazioni complesse, l'applicativo deve essere pronto all'uso non appena installato.
\end{itemize}

I requisiti del gruppo successivo sono accumunabili per il loro scopo, vale a dire l'automazione.

\begin{itemize}
\item \textbf{Automazione dei pacchetti}: la generazione dei pacchetti di installazione deve essere automatica e configurabile
\item \textbf{Automazione della distribuzione}: il rilascio di una nuova versione comprende la distribuzione di essa nei repository selezionati e deve essere eseguito automaticamente.
\item \textbf{Automazione della distribuzione}: il rilascio di una nuova versione comprende la distribuzione di essa nei repository selezionati e deve essere svolta in modo automatico.
\item \textbf{Verifica funzionamento}: entrambi i processi descritti precedentemente devono essere corredati da verifiche del loro funzionamento e devono bloccare la procedura di rilascio nell'eventualità siano presenti errori.
\end{itemize}

Expand Down
2 changes: 1 addition & 1 deletion chapters/3 - Design.tex
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ \subsection{Repository}

Una volta scritto lo script il pacchetto è pronto per essere installato e quindi pubblicato. Attraverso un programma fornito da Arch di nome \texttt{namcap} è possibile controllare che la sintassi ed i valori inseriti nello script siano validi. I pacchetti all'interno dell'\ac{aur} consistono in repository git contenenti il PKGBUILD ed altri file di configurazione opzionali, il processo di pubblicazione dunque è simile a qualsiasi progetto con un sistema di version control: la creazione di un commit e la pubblicazione di esso.

\paragraph{Winget} Il package manager winget presenta una struttura simile, un pacchetto è formato da diversi file \textit{manifest} i quali descrivono i meta-dati del pacchetto nel linguaggio YAML. A differenza del PKGBUILD, non ci sono script e non esistono funzioni, l'intera configurazione è descrittiva e non presenta la possibilità di inserire comandi da eseguire pre o post installazione. I file manifest si distinguono in: manifesto della versione, contenente dettagli identificativi del pacchetto, il manifesto delle impostazioni locali, il quale descrive la configurazione per uno specifico locale, ed il manifesto dell'installer, contenente l'URL dove reperire il pacchetto installante ed altre informazioni su di esso. Per semplificare il processo di creazione e pubblicazione dei pacchetti, Microsoft prevede l'utilizzo di uno script ``wingetcreate" che guida l'utente nella scelta dei parametri. Questo inoltre si presta ad essere utilizzato all'interno di pipeline \ac{cicd} per aggiornare pacchetti già presenti.
\paragraph{Winget}\label{chap:winget} Il package manager winget presenta una struttura simile, un pacchetto è formato da diversi file \textit{manifest} i quali descrivono i meta-dati del pacchetto nel linguaggio YAML. A differenza del PKGBUILD, non ci sono script e non esistono funzioni, l'intera configurazione è descrittiva e non presenta la possibilità di inserire comandi da eseguire pre o post installazione. I file manifest si distinguono in: manifesto della versione, contenente dettagli identificativi del pacchetto, il manifesto delle impostazioni locali, il quale descrive la configurazione per uno specifico locale, ed il manifesto dell'installer, contenente l'URL dove reperire il pacchetto installante ed altre informazioni su di esso. Per semplificare il processo di creazione e pubblicazione dei pacchetti, Microsoft prevede l'utilizzo di uno script ``wingetcreate" che guida l'utente nella scelta dei parametri. Questo inoltre si presta ad essere utilizzato all'interno di pipeline \ac{cicd} per aggiornare pacchetti già presenti.

\subsection{Semantic release}
Il processo di \ac{cicd} utilizzato da Alchemist, prevede l'utilizzo di una tecnica chiamata \textit{semantic release}, legata con il più conosciuto concetto di \textit{semantic versioning}. Abbreviato come ``SemVer", esso è uno schema di versionamento standardizzato per determinare le versioni di un software. È stato progettato per rendere intuitivo comprendere le modifiche apportate al software ed il loro impatto riguardo la compatibilità con le versioni precedenti. Lo schema descrive una versione come tre cifre principali separate da punti.
Expand Down
23 changes: 15 additions & 8 deletions chapters/4 - Implementazione.tex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

\chapter{Implementazione}

Nel seguente capitolo è illustrato il percorso e le relative scelte implementative effettuate per soddisfare i requisiti posti dal progetto. Successivamente, valuterò il lavoro svolto considerando i requisiti non funzionali posti.
Nel seguente capitolo è illustrato il percorso e le relative scelte implementative effettuate per soddisfare i requisiti posti dal progetto. Successivamente, valuterò il lavoro svolto considerando i requisiti posti durante l'analisi.

\section{Pacchettizzazione e meta-dati}

Expand Down Expand Up @@ -86,30 +86,37 @@ \subsection{Meta-dati}
\item la \textbf{sorgente}, ossia l'URL localizzante il pacchetto in rete. L'aggiornamento ad una nuova versione richiede l'utilizzo del pacchetto contenente la nuova versione del software, ragion per cui l'URL sarà differente.
\item il \textbf{checksum}, ovvero il codice hash legato alla sorgente. È utilizzato da makepkg per assicurarsi che il pacchetto installato non sia stato sostituito da un attore malevolo, una volta ottenuta la sorgente, ricalcola il checksum e lo confronta con quello fornito dallo script.
\end{itemize}
Mediante l'utilizzo di file detti template è possibile costruire dinamicamente i meta-dati mantenendo una struttura statica facilmente aggiornabile. In particolare, il template del file PKGBUILD contiene tutti i parametri statici correttamente assegnati, mentre quelli dinamici sono identificati attraverso l'inserimento di caratteri speciali inutilizzati. Il task \texttt{generatePKGBUILD} legge il file e sostituisce i caratteri con i valori corretti ottenendo come risultato il PKGBUILD conforme e compatibile con la versione corrente del software.
Mediante l'utilizzo di file detti template è possibile costruire dinamicamente i meta-dati mantenendo una struttura statica facilmente aggiornabile. In particolare, il template del file PKGBUILD contiene tutti i parametri statici correttamente assegnati, mentre quelli dinamici sono identificati attraverso l'inserimento di caratteri speciali inutilizzati. Il task \texttt{generatePKGBUILD} legge il file e sostituisce i caratteri con i valori corretti ottenendo come risultato il PKGBUILD conforme e compatibile con la versione corrente del software. A differenza di Winget, il file PKGBUILD permette di utilizzare una sorgente locale (un percorso nel filesystem). Ciò è risultato particolarmente utile per verificare il funzionamento dello script in modo completo, attraverso un flag configurabile quando si invoca il task generatore si comunica a Gradle di utilizzare una sorgente locale, nello specifico il nome del pacchetto rpm. In questo modo si può simulare l'installazione del pacchetto, anche se questo non è ancora stato rilasciato all'interno dell'Arch User Repository.

\paragraph{Script} Un discorso differente vale per il repository di Microsoft dalla quale winget reperisce i pacchetti. Come citato nella sezione \ref{chap:winget}, Windows fornisce uno script interattivo capace di inserire ed aggiornare nuovi pacchetti all'interno del repository. Esso permette inoltre di cercare il manifest dell'applicazione già online ed aggiornarlo dallo strumento senza utilizzare file locali. Il comando necessario all'aggiornamento è così formato:

\texttt{wingetcreate update \$packageId --version \$packageVersion \\\tab\tab --urls "\$installerUrl" --submit --token \$gitToken}
\vspace{0.8cm}

Autonomamente lo script: (i) cerca tra i pacchetti disponibili quello indicato da \texttt{\$packageId}, (ii) aggiorna gli attributi dinamici come la versione ed il pacchetto indicati dalle variabili \texttt{\$packageVersion} e \texttt{\$installerUrl}, (iii) utilizza il token GitHub per autenticarsi e richiedere attraverso una pull request l'aggiornamento del pacchetto. Mentre la modifica del pacchetto su \ac{aur} è istantanea, per winget questa deve superare un processo di validazione prima di essere eseguita con successo. Lo script, wingetcreate, in conclusione semplifica il processo e rende obsoleto l'utilizzo del build system, in quanto non è necessario comprendere il manifest del pacchetto all'interno del progetto.

\paragraph{Script}
% Utilizzo di wingetcreate nella pipeline

\section{Sviluppo della pipeline}

L'implementazione descritta consente attraverso l'utilizzo del build system di generare i pacchetti ed i corrispondenti meta-dati richiesti per la distribuzione. Il passo successivo è integrare la loro esecuzione all'interno della pipeline, in modo da conseguire gli obiettivi di integrazione e distribuzione continua. La sfida principale consiste nell'inserire i nuovi step senza stravolgere la struttura ed il flusso iniziale. Ciò è ottenibile inserendo nuovi job all'interno della pipeline delegati solamente allo svolgimento di singole e specifiche attività.

\subsection{Generazione degli artefatti}

Innanzitutto, il flusso richiede l'inserimento di nuovi processi incaricati di assemblare e generare gli artefatti richiesti per la distribuzione, ovvero il software impacchettato. Durante l'analisi dello strumento jpackage è stata evidenziata l'assenza del supporto al cross-platform, è necessario quindi delegare a runner con sistemi operativi differenti la generazione degli artefatti. La piattaforma Actions fornisce la possibilità di configurare una \textbf{strategia a matrice}: si determinano dei parametri e una lista dei possibili valori, successivamente in fase di esecuzione saranno eseguiti tanti job quante sono le possibili combinazioni di valori differenti. In questo modo descrivendo un solo job, in fase di esecuzione otteniamo diversi job paralleli i quali svolgono le stesse azioni, ma con un sistema operativo differente. Mediante l'utilizzo del build system richiediamo la generazione dei pacchetti, e successivamente carichiamo l'output utilizzando l'azione \texttt{action/upload-artifact}, quest'ultima permette di trasferire file al di fuori del runner, in modo che successivi job possano scaricare il loro contenuto. Tramite l'attributo \texttt{if} è consentito determinare se uno step deve essere eseguito o saltato dal runner che ha preso in carico l'esecuzione, esso è risultato utile per delegare la generazione dei file JAR a solamente un sistema operativo, dato che gli archivi sono identici essendo codice bytecode e quindi multi-piattaforma
Innanzitutto, il flusso richiede l'inserimento di nuovi processi incaricati di assemblare e generare gli artefatti richiesti per la distribuzione, ovvero il software impacchettato. Durante l'analisi dello strumento jpackage è stata evidenziata l'assenza del supporto al cross-platform, è necessario quindi delegare a runner con sistemi operativi differenti la generazione degli artefatti. La piattaforma Actions fornisce la possibilità di configurare una \textbf{strategia a matrice}: si determinano dei parametri e una lista dei possibili valori, successivamente in fase di esecuzione saranno eseguiti tanti job quante sono le possibili combinazioni di valori differenti. In questo modo descrivendo un solo job, in fase di esecuzione otteniamo diversi job paralleli i quali svolgono le stesse azioni, ma con un sistema operativo differente. Mediante l'utilizzo del build system richiediamo la generazione dei pacchetti, e successivamente carichiamo l'output utilizzando l'azione \texttt{action/upload-artifact}, quest'ultima permette di trasferire file al di fuori del runner, in modo che successivi job possano scaricare il loro contenuto. Tramite l'attributo \texttt{if} è consentito determinare se uno step deve essere eseguito o saltato dal runner che ha preso in carico l'esecuzione, esso è risultato utile per delegare la generazione dei file JAR a solamente un sistema operativo, dato che gli archivi sono identici essendo codice bytecode e quindi multi-piattaforma.

\lstinputlisting[float,language=Kotlin,label={lst:package-generation}, caption={Il job incaricato alla generazione dei pacchetti}]{listings/package-generation.yml}

Il job non richiede l'esecuzione di azioni preparative, se non l'inizializzazione della pipeline la quale è condotta da \texttt{select-java-version}. In questo modo sfruttiamo le potenzialità del calcolo parallelo per ridurre il tempo di esecuzione della pipeline. Una volta prodotti i pacchetti, questi saranno presi in carico dai successivi job per verificare il loro corretto funzionamento.

\subsection{Test dei processi}

Nel ruolo di test è necessaria la creazione di diversi job adibiti alla verifica del funzionamento di questi.
% Parla di testJpackageOutput
Ogni processo aggiuntivo richiede lo sviluppo di un test apposito. Il fallimento di un qualsiasi test deve bloccare l'esecuzione della pipeline cosicché si garantisce un processo di rilascio completamente funzionante. Questa proprietà si ottiene attraverso lo sviluppo di verifiche di validità dei pacchetti e controlli riguardo la conformità dei meta-dati.

La verifica dei pacchetti richiede l'utilizzo di programmi specifici i quali dipendono dalla tipologia di pacchetto che si vuole analizzare. Utilizzando il build system ed integrando un task specifico \texttt{testJpackageOutput} (\ref{lst:task-jpackage-test}) si collassa la definizione di molteplici job all'interno di un job unico, il quale utilizzando la strategia a matrice definita precedentemente, permette la definizione di un solo insieme di attività poi distribuito su più runner con sistemi operativi differenti. Le procedure di installazione si trovano all'interno del task, il quale conosce il sistema operativo sottostante dunque estrae i pacchetti relativi alla sua piattaforma e controlla la presenza dei file necessari all'utilizzo dell'applicazione. Il task estende la tipologia \texttt{Exec} fornita da Gradle che permette l'esecuzione di comandi nella shell del sistema operativo, in primo luogo configura il comando adibito ad installare il pacchetto a seconda della piattaforma su cui sta eseguendo (il blocco \texttt{doFirst}) ed infine una volta estratti utilizza le API di Kotlin per controllare il filesystem e quindi le cartelle generate durante l'installazione (il blocco \texttt{doLast}).

\lstinputlisting[float,language=Kotlin,label={lst:task-jpackage-test}, caption={Task \texttt{testJpackageOutput} Gradle incaricato di verificare la validità dei pacchetti}]{listings/test-jpackage-task.kt}

\section{Valutazione}
Inizialmente il task era posto come dipendente dall'esecuzione di jpackage, in modo che l'esecuzione del test garantisse la presenza dei pacchetti. Questo comportamento tuttavia costringe la generazione dei pacchetti ogniqualvolta si voglia eseguire la verifica. Ciò accade perché il task fornitoci dal plugin \texttt{jpackage-gradle-plugin} non dichiara alcun \textit{output}, quindi Gradle non consente di utilizzare la funzionalità della \textit{build incrementale}. In altre parole, il build system non conosce l'output del task generatore e in caso i pacchetti siano già presenti, come accade nella pipeline perché generati da job precedenti, esso riesegue la generazione sovrascrivendo gli ultimi. La rimozione della dipendenza garantisce un incremento delle prestazioni, permettendo di dividere le attività in due unità di esecuzione differenti. Il costo è una minor chiarezza nell'utilizzo manuale del task, in quanto l'esecuzione del task di verifica senza previa generazione fallirà per l'assenza dei pacchetti. Siccome il test è sviluppato principalmente per l'utilizzo all'interno di pipeline, questa limitazione non genera particolari problematiche, generalmente gli sviluppatori non eseguiranno il task sulla propria macchina, e in caso fosse necessario è sufficiente indicare l'esecuzione del task generatore assieme a quello di verifica.

\section{Valutazione}
% ottimizzazione, fail fast, dipendenze gradle, concurrency group, upload artifact, confronto
2 changes: 2 additions & 0 deletions chapters/5 - Conclusioni.tex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
%!TEX root = ../thesis-main.tex
\chapter{Conclusioni}

% Dimostrare i link con le release, installazione da AUR e da Winget

\section{Sviluppi futuri}
42 changes: 42 additions & 0 deletions listings/test-jpackage-task.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
tasks.register<Exec>("testJpackageOutput") {
group = "Verification"
description = "Verifies the jpackage output correctness for the OS running the script"
isIgnoreExitValue = true
workingDir = rootProject.file("build/package/")
doFirst {
val version = rootProject.version.toString().substringBefore('-')
// Extract the packet
when {
isWindows -> commandLine("msiexec", "-i", "${rootProject.name}-$version.msi", "-quiet", "INSTALLDIR=${workingDir.path}\\install")
isMac -> commandLine("sudo", "installer", "-pkg", "${rootProject.name}-$version.pkg", "-target", "/")
else -> {
workingDir.resolve("install").mkdirs()
commandLine("bsdtar", "-xf", "${rootProject.name}-$version-1.x86_64.rpm", "-C", "install")
}
}
}
doLast {
// Check if package contains every file needed
var execFiles: List<String>
var appFiles: List<String>
when {
isWindows -> {
execFiles = workingDir.resolve("install").listFiles().map { it.name }
appFiles = workingDir.resolve("install/app").listFiles().map { it.name }
}
isMac -> {
val root = File("/Applications/${rootProject.name}.app")
execFiles = root.resolve("Contents/MacOS").listFiles().map { it.name }
appFiles = root.resolve("Contents/app").listFiles().map { it.name }
}
else -> {
execFiles = workingDir.resolve("install/opt/alchemist/bin").listFiles().map { it.name }
appFiles = workingDir.resolve("install/opt/alchemist/lib/app").listFiles().map { it.name }
}
}
require(rootProject.name in execFiles || "${rootProject.name}.exe" in execFiles)
require(jpackageFull.get().mainJar in appFiles)
}
mustRunAfter(jpackageFull)
finalizedBy(deleteJpackageOutput)
}

0 comments on commit 70f349e

Please sign in to comment.