[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 572.   Compilazione guidata con Make

La compilazione di un programma, in qualunque linguaggio sia scritto, può essere un'operazione molto laboriosa, soprattutto se si tratta di aggregare un sorgente suddiviso in più parti, o peggio, se si tratta di un progetto costituito da più programmi. Per semplificare la procedura si potrebbe predisporre uno script che esegue sequenzialmente tutte le operazioni necessarie, ma la tradizione richiede di utilizzare il programma Make.

Uno dei vantaggi più appariscenti nell'uso di Make sta nella possibilità di evitare che vengano rielaborati i file che non sono stati modificati, abbreviando quindi il tempo di compilazione necessario quando si procede a una serie di modifiche limitate.

Make viene usato normalmente assieme a uno script, denominato comunemente Makefile,(1) scritto in un modo che dovrebbe risultare molto semplice da interpretare; tuttavia, è comunque possibile fare il contrario, specialmente con le versioni più evolute di tale programma. Evidentemente, Make è utile quando lo si utilizza con moderazione, ovvero con uno script semplice e lineare, altrimenti uno script di shell è sicuramente più appropriato al caso.

Nel capitolo, gli esempi che mostrano script di Make non contengono commenti, pertanto è bene chiarire subito che le righe bianche o vuote vengono ignorate, così come si ignora il testo che appare alla destra del simbolo #.

572.1   Obiettivo, dipendenze e comandi

Make viene usato per realizzare un obiettivo attraverso uno o più comandi da impartire alla shell (precisamente /bin/sh), dopo che sono stati soddisfatti altri eventuali obiettivi da cui quello finale dipende. In linea di principio, l'obiettivo è rappresentato dal nome di un file che deve essere generato.

Per esempio, volendo produrre il programma somma che si ottiene dalla compilazione dei file uno.c e due.c, l'obiettivo «somma» che si ottiene con il comando ld -o somma uno.o due.o, dipende dagli obiettivi «uno.o» e «due.o», in quanto i file uno.o e due.o devono essere presenti per poter eseguire il collegamento con il programma ld.

make somma

Nello script di Make, l'obiettivo di esempio mostrato si descrive come si vede nella figura successiva, dove il tratteggio verticale a sinistra rappresenta l'inizio della prima colonna. Ciò che descrive un obiettivo è, nel suo complesso, una regola:

make somma

Si deve tenere a mente che la riga che definisce l'obiettivo e le dipendenze deve iniziare dalla prima colonna, mentre le righe contenenti dei comandi devono trovarsi rientrate con un carattere di tabulazione orizzontale (<HT>); al contrario, degli spazi veri e propri come rientro non sono ammissibili.

L'esempio introdotto è incompleto, perché non esplicita in che modo ottenere gli obiettivi uno.o e due.o. Ecco come potrebbe essere composto lo script completo delle regole che descrivono tutte le dipendenze:

somma: uno.o due.o
        ld -o somma uno.o due.o

uno.o: uno.c mate.h
        cc -c -o uno.o uno.c

due.o: due.c
        cc -c -o due.o due.c

Per comprendere l'esempio va chiarito che per ottenere il file uno.o è necessario il file uno.c che a sua volta include il file mate.h.

Il vantaggio di usare Make sta nel fatto che questo tiene conto della data di modifica dei file, nel momento in cui valuta le dipendenze. Nel caso dell'esempio, per eseguire il collegamento (link) dei file oggetto nel file eseguibile somma, è necessario disporre di tali file oggetto, ma se il file eseguibile esiste e se questi file oggetto esistono e hanno una data di modifica antecedente a quella del file eseguibile, allora sarebbe da intendere che tale operazione non sia necessaria. Tuttavia, i file uno.o e due.o sono indicati come obiettivi da ottenere attraverso altri file: nel caso di uno.o è stabilito che dipende dai file uno.c e mate.h; nel caso di due.o è stabilito che dipende solo dal file due.c (si osservi che per i file uno.c, mate.h e due.c non sono state dichiarate altre dipendenze). A questo punto è logico attendersi che anche la data dei file di partenza conti. In pratica, le date di modifica di uno.c e mate.h devono essere antecedenti a qualla di uno.o e così deve essere antecedente anche quella di due.c rispetto a quella di due.o. Se a un certo punto si modifica il file mate.h (e quindi la data di modifica viene aggiornata dal sistema operativo), la dipendenza che riguarda il file uno.o richiede la ripetizione dei comandi relativi; quindi viene ricompilato il file uno.o e viene eseguito nuovamente il collegamento che genera il file eseguibile somma.

Figura 572.4. Modello sintattico di una regola, per la definizione di un obiettivo in uno script di Make.

obiettivo...: [dipendenza...]
<HT>comando
...

Per ottenere lo stesso risultato pratico dell'esempio mostrato, si può modificare il modo in cui si indica la dipendenza dovuta al file mate.h:

...
uno.o: uno.c
        cc -c -o uno.o uno.c

uno.c: mate.h
        touch uno.c mate.h
...

Ciò che appare nel pezzo mostrato indica che il file uno.o dipende da uno.c soltanto, ma il file uno.c dipende dal file mate.h. Se il file mate.h si trova ad avere una data più recente di uno.c, le date vengono rese uguali e viene rifatta la compilazione.

572.2   Obiettivi fittizi

In generale, un obiettivo di Make viene raggiunto con la creazione o l'aggiornamento di un file che ha lo stesso nome dell'obiettivo, attraverso dei comandi stabiliti. In pratica, l'obiettivo è quel file da generare o aggiornare. Tuttavia, spesso si definiscono obiettivi che non implicano la creazione di un file con tale nome; pertanto servono per essere eseguiti sempre, assicurando che le dipendenze eventuali siano rispettate.

all: somma moltiplicazione

somma: ...
        ...
moltiplicazione: ...
        ...
...

L'esempio mostra una situazione tipica in cui si utilizza un obiettivo fittizio, in questo caso denominato all. Questo obiettivo ha il solo scopo di richiamare automaticamente gli obiettivi somma e moltiplicazione (ma nell'esempio, questi ulteriori obiettivi non vengono descritti). C'è da osservare però una cosa importante: se per qualunque ragione dovesse esistere un file con lo stesso nome dell'obiettivo, avente una data di modifica successiva a quella dei file degli obiettivi da cui dipende, l'operazione non verrebbe eseguita, salve naturalmente altre ipotesi riferite alle dipendenze degli obiettivi precedenti.(2)

Per ovviare all'inconveniente dovuto alla possibilità che esista un file con lo stesso nome di un obiettivo fittizio, non correlato a tale file, si può usare uno strattagemma consolidato:

clean: FORCE
        rm *.o core

FORCE:

In questo caso, l'obiettivo FORCE (usato comunemente per questo scopo), non ha dipendenze, non ha comandi, inoltre si dà per certo che non possa esistere un file con lo stesso nome; pertanto l'obiettivo risulta sempre da raggiungere. L'obiettivo clean che ha evidentemente lo scopo di eliminare alcuni file non più necessari, dipendendo dall'obiettivo FORCE, viene eseguito in ogni caso, anche se esistesse un file clean, perché la dipendenza non è mai soddisfatta.(3)

572.3   Scelta dell'obiettivo

Make è costituito generalmente dal programma eseguibile make e si usa solitamente secondo la sintassi seguente:

make [opzioni] [obiettivi]

Per esempio, il comando seguente richiede a Make di «raggiungere» l'obiettivo somma:

make somma[Invio]

Se però non si specifica l'obiettivo, questo viene determinato in modo predefinito:

make[Invio]

Ammesso che nella directory corrente sia presente lo script di Make (per convenzione deve trattarsi del file Makefile), l'obiettivo viene cercato al suo interno e se non è stato definito si intende il primo che appare nel file.(4)

È comunque possibile utilizzare Make anche senza script, ma in tal caso l'indicazione dell'obiettivo nella riga di comando è obbligatoria. L'utilizzo di Make senza uno script dipende da quelle che sono definite regole implicite. In pratica, quando si richiede un obiettivo non previsto espressamente, Make cerca di fare la cosa più logica, partendo dal presupposto che il contesto sia relativo alla compilazione di un programma. Si osservi l'esempio seguente:

make prova[Invio]

Se non è stato definito l'obiettivo prova, Make considera il contenuto della directory corrente e cerca qualcosa che sia ragionevolmente trasformabile nel file prova. Per esempio, se trova il file prova.c esegue automaticamente il comando cc -o prova prova.c. Questa proprietà di Make consente di omettere la descrizione delle regole degli obiettivi «ovvi». Questo sistema di regole implicite serve anche per semplificare il lavoro di stesura di uno script di Make, quando si descrive un obiettivo finale e non si stabiliscono le regole per ottenere le dipendenze:

somma: uno.o due.o
        ld -o somma uno.o due.o

uno.c: mate.h
        touch mate.h uno.c

Questo esempio richiama quanto già mostrato in precedenza: dato che la costruzione dei file uno.o e due.o richiede dipendenze prevedibili, non è necessario descriverne le regole.

572.4   Interpretazione dei comandi che portano a un obiettivo

In condizioni normali, i comandi che devono essere eseguiti per il raggiungimento di un certo obiettivo, vengono passati alla shell /bin/sh, indipendentemente dalla shell utilizzata dall'utente che avvia il programma make.

I comandi troppo lunghi possono essere spezzati e ripresi nella riga successiva, se alla fine della riga interrotta appare il simbolo \, esattamente come sarebbe in uno script per una shell Bourne. C'è però da osservare che, in questo caso, il comando passato alla shell comprende letteralmente sia \, sia il codice di interruzione di riga successivo, ma questo fatto, di norma, non ha conseguenze nel risultato.

Sul problema dell'interruzione e proseguimento delle righe dei comandi occorre soffermarsi su un fatto: nella riga che viene ripresa, il carattere di tabulazione iniziale viene omesso automaticamente, nel momento in cui viene chiesto alla shell di eseguire il comando. L'esempio seguente rappresenta il contenuto di uno script che dovrebbe chiarire il meccanismo. Per ora si sorvoli sulla presenza della chiocciola all'inizio dei comandi:

esempio:
        @echo "supercalifragilisti\
        chespiralidoso"
        @echo "supercalifragilisti \
        chespiralidoso"

Ecco cosa succede:

make esempio[Invio]

supercalifragilistichespiralidoso
supercalifragilisti chespiralidoso

Si può osservare che nel primo caso la parola è rimasta unita, mentre nel secondo è separata perché uno spazio è stato inserito prima della segnalazione dell'interruzione.

I comandi di una regola sono eseguiti uno alla volta, ma Make tiene conto del risultato. Se il comando eseguito restituisce zero, ovvero se risulta eseguito correttamente, allora Make avvia il successivo, altrimenti interrompe l'operazione segnalando il fallimento dell'obiettivo e di quelli che da lui dipendono.(5) Pertanto, se i comandi possono restituire un errore anche se ciò non pregiudica il raggiungimento dell'obiettivo previsto, occorre provvedere in qualche modo. Per esempio così:

obiettivo: ...
        ...
        mkdir ciao ; true
        ...

In questo caso, la regola che descrive l'obiettivo contiene un comando che serve a garantire la presenza di una certa directory. Il comando in questione potrebbe fallire se la directory esiste già, senza per questo pregiudicare il resto del procedimento, così si unisce al comando true che complessivamente fa sì che l'esito sia sempre «corretto». È comunque possibile usare un prefisso che informa Make di ignorare gli errori; si tratta del segno -, pertanto l'esempio appena apparso può essere modificato così:

obiettivo: ...
        ...
        -mkdir ciao
        ...

In generale, prima di avviare ogni comando, Make lo visualizza, in modo da far capire ciò che accade all'utente. In alcune situazioni, però, ciò può essere spiacevole, pertanto è possibile utilizzare il prefisso @ che evita tale comportamento:

mio: ...
        @echo "sto per eseguire la compilazione, bla bla bla..."
        cc -o mio mio.c

Come si vede nell'esempio, si vuole fare in modo che il comando echo non sia «descritto», dato che già serve a mostrare qualcosa.

Tabella 572.14. Alcuni prefissi da usare nelle righe che contengono comandi.

Prefisso Significato
-
fa in modo che gli errori vengano ignorati;
+
fa in modo che il comando venga eseguito sempre;
@
fa in modo che il testo del comando non venga mostrato.

572.5   Variabili o «macro»

All'interno di uno script di Make è possibile definire delle variabili, altrimenti note come «macro». Le variabili si dichiarano attraverso direttive espresse nella forma seguente:

nome = stringa

In particolare, la stringa non deve essere delimitata e l'ordine della dichiarazione delle variabili non viene tenuto in considerazione, come dimostrato poco più avanti. L'espansione di una variabile si indica attraverso due modi possibili:

$(nome)

Oppure:

${nome}

Si osservi l'esempio seguente, in particolare a proposito del fatto che l'ordine di dichiarazione delle variabili non è significativo:

bindir = $(exec_prefix)/bin
prefix = /usr/local
sbindir = $(exec_prefix)/sbin
exec_prefix = $(prefix)

all:
        @echo "prefix = $(prefix)"
        @echo "exec_prefix = $(exec_prefix)"
        @echo "bindir = $(bindir)"
        @echo "sbindir = $(sbindir)"

Ammesso che questo sia lo script di Make contenuto nella directory corrente:

make[Invio]

prefix = /usr/local
exec_prefix = /usr/local
bindir = /usr/local/bin
sbindir = /usr/local/sbin

Il fatto che l'ordine nella dichiarazione nelle variabili non conti, implica che l'assegnamento a una variabile del proprio stesso contenuto produca un circolo vizioso. In pratica, una cosa come la dichiarazione seguente non è ammissibile:

opzioni = -c
opzioni = -gstabs $(opzioni)

Invece di agire così, per aggiungere qualcosa a una variabile occorre una direttiva differente:

nome += stringa

Si osservi l'esempio seguente e ciò che succede provando a usare make:

opzioni = -c
opzioni += -gstabs

all:
        @echo "opzioni = $(opzioni)"

make[Invio]

opzioni = -c -gstabs

Come si può intendere, le variabili di Make che appaiono all'interno dei comandi, vengono espanse prima dell'esecuzione dei comandi stessi; di conseguenza, se si vuole usare il dollaro ($) in modo che la shell lo recepisca, occorre raddoppiarlo:

obiettivo: ...
        ...
        NUM=3 ; echo $$NUM
        ...

GNU Make recepisce le variabili di ambiente e le assimila tra le proprie variabili, ma se nel proprio script vengono ridefinite, ciò prende il sopravvento sul valore ottenuto dall'esterno.

Make prevede delle variabili predefinite, il cui scopo principale è controllare il funzionamento delle regole implicite, ma che spesso vengono usate per coerenza anche nei comandi di obiettivi dichiarati esplicitamente. La tabella 572.23 ne elenca alcune e l'esempio successivo, riprendendone un altro già apparso, mostra in che modo potrebbero essere usate:

somma: uno.o due.o
        $(LD) $(LDFLAGS) -o somma uno.o due.o

uno.o: uno.c mate.h
        $(CC) -c $(CFLAGS) -o uno.o uno.c

due.o: due.c
        $(CC) -c $(CFLAGS) -o due.o due.c

Trattandosi di variabili conosciute, se utilizzate correttamente si facilita la lettura dello script, consentendo di precisare, se ce ne fosse bisogno, il nome del compilatore e le opzioni da dare:

LD = ld
CC = gcc
CFLAGS = -gstabs

somma: uno.o due.o
        $(LD) $(LDFLAGS) -o somma uno.o due.o

uno.o: uno.c mate.h
        $(CC) -c $(CPPFLAGS) $(CFLAGS) -o uno.o uno.c

due.o: due.c
        $(CC) -c $(CPPFLAGS) $(CFLAGS) -o due.o due.c

Tabella 572.23. Elenco di alcune variabili predefinite di Make.

Nome Contenuto usuale Annotazioni
MAKE
make
Il nome del programma stesso. Di solito viene usata questa informazione per l'avvio di altri Make in sottodirectory con un proprio script.
SHELL
/bin/sh
La shell che deve eseguire i comandi: è bene evitare di cambiare il valore di questa variabile, ovvero, se dichiarata, è bene confermarlo.
AR
ARFLAGS
ar
rw
Il programma di archiviazione usato per creare le librerie statiche e le sue opzioni consuete.
LD
LDFLAGS
ld

 
Il programma usato per collegare i file-oggetto in un file eseguibile e le sue opzioni consuete. Di norma non sono previste opzioni particolari.
AS
ASFLAGS
as

 
Il programma usato per compilare un file in linguaggio assemblatore e le sue opzioni consuete. Di norma non sono previste opzioni particolari.
CPP
CPPFLAGS
cpp

 
Il precompilatore e le sue opzioni consuete. Di norma non sono previste opzioni particolari.
CC
CFLAGS
cc

 
Il programma usato per compilare un file in linguaggio C e le sue opzioni consuete. Di norma non sono previste opzioni particolari.
PC
PFLAGS
pc

 
Il programma usato per compilare un file in linguaggio Pascal e le sue opzioni consuete. Di norma non sono previste opzioni particolari.

572.6   Utilizzo oculato delle variabili

Dal momento che le variabili possono essere espanse in ogni posizione di uno script di Make, le definizioni ripetitive possono essere semplificate. Nell'esempio successivo si dichiara la variabile obj, contenente l'elenco dei file-oggetto coinvolti nella produzione di un certo file eseguibile:

obj = aaa.o bbb.o ccc.o ddd.o \
      eee.o fff.o ggg.o
...
prog: $(obj)
    ld -o prog $(obj)
...

Generalmente, i nomi delle variabili sono scritti utilizzando solo lettere maiuscole, ma non c'è un obbligo in tal senso. Di solito, l'utilizzo di lettere maiuscole per le variabili vuole indicare la possibilità di modificarne il contenuto per qualunque adattamento possa essere necessario; per questo, se invece si utilizzano nomi di variabili con lettere minuscole (come nell'esempio mostrato), di solito lo si fa per quelle cose che è bene non modificare.

Come già accennato, GNU Make eredita le variabili di ambiente come proprie variabili-macro, anche se poi queste possono essere ridefinite nello script. In ogni caso, il modo «normale» di assegnare un valore a una variabile, nel momento dell'avvio del programma make, è quello di usare la riga di comando, per esempio, così:

make "CFLAGS = -O" "LDFLAGS = -s" obiettivo[Invio]

Si supponga di avere uno script come quello seguente e si osservi cosa succede con il comando appena mostrato:

CFLAGS = -gstabs
LDFLAGS = -S

all:
        @echo "CFLAGS:  $(CFLAGS)"
        @echo "LDFLAGS: $(LDFLAGS)"

make "CFLAGS = -O" "LDFLAGS = -s" obiettivo[Invio]

CFLAGS:  -O
LDFLAGS: -s

Pertanto, questo modo di passare il valore alle variabili di Make prende il sopravvento sulla dichiarazione interna di uno script.

572.7   Espansione e continuazione al di fuori dei comandi

Il testo di uno script di Make, quando non costituisce un comando da passare alla shell e non si tratta nemmeno di un commento, può essere espanso, sia a causa dell'uso di variabili, sia per la presenza di caratteri che si espandono in nomi di file, come si fa comunemente per le shell POSIX (quindi si possono usare l'asterisco, il punto interrogativo e le parentesi quadre, con lo stesso significato che hanno per una shell POSIX e si possono anche proteggere i simboli, contro l'espansione, facendoli precedere da una barra obliqua inversa: \). Inoltre, è possibile continuare il testo su più righe, usando il simbolo \ alla fine della riga che deve continuare. L'esempio che appare sotto serve a mostrare l'effetto dell'espansione, ma non è un modello da seguire, perché in pratica si creerebbero delle complicazioni:

prog: *.o
        $(LD) $(LDFLAGS) -o prog *.o

In questo caso, la realizzazione del file prog dipende da tutti i file-oggetto presenti nella directory corrente. L'esempio non è utile in generale, perché se tali file sono assenti viene meno la realizzazione dell'obiettivo. È comunque interessante osservare che l'espansione di *.o nell'elenco delle dipendenze avviene per opera di Make, mentre ciò che appare nel comando viene espanso dalla shell.

572.8   Variabili automatiche

Alcune variabili non possono essere dichiarate e nemmeno modificate nel loro contenuto. Si tratta delle variabili automatiche, composte da un solo carattere. Per esempio, la variabile @ rappresenta l'obiettivo attuale, ma per espandere il suo contenuto è sufficiente scrivere $@, senza bisogno di parentesi.

Per espandere una variabile si possono sempre evitare le parentesi se il nome di questa è composto da un solo carattere; tuttavia, si preferisce rinunciare alle parentesi solo quando si tratta precisamente di variabili automatiche.

Tabella 572.28. Alcune variabili automatiche.

Variabile
automatica
Significato
*
Il nome dell'obiettivo attuale, ma senza suffisso; se l'obiettivo non ha suffisso, la variabile risulta vuota.
@
L'obiettivo attuale, completo.
<
La voce che costituisce la prima dipendenza (quella più a sinistra).
?
L'elenco delle dipendenze associate a file che sono più recenti di quello che rappresenta l'obiettivo.
^
L'elenco di tutte le dipendenze previste.
$
Si espande semplicemente nel simbolo $ e, come per tutte le variabili automatiche, va usato aggiungendo un altro dollaro: $$. In pratica, nei comandi, le variabili di ambiente vanno annotate raddoppiando il simbolo dollaro; per esempio: $$HOME.

Per comprendere meglio il significato della descrizione fatta nella tabella precedente, si consideri di disporre dei file seguenti: uno.c, due.c, somma.o. Inoltre, si suppone che solo due.c abbia una data di modifica successiva a quella di somma.o. A tale proposito, si consideri lo script seguente:

somma.o: uno.c due.c
        @echo \$$\* = $*
        @echo \$$@ = $@
        @echo \$$\< = $<
        @echo \$$\? = $?
        @echo \$$\^ = $^

Se viene avviato, si può leggere lo stato delle variabili automatiche:

make[Invio]

$* = somma
$@ = somma.o
$< = uno.c
$? = due.c
$^ = uno.c due.c

Come si può vedere, in questo caso la variabile automatica ? consentirebbe di individuare le dipendenze per le quali si richiede una nuova compilazione.

È importante notare che le variabili automatiche possono essere usate solo all'interno di comandi, perché il loro contenuto si definisce dopo la dichiarazione dell'obiettivo e delle sue dipendenze.

572.9   Regole implicite

Le regole implicite sono quelle che descrivono degli obiettivi predefiniti, nel modo più logico possibile. Come già accennato altrove nel capitolo, queste regole definiscono i comandi attraverso delle variabili che è possibile controllare.

Tabella 572.31. Comandi comuni di regole implicite.

Comando Condizione di utilizzo
$(CC) -c $(CPPFLAGS) $(CFLAGS) nome.c
Se si richiede la realizzazione dell'obiettivo denominato nome.o ed esiste il file nome.c.
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) nome.cc
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) nome.cpp
Se si richiede la realizzazione dell'obiettivo denominato nome.o ed esiste il file nome.cc o nome.cpp.
$(AS) $(ASFLAGS) nome.s
Se si richiede la realizzazione dell'obiettivo denominato nome.o ed esiste il file nome.s. Se il file nome.s è assente ma al suo posto esiste nome.S, allora il primo viene generato dal comando successivo.
$(CPP) $(CPPFLAGS) nome.S
Se si richiede la realizzazione dell'obiettivo denominato nome.s ed esiste il file nome.S.
$(CC) $(LDFLAGS) nome.o $(LOADLIBES) $(LDLIBS)
Se si richiede la realizzazione dell'obiettivo denominato nome ed esiste il file nome.o.

Naturalmente, le regole implicite si concatenano tra di loro. Per esempio, si suppone di disporre del file prova.c e di volerlo compilare utilizzando Make nel modo seguente:

make prova[Invio]

Si sta facendo riferimento all'obiettivo prova che si intende non sia stato dichiarato nello script di Make. Pertanto, per realizzare questo obiettivo, Make deve cercare una regola implicita appropriata e in questo caso è quella che serve a collegare un file oggetto prova.o:

prova: prova.o
        $(CC) $(LDFLAGS) prova.o $(LOADLIBES) $(LDLIBS)

Questa regola implicita, evidentemente, dipende da un'altra regola che descrive in che modo viene ottenuto il file prova.o. Dal momento che Make trova il file prova.c, la regola è questa:

prova.o: prova.c
        $(CC) -c $(CPPFLAGS) $(CFLAGS) prova.c

Di conseguenza viene eseguita la compilazione.

572.10   Uno script per ogni sottodirectory

Di solito si predispone uno script di Make per ogni sottodirectory che contenga qualcosa da costruire; poi, in una o in alcune directory si colloca uno script realizzato in modo da avviare lo stesso programma make nelle sottodirectory inferiori.

A titolo di esempio, si suppone di avere un progetto suddiviso in tre sottodirectory: mele/, arance/ e limoni/. All'interno di ogni sottodirectory c'è un file Makefile. Nella directory che contiene queste sottodirectory c'è un file Makefile con una regola per avviare sequenzialmente gli altri file equivalenti delle sottodirectory:

sub = mele arance limoni

all:
        for d in $(sub) ; do cd $$d ; $(MAKE) ; cd .. ; done

Viene usato un ciclo per la scansione delle sottodirectory che la shell interpreta così:

for d in mele arance limoni
do
    cd $d
    make
    cd ..
done

Ogni volta che si usa Make in questo modo, si dovrebbe vedere un avvertimento come quello seguente:

make[1]: Entering directory `/home/tizio/mele'
...
make[1]: Leaving directory `/home/tizio/mele'
make[1]: Entering directory `/home/tizio/arance'
...
make[1]: Leaving directory `/home/tizio/arance'
make[1]: Entering directory `/home/tizio/limoni'
...
make[1]: Leaving directory `/home/tizio/limoni'

Per lasciare a Make il controllo del ciclo di avvii nelle sottodirectory, si può usare un meccanismo differente, come quello che si vede nel listato successivo:

sub = mele arance limoni

all: $(sub)

$(sub): FORCE
        cd $@ && $(MAKE)

FORCE:

Si può vedere che l'obiettivo all (evidentemente un obiettivo fittizio), dipende dai nomi delle sottodirectory. Successivamente è dichiarata una regola con obiettivo multiplo, ovvero una regola che vale indifferentemente per i tre obiettivi di ogni sottodirectory (la variabile automatica $@ si espande nel nome dell'obiettivo preso in considerazione effettivamente). Tale regola dipende però da un altro obiettivo, senza dipendenze e senza comandi, per il quale si è certi che non possa esistere un file con lo stesso nome.

Come mostrato in questi esempi, invece di scrivere il nome del programma eseguibile make, è stata usata la variabile MAKE, la quale riproduce il nome del comando usato per avviare l'interpretazione dello script. Per esempio, se per qualunque motivo il programma make fosse nominato in maniera differente o fosse usato al di fuori dei percorsi di ricerca per gli eseguibili, con la variabile MAKE si garantisce sempre di trovare lo stesso programma che risulta già in funzione.

572.11   Una regola per più obiettivi

La sezione precedente introduce il concetto di obiettivo multiplo, che però può risultare difficile da intendere con l'esempio mostrato, dal momento che si utilizza una variabile per esprimere l'elenco di obiettivi. Lo stesso esempio può essere tradotto così, senza l'uso di variabili:

all: mele arance limoni

mele arance limoni: FORCE
        cd $@ && $(MAKE)

FORCE:

Senza usare una regola del genere, occorrerebbe suddividere la stessa in tre (una per ogni singolo obiettivo):

...
mele: FORCE
        cd mele && $(MAKE)

arance: FORCE
        cd arance && $(MAKE)

limoni: FORCE
        cd limoni && $(MAKE)
...

Questo dovrebbe chiarire anche l'utilità della variabile automatica $@, per individuare l'obiettivo preso effettivamente in considerazione in un dato momento.

572.12   Regole fittizie tipiche

Generalmente si organizza uno script di Make in modo da avere alcuni obiettivi fittizi, con cui eseguire le operazioni più comuni in modo complessivo. Tra questi, l'obiettivo più importante in assoluto è quello che deve essere eseguito in modo predefinito (ovvero il primo) e generalmente viene chiamato all. Tale obiettivo serve di norma per indicare soltanto delle dipendenze da soddisfare.

Tabella 572.40. Obiettivi fittizi comuni.

Obiettivo Significato comune
all
Le azioni da compiere quando non si indica alcun obiettivo in modo esplicito.
clean
I comandi da eseguire per cancellare i file oggetto, i binari già compilati ed eventualmente altri file temporanei.
install
I comandi necessari a installare i programmi dopo la compilazione.

Stando alla tabella appena mostrata, si può ricordare che le fasi tipiche di un'installazione di un programma distribuito in forma sorgente sono appunto quelle seguenti:

  1. make[Invio]

    Richiama automaticamente l'obiettivo all, coincidente con i comandi necessari per la compilazione del programma.

  2. make install[Invio]

    Provvede a installare gli eseguibili compilati nella loro destinazione prevista.

Supponendo di avere realizzato un programma, denominato mio_prog.c, il cui eseguibile debba essere installato nella directory /usr/local/bin/, si potrebbe utilizzare uno script composto come l'esempio seguente, dove l'obiettivo all richiama la dipendenza dal programma mio_prog che viene soddisfatta in modo implicito:

all: mio_prog

clean:
        rm -f core *.o mio_prog

install:
        cp mio_prog /usr/local/bin

572.13   Variabili per l'installazione

In uno script di Make realizzato per la compilazione di un programma (composto eventualmente da uno o più file eseguibili) e per la sua installazione successiva, sono presenti normalmente alcune variabili, più o meno standardizzate, per descrivere la collocazione finale dei file. Alcune di queste variabili sono elencate nella tabella successiva.

Tabella 572.42. Alcune variabili usate comunemente per definire l'installazione.

Variabile Utilizzo
prefix
La variabile prefix viene usata normalmente come punto di partenza da cui traggono origine altre variabili più specifiche. Di solito, il valore dato a questa variabile per la distribuzione di un pacchetto sorgente è /usr/local/, dove poi chi compila e installa deve attribuire un valore che per sé possa essere più appropriato.
exec_prefix
La variabile exec_prefix rappresenta il punto di riferimento iniziale per l'installazione dei file eseguibili e di solito corrisponde al contenuto di prefix.
bindir
Rappresenta la directory in cui vanno installati i file eseguibili a disposizione di tutti gli utenti e corrisponde normalmente alla directory bin/ successiva al contenuto di exec_prefix. Pertanto, se prefix e exec_prefix indicano /usr/local/, bindir indica normalmente /usr/local/bin/.
sbindir
Rappresenta la directory in cui vanno installati i file eseguibili utili per l'amministrazione e corrisponde normalmente alla directory sbin/ successiva al contenuto di exec_prefix.

Uno script che usa queste variabili potrebbe essere realizzato così:

prefix = /usr/local
exec_prefix = $(prefix)
bindir=$(exec_prefix)/bin

all: mio_prog

clean:
        rm -f core *.o mio_prog

install:
        cp mio_prog $(bindir)

572.14   Definizione della shell

In linea di principio, la shell usata per eseguire i comandi contenuti nelle regole di uno script di Make è /bin/sh. Tuttavia, il nome e il percorso esatto possono essere controllati attraverso la variabile SHELL. In generale può essere conveniente aggiungere nello script la dichiarazione seguente, in modo da non avere sorprese:

SHELL = /bin/sh

Per esempio, utilizzando GNU Make occorre considerare che le variabili di ambiente sono ereditate e la presenza eventuale di una variabile SHELL potrebbe creare problemi: è per questo che la dichiarazione suggerita può essere conveniente.

Evidentemente, nello stesso modo descritto è possibile cambiare la shell che interpreta i comandi, ma ciò è sicuramente sconsigliabile, nell'ottica della creazione di script «standard».

572.15   Installazione dei programmi

È il caso di osservare che, normalmente, l'installazione dei programmi, ovvero la loro copia nella destinazione finale, dopo la compilazione, si esegue con il programma install (eventualmente si veda il capitolo 182). Il motivo di questa scelta sta normalmente nella facilità con cui, assieme alla copia, si definiscono i permessi e la proprietà del file nella destinazione. Lo script seguente riprende gli ultimi esempi e concetti già visti:

SHELL = /bin/sh
prefix = /usr/local
exec_prefix = $(prefix)
bindir=$(exec_prefix)/bin

all: mio_prog

clean:
        rm -f core *.o mio_prog

install:
        install -o root -g bin -m 755 mio_prog $(bindir)

572.16   Riferimenti


1) Lo script di Make potrebbe essere nominato anche in altri modi, per esempio senza l'iniziale maiuscola (quindi solo makefile) ma in generale conviene attenersi al suggerimento di usare il nome Makefile che, in un elenco ordinato per nome dei file di una directory, ha il vantaggio di apparire prima di altri a causa dell'iniziale maiuscola.

2) Nel caso di GNU Make esiste una direttiva apposita per dichiarare quali obiettivi sono fittizi (phoney).

3) GNU Make offre un meccanismo più accurato per impedire che un obiettivo fittizio sia bloccato da un file, ma il metodo mostrato è valido in generale.

4) Nel caso di GNU Make, nella ricerca del primo obiettivo si escludono i nomi che iniziano con un punto, dal momento che a quelli viene dato un significato particolare.

5) Di solito il fallimento di un obiettivo può comportare la cancellazione contestuale del file corrispondente all'obiettivo mancato, anche se si tratta di una versione precedente.


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome compilazione_guidata_con_make.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory