Nota: Questo documento vuole essere solo una breve guida introduttiva. Per un reale approfondimento si rimanda al manuale di GNU Make, normalmente disponibile in ogni sistema digitando "info make" e in rete nei manuali GNU.
Lo scopo principale dell'utility make è quello di attuare automaticamente i comandi
necessari a compilare un programma, in tutto o in parte, in seguito a eventuali modifiche.
Risparmia quindi allo sviluppatore la fatica di ricordare e
digitare lunghe righe di comando per il compilatore (semplicemente sosituite dal comando
"make"). Ottimizza inoltre il tempo di compilazione
laddove siano state modificate solo alcune parti del sorgente.
Vista la capacità di gestire regole e dipendenze è utilizzato anche in altri casi simili
(ad esempio per la creazione ricorsiva di archivi).
Il comando make determina le regole di compilazione da un file, che in assenza di specifica si chiama Makefile. Nel caso più semplice per compilare il programma sarà sufficiente digitare (nella directory del Makefile) il comando make. Il comando può essere seguito da alcuni specificatori (o target) che ne modificano il comportamento. Il funzionamento di questi target dipende ovviamente dal contenuto del Makefile. I più comuni, utilizzati convenzionalmente in quasi tutti i makefile, sono:
Make lavora tramite una sequenza di regole. La struttura tipo della regola è la seguente:
target : dependencies command1 [command2] [...Che significa: per realizzare target devi eseguire i comandi command, i comandi, inoltre, vanno eseguiti solo se qualcuna delle dependencies è più recente di target (quindi è stata modificata dall'ultima compilazione).
E' importante notare che il target DEVE iniziare nella prima colonna di testo (non può essere preceduto da blanks), mentre i comandi DEVONO essere preceduti da un tab, e non da spazi.
edit : main.o kbd.o command.o cc -o edit main.o kbd.o command.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c clean : rm edit main.o kbd.o command.oSpiegazione:
Il makefile utilizza una sintassi per le variabili analoga a qualla della shell. E' qundi necessario far precedere il segno $ al nome per valutare una variabile ($VAR è il valore della variabile VAR). Per convenzione per i nomi delle variabili vengono utilizzate lettere maiuscole.
Sono inoltre definite alcune variabili automatiche. Le più comuni sono:
$@ : file target di una regola (in caso di target multipli si riferisce al target corrente) $% : Se il target è un archivio è il nome del file nell'archivio (mentre $@ è il nome dell'archivio) $? : elenco delle dipendenze non realizzate (cioè più recenti del target) $^ : elenco di tutte le dipendenze ...
Sono definite alcune regole implicite per le operazioni più comunemente eseguite. Ad esempio:
$(CC) -c $(CPPFLAGS) $(CFLAGS)è la regola di compilazione di un sorgente C per ottenere il corrispondente file oggetto. E' quindi sufficiente definire le variabili CC, CPPFLAGS, CFLAGS (rispettivamente il nome del compilatore, e gli eventuali flags di compilazione). Similmente esistono regole impicite per Pascal, C++, Fortran, Modula2, Tex, ecc.
OBJECTS = main.o kbd.o command.o edit : $(OBJECTS) cc -o edit $(OBJECTS) main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h .PHONY : clean clean : rm edit $(OBJECTS)Il makefile realizza le stesse regole dell'esempio 1 ma con una scrittura più sintetica (e realistica). Come si vede non è necessario specificare la regola per ottenere il .o dal rispettivo .c.
NOTA: la riga che inizia con .PHONY serve ad elencare i successivi phony target. Questo per evitare che l'esistenza di un file con nome corrispondente ad un phony target (clean in questo caso) dia luogo ad effetti indesiderati.
CC=gcc CFLAGS=-Wall -g LDLIBS= all: main main: main.cQuesto makefile funziona affidandosi interamente alle regole implicite.
Questo è un esempio piuttosto comune di makefile, utilizzabile assegnando semplicemente il nome dell'eseguibile, la lista dei sorgenti e le librerie. Il makefile completa le dipendenze, includendo anche gli header file, utilizando le capacità del comando gcc -MM il cuoi output viene scritto in un file (.depend) nascosto nella stessa directory del makefile.
#nome dell'eseguibile da generare PROG = hellow # lista dei sorgenti separati da spazi SOURCES = main.cpp # librerie da includere (-lnomelib1 -lnomelib2 ...) # ed eventuali path non standard (-L/path/ ...) LIBS = # flags del compilatore (Es: -O2:release, -g:debug) # ed eventuali include path non standard (-I/path/) CFLAGS = -g #comando compilatore e linker (gcc, g++, ecc) CC = g++ #####fine parte da editare###################### CXXFLAGS = $(CFLAGS) TOBJ = $(SOURCES:.cpp=.o) OBJS = $(TOBJ:.c=.o) all: .depend $(PROG) $(PROG) : $(OBJS) $(CC) $(CFLAGS) -o $(PROG) $(OBJS) $(LIBS) .PHONY: clean .depend clean: rm -f $(PROG) $(OBJS) .depend .depend: $(CC) -MM $(INCS) $(SOURCES) >.depend ifeq ($(wildcard .depend), .depend) include .depend endif
Il makefile seguente è un esempio più completo.
Include anche i target help (stampa a video l'help) e release (ricompila tutto ottimizzato)
# Makefile generico per C e C++ # Autore: Matteo Lucarelli #####parte da editare########################### #nome dell'eseguibile da generare PROG = # lista dei sorgenti separati da spazi SOURCES = # librerie da includere (-lnomelib1 -lnomelib2 ...) # ed eventuali library path non standard (-L/path/ ...) LIBS = # flags del compilatore (-Wall, ecc.) # ed eventuali include path non standard (-I/path/) ALLFLAGS = -Wall # flag specifici per release RELEASE = -O2 # flag specifici per debug DEBUG = -g #comando compilatore e linker (gcc, g++, ecc) CC = g++ #####fine parte da editare###################### CFLAGS=$(ALLFLAGS) $(DEBUG) CXXFLAGS=$(CFLAGS) TOBJ = $(SOURCES:.cpp=.o) OBJS = $(TOBJ:.c=.o) release: CFLAGS = $(ALLFLAGS) $(RELEASE) release: CXXFLAGS=$(CFLAGS) #all release: depend $(PROG) # @echo $@ DONE all : depend $(PROG) @echo " BUILD DONE" release: clean depend $(PROG) @echo " RELEASE DONE" debug: clean depend $(PROG) @echo " DEBUG DONE" $(PROG) : $(OBJS) $(CC) $(CFLAGS) -o $(PROG) $(OBJS) $(LIBS) .PHONY: clean depend help clean: rm -f $(PROG) $(OBJS) .depend @echo " SOURCES CLEAN" depend: $(CC) -MM $(INCS) $(SOURCES) >.depend help: @ echo @ echo "Standard Makefile for C/C++ code" @ echo " make/make all: compile all (default is debug mode)" @ echo " make release : clean and release compile" @ echo " make debug : clean and debug compile" @ echo " make clean : clean sources" @ echo "Current settings are:" @ echo " executable :" $(PROG) @ echo " sources :" $(SOURCES) @ echo " libraries :" $(LIBS) @ echo " debug flags :" $(ALLFLAGS) $(DEBUG) @ echo " release flags:" $(ALLFLAGS) $(RELEASE) @ echo ifeq ($(wildcard .depend), .depend) include .depend endif
Scaricando pacchetti sorgenti si incontreranno spesso dei makefile estremamente lunghi e complicati. Tali makefile sono spesso il risultato dell'utilizzo delle utility autoconf e automake che servono appunto ad automatizzarne la creazione per progetti molto complessi (nel qual caso solitamente il makefile viene creato dal comando configure).