Makefile
Posted: Giugno 20th, 2007 | Author: packz | Filed under: Guide | 1 Comment »Uno degli splendidi strumenti costruiti dai programmatori del mondo Unix è rappresentato dal programma make che ha lo scopo di aiutare uno sviluppatore nello sviluppo di progetti di grosse dimensioni tenendo conto in automatico quali file sono stati modificati e dunque necessitano operazioni su di esse.
Nasce per opera di Stuart Feldman nei laboratori Bell nel 1977 (esistevano comunque altri tools similari ma questo è quello che si è diffuso di più!); basa principalmente il suo funzionamento sull’interpretazione di file nominati Makefile da cui deduce le cosidette regole.
Per averlo installato sul sistema si necessita del pacchetto make da installarsi sui sistemi debian-like attraverso il ben noto
apt-get install make
Rules (info make rules)
È la parte costitutiva dei Makefile: sono costituiti da un target (cioè un file o più file da creare) e da i suoi/loro prerequisiti necessari per ottenerlo; tra loro sono separati dai due punti. Subito sotto vanno posizionati i comandi da eseguire ognuno preceduto da un [TAB] ad inizio linea:
target:prerequisites
[TAB]command1
[TAB]…
[TAB]commandn
make segue la strategia: controlla se i prerequisiti esistono, in caso contrario cerca se esistono regole implicite od esplicite per ottenerli (in caso contrario esce con errore), se esistono controlla che non siano stati modificati e nel caso uno o più di essi risulti modificato riesegue su di esso la regola ed infine esegue la regola iniziale.
Commands (info make commands)
Come abbiamo visto subito sopra, all’interno delle regole abbiano i comandi da eseguire per ottenere il target: questi non vengono eseguiti da make, ma gestiti attraverso una shell che interpreta direttamente tutto quello che si trova nelle righe che iniziano con il carattere [TAB]; quindi se dopo questo la linea è vuota alla shell arriverà un comando "vuoto", se è presente un commento sarà interpretato come tale solo se lo è anche per la shell. Anche la definizione di variabili è interna alla shell e non influenzerà i comandi successivi: nel caso
echo:
@PIPPO="la madonna piange sperma" && echo $$PIPPO
@echo $$PIPPO
si avrà il seguente output da console
packz@godel:/tmp
🙂 $ make
la madonna piange spermapackz@godel:/tmp
🙂 $
Si può notare che nel primo caso avviene l’output seguendo il contenuto della variabile PIPPO, mentre nella riga seguente no (tutto sta nel costutto && e ancora adesso mi sfugge perché non funga…).
È possibile spezzare comandi su più linee usando il carattere di backslash () seguito da newline ed in tal caso il make passa più righe alla shell (se dopo n è presente il [TAB] allora viene rimosso) che interpreterà questi in base alle sue regole, per esempio
all : ; @echo ‘hello
world’ ; echo "hello
world"
verrà trasformato nel comando di shell
echo ‘hello
world’ ; echo "hello
world"
da cui risulterà un output
hello
world
hello world
siccome essa avrà tenuto conto delle regole di quoting interne (in questo caso specifico ci si riferisce ad/bin/sh).
Variabili
Ovviamente è possibile definire delle variabili che possono influenzare il comportamento del programma e dei comandi durante l’esecuzione dello stesso: per definirne una basta collocare nel Makefile una riga del tipo
NOME_VARIABILE=valore
per poi richiamarlo nei comandi tramite la forma
$(NOME_VARIABILE) oppure ${NOME_VARIABILE}
Da notare che siccome il carattere $ è utilizzato da make per scopi suoi, nel caso abbiate la necessità di passare quel carattere alla shell, dovete raddoppiarla, cioé usare $$ (vedasi esempio sopra).
Per ogni regola make crea automaticamente delle variabili che individuano secondo alcune modalità i target ed i prerequisiti (vedi sezione "automatic variables").
Automatic Variables (info make "automatic variables")
Sono variabili definiti per ogni regola ed identificano i prerequisiti etc…
- $@ – target
- $% – utilizzato solo dagli archivi ed individua l’elemento interno dell’archivio del target; nei casi in cui nel target non c’è un archivio è nulla.
- $< – primo prerequisito
- $? – prerequisiti più nuovi (modificati)
- $^ – prerequisiti (senza duplicati)
- $+ – prerequisiti (con duplicati)
- $| –
- $* – radice con cui la regola implicita (se è il caso questo) viene identificata
Pattern rules (info make "pattern rules")
Consistono di regole definite attraverso i suffissi/prefissi/radici riconoscibili negli elementi da elaborare: per capirci, in tutti i progetti di programmi in C, i file oggetto vengono sempre creati a partire da file con la stesso nome ma con il suffisso cambiato da ‘.c’ a ‘.o’, esplicitato tramite una regola da Makefile diventa
file.o:file.c
gcc file.c -c
siccome è una regola condivisibile tra tutti i file di questo tipo, è possibile definire delle regole che tengano conto dello schema dei file. Fondamentalmente le pattern rules
- contengono il carattere % che individua qualunque stringa non nulla
- nei prerequisiti (il carattere %) viene sostituito a runtime con la radice individuata nel target
quindi nel caso precedente è possibile riscrivere la regola usando % nel seguente modo
%.o:%.c
gcc $< -c
(l’uso di $< vedi "automatic variables"); la particolarità consiste nell’aver esteso la regola a qualunque file .o, basta che sia presente il corrispettivo file .c nella directory corrente. Per conoscere le regole interne predefinite usate make -p in una directory senza un makefile; così scoprirete che un file .o viene creato da un file .c secondo la regola
%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
dove vengono definite in automatico le variabili
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
OUTPUT_OPTION = -o $@
mentre un file eseguibile viene generato da un file .o attraverso la regola
%: %.c
# commands to execute (built-in):
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
dove
LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
Including
È possibile inserire altri makefile all’interno di un Makefile usando la direttiva
[-]include FILENAMES
dove il dash si usa nel caso in cui non si desiderano warning nel caso non esistano; questo è utile nel caso le dipendenze dei file vengano generati in automatico tramite l’opzione -M del compilatore (gcc ha anche l’opzione -MM che non include le librerie standard che probabilmente non verranno modificate dal programmatore e quindi non sono vincolanti per l’aggiornamento del file oggetto): nelle pagine di info viene consigliato di creare un file .d per ogni sorgente .c per poi includerlo esplicitamente tramite
SRC= file1.c file2.c file3.c
include $(SRC:.c=.d)%.d: %.c
@set -e; rm -f $@;
$(CC) -MM $(CPPFLAGS) $(CPP_CAIRO) $(CPP_PANGO) $(CPPFLAGS_X11) $< > $@.$$$$;
sed ‘s,($*).o[ :]*,1.o $@ : ,g’ < $@.$$$$ > $@;
rm -f $@.$$$$
Nel caso in cui gli include non vengano trovati alla prima lettura del Makefile, make cerca di generarli o aggiornarli e solo in questo caso la loro mancanza crea un errore "fatale" che blocca la sequenza.
Archivi
Un discorso a parte merita la gestione degli archivi da parte di make: è possibile usare un membro interno di un archivio come target e/o prerequisito in una regola seguendo lo schema
nome_archivio(nome_elemento)
nel caso sia necessario è possibile individuare più elementi interni all’archivio separandoli con degli spazi come
nome_archivio(nome_primo_elemento nome_secondo_elemento)
Per esempio ecco un Makefile che a partire dai file .c genera un archivio ‘packz’
ARCHIVIO=packz
MEMBERS=$(wildcard *.c)$(ARCHIVIO):$(ARCHIVIO)($(MEMBERS))
@echo "Creating archive from files $(MEMBERS)"(%):%
ar rv $(ARCHIVIO) $%clean:
rm -f $(ARCHIVIO)
(per adesso la gestione degli archivi pare funzionare solamente con ar!).
Informazioni
- info make
- Guida sul sito della GNU
Ottimo tutorial… Ma non ci si poteva aspettare niente di meno da uno come te…
Packz rules… (Degno di menzione)
NB Lodate Javhè miscredenti…