Disclaimer :
La versione originale di questo articolo è stata pubblicata da IBM
developerWorks ed è di proprietà di Westtech Information Services. Questo
documento è una versione aggiornata dell'articolo originale, e contiene
numerosi miglioramenti apportati dal Gentoo Linux Documentation team.
Questo documento non è mantenuto attivamente.
|
Le migliori tecniche con gli autotools
1.
Le migliori tecniche con gli autotools
Il cuore della catena di compilazione GNU -- il set di strumenti utilizzato per
compilare i pacchetti software GNU -- è il cosiddetto "autotools," un termine
che si riferisce ai programmi autoconf e automake, nonché a libtool,
autoheader, pkg-config, e talvolta a gettext. Questi tools consentono di
compilare software GNU su un gran numero di piattaforme e sistemi operativi Unix
e Unix-like, fornendo agli sviluppatori uno strumento per controllare la
presenza delle librerie, delle funzioni e dei tools che essi vogliono
utilizzare. Se gli autotools sono eccezionali nelle mani di uno sviluppatore
esperto, essi possono essere piuttosto difficili da utilizzare per chi vi si
avvicina per la prima volta, e non è così raro che dei pacchetti siano
distrubuiti con un supporto autotools "working-but-broken." Questo articolo
tratterà alcuni degli errori più comuni commessi da chi utilizza gli autotools,
mostrando come ottenere migliori risultati.
A dispetto dell'opinione che ciascuno può avere su di essi, attualmente non
esiste una valida alternativa agli autotools. Progetti come Scons non sono
portabili come gli autotools, e per il momento non contengono abbastanza
funzioni da essere utili. Gli autotools compiono tantissimi controlli
automatici, e molte librerie hanno una libreria m4 con macro che controllano la
loro presenza.
La struttura base di un progetto autotools è semplice. Autoconf si aiuta con il
file aclocal.m4 (creato da aclocal utilizzando le librerie m4 nel
suo path di ricerca e il file acinclude.m4) per analizzare il file
configure.ac (in precedenza configure.in) e
trasformarlo in uno script "configure." Per ogni directory dovrebbe esistere un
file Makefile.am, che è utilizzato da automake per creare i modelli
Makefile.in. Questi sono poi processati e trasformati in veri Makefiles dallo
script configure. Si può evitare l'uso di automake scrivendo i
files Makefile.in a mano, ma questo è un processo abbastanza complesso e si
perdono alcune funzioni degli autotools.
In un file configure.ac si possono usare macro definite
manualmente, fornite di default con autoconf e aclocal, oppure esterne, fornite,
per esempio, con altri pacchetti. In questo caso aclocal creerà il file
aclocal.m4 aggiungendo i file di libreria trovati nella libreria di
sistema alle le macro definite; questo è un passaggio fondamentale per avere un
progetto autotools funzionante, come vedremo tra un momento.
Un Makefile.am è principalmente una dichiarazione di intenti: si
possono definire alcune variabili con i nomi di ciò che si vuole compilare.
Queste variabili sono strutturate in un formato del tipo
placetoinstall_TYPEOFTARGET. "Placetoinstall" è una collocazione all'interno di
un filesystem gerarchico Unix (bin, lib, include, ...), una keyword non
utilizzata che può essere definita con un percorso arbitrario (usando la
variabile keyworddir), o la keyword speciale noinst che contrassegna gli oggetti
che non necessitano di installazione (ad esempio headers privati, o librerie
statiche usate durante la compilazione). Dopo aver dato un nome all'oggetto, si
può usare questo nome (sostituendo i punti con "_") come prefisso per le
variabili che influenzano la sua compilazione. In questo modo si possono
definire variabili speciali CFLAGS, LDFLAGS, e LDADD, utilizzate durante la
compilazione di ogni singolo oggetto, anziché doverle modificare per tutti gli
oggetti. Si possono anche utilizzare le variabili raccolte durante la fase di
configure, se sono state trasferite alla macro AC_SUBST in
configure.ac, cosicché possano essere sostituite all'interno dei
makefiles. Inoltre, nonostante definire CFLAGS e LDFLAGS globali sembri utile,
aggiungere flags statiche in Makefile.am è negativo per la portabilità, in
quanto non si può sapere se il compilatore che si sta usando le supporta, o se
esse sono realmente necessarie (-ldl messo in LDFLAGS è un buon esempio di flag
necessaria su Linux ma non su FreeBSD); in questi casi si dovrebbe utilizzare
configure.ac per aggiungere questi flags.
Le macro usate più comunemente in configure.ac sono AC_CHECK_HEADERS,
AC_CHECK_FUNCS, e AC_CHECK_LIB, che servono a verificare la presenza,
rispettivamente, di alcuni header files, di alcune funzioni di libreria, e di
una data libreria (con una funzione specifica all'interno). Esse sono importanti
per la portabilità, in quanto forniscono uno strumento per controllare quali
headers sono presenti e quali no (per esempio headers di sistema che hanno
diversa collocazione in diversi sistemi operativi), per controllare se una
funzione è presente nella libreria di sistema (ad esempio asprintf() manca in
OpenBSD, mentre è presente nella libreria GNU C e in FreeBSD), e infine per
controllare la presenza di alcune librerie di terze parti o per vedere se uno
specifico collegamento ad una libreria è necessario per avere alcune funzioni
(per esempio la funzione dlopen() si trova nella libreria libdl sui sistemi GNU,
mentre è fornita dalla libreria C di sistema su FreeBSD).
Oltre a verificare la presenza o meno di funzioni o headers (e a volte di
librerie) di solito è necessario modificare il percorso del codice (ad esempio
per evitare l'uso di funzioni mancanti, o per definire un rimpiazzo di esse).
Autoconf di solito è usato in coppia con un altro tool, autoheader, il quale
crea un template config.h.in utilizzato dallo script configure per
creare l'header config.h, nel quale sono definite alcune macro
preprocessore nella forma HAVE_givenfunction o HAVE_givenheader_H, che possono
essere testate con le direttive #ifdef/#ifndef all'interno di un file sorgente C
o C++, per modificare il codice a seconda delle caratteristiche presenti.
Ecco alcune tecniche da tenere a mente nell'uso degli autotools per creare un
codice che sia il più portabile possibile.
Il file header config.h dovrebbe essere considerato un file header
interno, e quindi dovrebbe essere utilizzato solo dal singolo pacchetto in
cui è creato. Andrebbe evitato di editare il template config.h.in
per aggiungervi il proprio codice, in quanto ciò richiede il suo aggiornamento
manuale, in base al file configure.ac che si sta scrivendo.
Sfortunatamente alcuni progetti, come Net-SNMP, esportano questo file header
insieme agli header di altre librerie, il che porta alla necessità di includerli
(o di fornire una loro copia delle strutture interne Net-SNMP). Questo è un
fatto negativo, in quanto la struttura di un progetto di libreria autotools
dovrebbe essere invisibile al software che lo utilizza (che potrebbe non usare
affatto gli autotools). Inoltre, cambiamenti nel comportamento degli autotools
non sono affatto rari, quindi si possono avere due controlli identici con
risultati differenti a causa di un cambiamento nel modo in cui essi sono
eseguiti. Se si ha necessità di definire i propri wrappers o sostituzioni in
previsione del caso che qualcosa non sia nell'ambiente per cui si sta
compilando, lo si dovrebbe fare in headers privati che non vengono installati
(dichiarati come noinst_HEADERS nei files Makefile.am).
Fornisci sempre i file m4 che hai usato. Dato che gli autotools sono
utilizzati da anni, molti pacchetti (ad esempio le librerie) che possono essere
riutilizzati da altri programmi forniscono in /usr/share/aclocal un
file di libreria m4, che rende possibile verificare la loro presenza (ad esempio
usando gli script -config) con un semplice richiamo di macro. Questi files sono
utilizzati da aclocal per creare il file aclocal.m4, e di solito
sono presenti sui sistemi degli sviluppatori quando per creare la release viene
eseguito aclocal, ma quando essi servono per dipendenze opzionali possono anche
mancare sui sistemi degli utenti. Mentre di solito questo non è un problema,
dato che gli utenti eseguono aclocal molto raramente, diventa un problema per le
distribuzioni che utilizzano i sorgenti, come ad esempio Gentoo, dove a volte è
necessario patchare un Makefile.am o il file configure.ac,
eseguendo poi di nuovo autoconf senza poter installare tutte le dipendenze
opzionali (o avendo versioni differenti, che possono essere incompatibili o
buggate, dello stesso file m4).
Per superare questo problema, bisognebbe creare, nella directory del pacchetto,
una sottodirectory m4 dove mettere i files di libreria m4 che si stanno
utilizzando. Fatto ciò, va eseguito aclocal con il comando "aclocal -I m4," per
cercare in quella directory prima che nella libreria di sistema. Si può poi
scegliere se mettere quella directory sotto revision control (CVS, SVN, o
qualunque altro si stia utilizzando) o se crearla semplicemente per le release.
Il secondo caso è il requisito minimo indispensabile per un pacchetto. In questo
modo si diminuisce la quantità di codice sottoposta a revision control e ci si
assicura di star sempre utilizzando l'ultima versione m4, ma si ha lo svantaggio
che chiunque controlli la repository non sarà in grado di eseguire autoconf se
non prendendo i file m4 dal tarball di una release (e questo potrebbe
funzionare, ma anche no, in quanto il file configure.ac potrebbe essere stato
aggiornato per soddisfare una nuova macro, oppure potrebbero essere state
aggiunte delle nuove dipendenze). D'altro canto, mettere la directory m4 sotto
revision control a volte spinge gli sviluppatori a modificare le macro per
soddisfare le proprie esigenze. Benché ciò sembri logico, dato che i files m4
sono sotto il proprio revision control, questo farà innervosire molti mantainers
di pacchetti, visto che a volte le nuove versioni dei files m4 correggono bugs o
supportano nuove opzioni e percorsi di installazione (per esempio multilib
setups), e avere i files m4 modificati rende impossibile sostituirli
semplicemente con le versioni aggiornate. Questo significa anche che quando si
aggiorna un file m4 file si devono rifare le modifiche contro l'originale.
Lavorare con i files m4 è sempre un problema. Essi devono replicare praticamente
lo stesso codice da libreria a libreria (a seconda del modo in cui si ha
necessità di fornire CFLAGS/LDFLAGS: con test o con uno script -config). Per
evitare questo problema, i progetti GNOME e FreeDesktop hanno sviluppato un tool
chiamato pkg-config, che fornisce sia un binario eseguibile sia un file m4 da
includere nei file configure.ac, e che permette agli sviluppatori di verificare
la presenza di una data libreria (e/o pacchetto), ammesso che il pacchetto
stesso abbia installato un file di dati .pc di pkg-config. Questo approccio
semplifica il lavoro di mantenimento degli script configure.ac, e richiede molto
meno tempo per essere processato durante l'esecuzione dello script configure,
dato che si utilizzano le informazioni fornite dallo stesso pacchetto installato
anziché verificare semplicemente se esso è presente. D'altro canto, questo
approccio implica che un errore compiuto dagli sviluppatori riguardo a una
dipendenza può rendere inutilizzabile il programma all'utente, visto che essi
inseriscono direttamente il compilatore e i linker flags nel file di dati, e lo
script configure non verifica realmente se la libreria funziona o no.
Fortunatamente, ciò non accade troppo spesso.
Per creare il file configure, è necessario PKG_CHECK_MODULES, contenuto nella
libreria pkg.m4. Si dovrebbe aggiungere quel file alla propria
directory m4. Se la dipendenza di pkg-config è obbligatoria (cioè se il tool è
eseguito dallo script configure) non si può essere sicuri che il file m4 che si
sta usando sia lo stesso che si trova sui sistemi degli utenti, né che esso non
includa dei bugs extra, potendo essere più vecchio del proprio.
Controlla sempre le librerie a cui ti collegherai, se esse sono
dipendenze obbligatorie. Di solito le macro di autoconf o i file di dati di
pkg-config definiscono librerie indispensabili, che devono essere collegate alla
propria libreria. Inoltre, alcune funzioni che si trovano in librerie extra di
alcuni sistemi (come dlopen() in libdl su Linux e Mac OS X) possono essere nel
libc di un altro sistema (la stessa funzione è in libc su FreeBSD). In questi
casi si deve verificare se la funzione può essere trovata senza alcun
collegamento, o se invece è necessario usare una libreria specifica (ad esempio
per evitare di collegarsi dove non necessario ad un libdl inesistente che non
funzionerebbe).
Stai attento alle estensioni GNU. Una delle cose che rendono la
portabilità un problema è l'uso di funzioni di estensione, che sono fornite da
GNU libc ma non sono presenti su altre librerie C come quelle di BSD o su
uClibc. Quando si utilizzano tali funzioni, bisognerebbe sempre fornire un
"drop-in replacement," una funzione che può fornire la stessa funzionalità della
funzione di libreria, forse con minori performance o sicurezza, e che può essere
utilizzata quando la funzione di estensione non è presente nella libreria C di
sistema. Tali funzioni devono essere protette da un blocco #ifdef HAVE_function
... #endif, cosicché non vengano duplicate quando sono già presenti. Accertati
che queste funzioni non siano esportate dalla libreria verso gli utenti esterni;
esse dovrebbero essere dichiarate dentro un header interno, per evitare di
rompere altre librerie che potrebbero fare simili trucchetti.
Evita di compilare codice specifico per un sistema operativo quando ciò non è
necessario. Quando un programma supporta opzionalmente specifiche librerie o
specifici sistemi operativi, non è raro avere interi file sorgenti che sono
specifici per quel codice. Per evitare di compilarli quando non sono necessari,
usa la macro AM_CONDITIONAL all'interno di un file configure.ac. Questa macro di
automake (utilizzabile solo se si sta usando automake per compilare il progetto)
consente di definire blocchi if .. endif in un file Makefile.am, all'interno del
quale possono essere impostate variabili speciali. Si può, ad esempio,
aggiungere una variabile "platformsrcs," impostata verso il corretto file
sorgente per la piattaforma per cui si vuole compilare, utilizzando poi una
variabile a _SOURCES.
Tuttavia, ci sono due errori comuni commessi dagli sviluppatori nell'uso di
AM_CONDITIONAL. Il primo è l'utilizzo di AM_CONDITIONAL in un ramo già
condizionale (per esempio sotto una info o in un case switch), che porta
automake a protestare per un condizionale definito solo condizionalmente
(AM_CONDITIONAL deve essere invocato su scala globale, fuori da ogni blocco if,
quindi è necessario definire una variabile per contenere lo stato delle
condizioni, testandola poi invocando AM_CONDITIONAL). Il secondo è che non si
possono modificare le variabili degli oggetti direttamente, ed è necessario
definire variabili "merce" i cui risultati escono dal condizionale, in modo da
poter aggiungere o rimuovere file sorgenti e oggetti.
Molti progetti, per evitare di compilare codice per specifici percorsi di
codice, aggiungono tutti i files in condizionali preprocessore #ifdef ...
#endif. Sebbene questo di solito funzioni, rende il codice brutto e incline agli
errori, in quanto una singola riga fuori dal blocco condizionale può essere
compilata dove il file sorgente non è necessario. Ciò a volte inganna anche gli
utenti, in quanto i sorgenti sembrano essere compilati in situazioni in cui non
hanno senso.
Sii intelligente nel cercare un sistema operativo o una piattaforma
hardware. A volte è necessario cercare uno specifico sistema operativo o
piattaforma hardware. Il modo corretto di farlo dipende dal perché serve sapere
ciò. Se è necessario saperlo per abilitare test extra in configure, o per
aggiungere oggetti extra sui makefiles, si deve fare il controllo in
configure.ac. D'altro canto, se è necessario conoscere la differenza in un file
sorgente, ad esempio per abilitare una funzione opzionale asm-coded, ci si
dovrebbe affidare direttamente al compilatore/preprocessore, quindi si
dovrebbero usare direttive #ifdef con le macro di default abilitate sulla
piattaforma in oggetto (ad esempio __linux__, __i386__, _ARC_PPC, __sparc__,
_FreeBSD_ e __APPLE__).
Non eseguire comandi in configure.ac. Se si ha necessità di controllare
l'hardware o il sistema operativo in un file configure.ac, si dovrebbe evitare
di usare il comando uname, nonostante esso sia uno dei modi più comuni per fare
un simile test. Questo è un errore in realtà, dato che rompe la
crosscompilazione. Autotools supporta i progetti di crosscompilazione da una
macchina ad un'altra utilizzando definizioni di host: stringhe nella forma
"hardware-vendor-os" (in realtà, "hardware-vendor-os-libc" quando GNU libc è
utilizzato), come ad esempio i686-pc-linux-gnu e x86_64-unknown-freebsd5.4.
CHOST è la definizione di host per il sistema per cui si sta compilando il
software, CBUILD è la definizione di host per il sistema operativo su cui si sta
compilando; quando CHOST e CBUILD differiscono, si sta crosscompilando.
Nell'esempio sopra, la prima definizione di host mostra un sistema x86-like, con
un processore pentium2-equivalente (o successivo), su cui gira un kernel Linux
con GNU libc (di solito questo si riferisce a un sistema GNU/Linux). La seconda
si riferisce a un sistema AMD64 con sistema operativo FreeBSD 5.4 (per un
sistema GNU/kFreeBSD, che utilizza il kernel FreeBSD e GNU libc, la definizione
di host è hw-unknown-freebsd-gnu, mentre per un sistema Gentoo/FreeBSD, che
utilizza il kernel FreeBSD e libc, ma con la struttura Gentoo, la definizione di
host è hw-gentoo-freebsd5.4). Usando le variabili $host e $build dentro uno
script configure.ac si possono abilitare o disabilitare funzioni specifiche
basate sul sistema operativo o sull'hardware per cui o su cui si sta compilando.
Non abusare delle dipendenze "automagic". Una delle funzioni più
importanti degli autotools sono i controlli automatici della presenza di una
liberia, usati spesso per abilitare automaticamente il supporto per dipendenze
extra o per cose simili. Tuttavia, abusare di questa funzione rende la
compilazione di un pacchetto un po' problematica. Mentre essa è abbastanza utile
agli utenti inesperti, e nonostante la maggiorparte dei progetti aventi
dipendenze complesse (ad esempio i programmi multimediali come xine e VLC) usino
una struttura basata sui plugin, che consente loro di evitare la maggiorparte
delle rotture, le dipendenze "automagic" sono una grande sofferenza per i
pacchetti, specialmente per quelli che devono girare su distrubuzioni basate sui
sorgenti come Gentoo e le strutture ports-like. Quando si compila qualcosa con
dipendenze automagiche si abilitano le funzioni supportate dalle librerie
trovate sul sistema in cui è eseguito lo script configure. Questo significa che
i binari risultanti potrebbero non funzionare su un sistema che ha gli stessi
pacchetti base ma dove manca anche una sola libreria extra, ad esempio. Inoltre,
non si possono conoscere le esatte dipendenze di un pacchetto, visto che alcune
potrebbero essere opzionali e non venir compilate quando le librerie non sono
presenti.
Per evitare ciò, autoconf consente di aggiungere le opzioni --enable/--disable e
--with/--without nello script configure. Con queste opzioni si può abilitare o
disabilitare forzatamente una specifica opzione (come il supporto per una
libreria extra o per una specifica funzione), e lasciare il default ai test
automatici.
Sfortunatamente, molti sviluppatori fraintendono il significato dei due
parametri delle funzioni utilizzate per aggiungere quelle opzioni (AC_ARG_ENABLE
e AC_ARG_WITH). Esse rappresentano il codice da eseguire quando viene dato un
parametro e quando no. Molti sviluppatori ritengono erroneamente che i due
parametri definiscano il codice da eseguire quando la funzione è attivata e
quando è disattivata. Se questo di solito funziona quando si dà un parametro
semplicemente per modificare il comportamento di default, molte distribuzioni
basate sui sorgenti danno parametri anche per confermare il comportamento di
default, il che porta ad errori (mancanza di funzioni esplicitamente richieste).
Essere in grado di disabilitare funzioni opzionali se esse non aggiungono
dipendenze (pensiamo al supporto audio OSS su Linux) è sempre una buona cosa per
gli utenti, che possono evitare così di compilare codice extra se non prevedono
di utilizzarlo, ad inoltre risparmia ai maintainers il dover compiere sporchi
trucchetti di caching per abilitare o disabilitare funzioni come richiesto dai
loro utenti.
Mentre gli autotools sono un grosso problema sia per gli sviluppatori che per i
maintainers, in quanto ci sono versioni differenti e incompatibili che non vanno
molto d'accordo tra loro (in quanto si installano negli stessi luoghi e con gli
stessi nomi) e che sono usate in diverse combinazioni, l'uso degli autotools
salva i maintainers dal dover fare ogni sorta di sporchi trucchetti per poter
compilare il software. Se si guardano gli ebuild nel portage di Gentoo, i pochi
che non utilizzano gli autotools sono anche i più complessi, in quanto
necessitano di verificare le variabili in configurazioni molto diverse (si può
avere o non avere il supporto NPTL; si può essere su Linux, FreeBSD, o Mac OS X;
si può utilizzare GLIBC on un'altra libc; e così via), mentre gli autotools di
solito si occupano di questo da soli. È anche vero che molte patch applicate dai
maintainers servono a correggere script autotools rotti nei sorgenti a monte, ma
questo è solo un piccolo problema se paragonato al caos che deriva dall'uso di
speciali sistemi di compilazione che smettono di funzionare anche per piccoli
cambiamenti nell'ambiente.
Gli autotools possono essere abbastanza ostici per i principianti, ma quando si
inizia ad utilizzarli ogni giorno si capisce che è molto più semplice che avere
a che fare con makefiles manuali o con altri strani strumenti di compilazione
come imake o qmake, o ancora peggio, speciali script di compilazione simili agli
autotools che cercano di riconoscere il sistema su cui stanno compilando.
Autotools rende semplice il supporto di nuovi sistemi operativi e di nuove
piattaforme hardware, e risparmia ai maintainers e ai porters di dover imparare
come intervenire manualmente per riuscire nella compilazione. Scrivendo uno
script con attenzione, gli sviluppatori possono supportare nuove piattaforme
senza alcuna modifica.
I contenuti di questo documento sono rilasciati sotto la licenza Creative
Commons - Attribution / Share Alike.
|
|