Gentoo Logo

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.



Stampa

Aggiornato il 16 dicembre 2005

La versione originale di questo documento è più recente ed è stata aggiornata il 16 dicembre 2005

Oggetto: Questo articolo tratta alcuni degli errori più comuni commessi da chi utilizza gli autotools, e mostra come ottenere migliori risultati.

Diego Pettenò
Autore

Cristiano Chiucchiolo
Traduttore

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.