Il comando make è uno dei più utilizzati in ambiente Unix-like per gestire le operazioni che devono essere effettuate in corrispondenza dell’aggiornamento di determinati file da cui altri file dipendono. L’uso più frequente è quello per la compilazione dei sorgenti e l’installazione dei file eseguibili sul sistema, ma può essere utilizzato anche per altri scopi. GNU/Linux utilizza make di GNU che è conforme allo standard POSIX. Il comando make basa il suo funzionamento sul contenuto di un file detto genericamente makefile, che descrive le relazioni di dipendenza tra file interessati all’operazione, e sulla data dei file convolti.
Con il termine makefile si indica genericamente un file in formato testo che esprime le dipendenze tra determinati file ed i comandi che devono essere eseguiti da make.
Essenzialmente un makefile contiene delle regole del tipo
target : [prerequisites] [; command]
command
...
dove target è il nome di uno o più file o un’etichetta (phony target), prerequisites è un elenco
di file dai quali dipende target e command è il comando da lanciare in esecuzione
(possono essere specificati più comandi, uno per ogni riga). Va sottolineato che i
comandi sono riconosciuti dal fatto che il prima carattere della riga che li contiene è un
TAB.
Nelle indicazioni dei target e prerequisites possono essere utilizzate le wildcard ‘*’, ‘?’ e ‘[...]’ il cui significato è quello attribuito dalla shell. Inoltre può essere utilizzata la wilcard ‘~’ che indica la home directory dell’utente che ha lanciato make (es. ~/prova indica il file (o directory) prova contenuto nella home directory dell’utente che ha lanciato make) o la home directory dell’utente di seguito indicato (es. ~daniele/prova indica il file (o directory) prova contenuto nella home directory dell’utente daniele). Le wildcard possono essere fatte considerare per il loro valore letterale anteponendo alla wildcard stessa il simbolo ‘\’.
Quando viene lanciato il comando
$ make [target]
make ricerca il makefile nella directory corrente, ovvero un file denominato
GNUmakefile, makefile o Makefile (viene considerato il primo trovato in
quest’ordine)19
e quindi ricercato al suo interno il target specificato sulla riga di comando o quello di
default (nel caso in cui non sia specificato). Il target di default, detto anche default
goal, è il primo che make trova nel makefile, oppure quello indicato con il nome
.DEFAULT_GOAL20.
Per il target considerato, make controlla l’elenco dei file dai quali esso dipende: se la data di modifica dei file è più recente di quella del target (o il target non esiste affatto), viene lanciato in esecuzione l’elenco di comandi indicati dalla regola.
Un comando o l’elenco dei prerequisites può continuare sulla riga successiva inserendo alla fine della riga precedente il carattere ‘\’.
Le regole sono considerate nell’opportuna sequenza, in modo che vengano prima considerate quelle che hanno come target i file che figurano tra i prerequisiti di un altro target. Le regole dalle quali il target desiderato non dipende, non vengono considerate da make.
Nel caso in cui un target non sia il nome di un file, ma un cosiddetto phony target, è opportuno dichiararlo come tale all’inizio del makefile, in quanto si evita di incorrere nel rischio che l’esistenza di un file con tale nome induca comportamenti inattesi di make. Ad esempio, nel caso di un phony target del tipo
clean:
rm *.o temp
è meglio anteporre la riga di dichiarazione di phony target come riportato di
seguito, in modo che l’esistenza di un file clean sia la causa della non esecuzione del
target.
.PHONY: clean
clean:
rm *.o temp
All’interno del makefile possono essere utilizzate, per comodità, delle variabili in maniera
da definirle una sola volta e riutilizzare il loro contenuto da varie parti. La definizione di una
variabile è indicata da una riga del tipo
variable = value
dove variable è il nome della variabile alla quale viene assegnato il valore value, che può
essere qualsiasi cosa (spesso si tratta di un elenco di nomi di file).
All’interno del makefile, quando si vuol far riferimento al contenuto della variabile variable si deve utilizzare il simbolo $(variable).
Alcune varibili sono interpretate in modo particolare da make. Ad esempio la variabile VPATH indica l’ordine delle directory nelle quali viene effettuata la ricerca dei file, ovvero i prerequisites (la sintassi è analoga a quella della variabile PATH21). A tale scopo si può utilizzare anche la direttiva vpath che può anche specificare il percorso di ricerca per determinati pattern. La sintassi è la seguente
vpath pattern directories
dove pattern è l’elenco dei file eper i quali deve essere considerato il percorso di ricerca
indicato da directories. La wildacrd utilizzabile come qualunque sequenza di caratteri in pattern
è ‘%’.
Nel caso in cui il comando debba dipendere da uno dei nomi dei file specificati nei presequisites, può essere utile considerare l’uso delle variabili automatiche $^, che contiene l’elenco dei presequisites per la regola considerata completi di path, $<, che contiene soltanto il primo nome presente nei presequisites, e $@, che contiene il nome del target considerato.
Poiché il makefile è principalmente utilizzato per la compilazione di sorgenti C, non è necessario specificare il comando per la compilazione dei file sorgenti, ovvero il comando relativo alla pura compilazione dei sorgenti può essere omesso. Inoltre possono essere omessi anche i nomi dei file .c per i target dei relativi file oggetto (con estensione .o). Ad esempio la riga
main.o : defs.h
è sufficiente per indicare la produzione del file oggetto main.o compilando i file defs.h e
main.c.
Il simbolo ‘#’ indica l’inizio di un commento che si protrae fino alla fine della riga considerata. Per indicare il simbolo ‘#’ vero e proprio e non l’inizio di un commento, è necessario scrivere ‘\#’.
Le righe che iniziano con un TAB vengono passate all’interprete dei comandi (shell) così come sono, sia che contengano il simbolo ‘#’ o meno. Nelle righe che rappresentano definizioni di varibili il simbolo ‘#’ è considerato per quello che effettivamente è, e non l’inizio di un commento. Quando però si considera il contenuto della variabile, si tiene conto del significato di inizio commento dell’eventuale carattere ‘#’.
Inoltre è possibile includere altri makefile all’interno di un makefile. Questo può essere fatto con
include makefiles
dove makefiles è un elenco di nomi di file, che in realtà hanno la sintassi di un makefile (per
specificare i nomi dei file possono essere usate anche le wildcard).
Il nome di un makefile da includere, a meno che non inizi con il carattere ‘/’, viene ricercato nella directory corrente, nelle directory indicate con l’opzione -I e quindi nelle directory prefix/include (generalmente /usr/local/include), /usr/gnu/include, /usr/local/include, /usr/include.
Può essere utilizzata anche la direttiva -include (o sinclude) che ha la stessa funzione di include ma non riporta errori o warning nel caso in cui uno dei file non esista.
Possono essere specificate anche delle istruzioni condizionali che permettono alle parti del makefile da esse racchiuse di essere considerate o meno, dipendentemente dal valore di alcune variabili. Ad esempio, le righe riportate di seguito
ifeq ($(compiler),gcc) $(compiler) -o pippo $(obj_files) $(libs_per_gcc) else $(compiler) -o pippo $(obj_files) $(altre_libs) endif
Possono essere specificati anche costrutti condizionali con un solo ramo (senza il ramo else) e con più di due rami. In quest’ultimo caso possono essere definiti più rami else condizionali che rappresentano un costrutto condizionale nidificato all’interno di quello di cui fanno parte.
ifeq ... ... else ifeq ... ... else ... ... endif
È inoltre possibile controllare se una variabile è vuota o meno con la parola chiave ifdef oppure controllare se una variabile non lo è con ifndef.
[da completare ...]