Immaginate un progetto molto esteso, formato da decine e decine di moduli, e di voler cambiare solo una piccola parte di codice e di voler poi testare il programma. Ovviamente, per evitare di ricompilare tutto il codice ad ogni modifica, e' conveniente compilare solo i moduli appena modificati e poi effettuare il link con la parte di codice rimasta immutata. Potrebbe pero' essere difficile, o quanto meno noioso, controllare ripetutamente quali moduli devono essere per forza ricompilati e quali no. Il comando make fa questo per voi!
Il Makefile ed i target del make
Per funzionare make ha bisogno che voi scriviate un file chiamato Makefile in cui siano descritte le relazioni fra i vostri files ed i comandi per aggiornarli. Quando il make viene invocato esegue le istruzioni contenute nel Makefile.
Una idea base che bisogna capire del make e' il concetto di target . Il primo target in assoluto e' il Makefile stesso. se si lancia il make senza aver preparato un Makefile si ottiene il seguente risultato
make make: *** No targets specified and no makefile found. Stop. |
# Un esempio di Makefile one: @echo UNO! two: @echo DUE! three: @echo E TRE! |
La definizione di un target inizia sempre all'inizio della riga ed seguito da : . Le azioni (in questo caso degli output su schermo) seguono le definizioni di ogni target e, anche se in questo esempio sono singole, possono essere molteplici. La prima riga, che inizia con #, e' un commento.
Per utilizare i target invochiamoli sulla riga di comando del make:
make one UNO! make one two three UNO! DUE! E TRE! |
Se non si invoca nessun target nella linea di comando, make assume come default il primo che trova nel Makefile:
make UNO! |
IMPORTANTE: le linee in cui si specificano le azioni corrispondenti ad ogni target (Es. @echo UNO!) devono iniziare con un separatore <TAB>!
# Un esempio di Makefile mal scritto one: @echo UNO! |
make oneMakefile:4: *** missing separator. Stop.Le righe di azione devo iniziare invariabilmente con un separatore <TAB>, NON POSSONO ESSERE UITLIZZATI DEGLI SPAZI!
Dipendenze
E' possibile definire delle
dipendenze fra i target all' interno del Makefile
# Un esempio di Makefile con dipendenze one: @echo UNO! two: one @echo DUE! three: one two @echo E TRE! all: one two three @echo TUTTI E TRE! |
Si noti come i target vengono elaborati in sequenza:
make three UNO! DUE! E TRE! make all UNO! DUE! E TRE! TUTTI E TRE! |
E' possibile definere delle Macro all' interno del Makefile
#Definiamo la Macro OBJECT OBJECT=PIPPO one: @echo CIAO $(OBJECT)!
make CIAO PIPPO!Possiamo ridefinire il valore della macro OBJECT direttamente sulla riga di comando, senza alterare il Makefile!
make OBJECT=pippa CIAO pippa!Il Makefile puo' accedere alle variabili ambiente:
# Usiamo una variabile ambiente OBJECT=$(TERM) one: @echo CIAO $(OBJECT)!
make CIAO xterm!
Supponiamo di voler compilare il seguente codice C++ composto da tre moduli (main.cpp, myfunc.cpp e myfunc.h) usando il comando make.
// main.cpp#include<iostream> #include"myfunc.h" int main() { int a=6; int b=3; cout<<"a="<<a<<", b="<<b<<endl; cout<<"a/b="<<div(a,b)<<endl; cout<<"a*b="<<mul(a,b)<<endl; cout<<"a^b="<<pot(a,b)<<endl; return 0; }
// myfunc.cpp#include<math.h> int div(int a, int b) { return a/b; }; int mul(int a, int b) { return a*b; }; float pot(float a, float b) { return pow(a,b); }
// myfunc.h int div(int a, int b); int mul(int a, int b); float pot(float a, float b);Un semplice Makefile si presenta cosi':
OBJECTS=main.o myfunc.oCFLAGS=-g -Wall LIBS=-lm CC=g++PROGRAM_NAME=prova $(PROGRAM_NAME):$(OBJECTS) $(CC) $(CFLAGS) -o $(PROGRAM_NAME) $(OBJECTS) $(LIBS) @echo " " @echo "Compilazione completata!" @echo " "Il make ricompilera' il target prova se i files da cui questo dipende (gli OBJECTS main.o e myfunc.o) sono stati modificati dopo che prova e' stato modificato l'ultima volta oppure non esistono. Il processo di ricompilazione avverra' secondo la regola descritta nell' azione del target e usando le Macro definite dall' utente (CC, CFLAGS, LIBS).
Per compilare usiamo semplicemente
makeg++ -c -o main.o main.cpp g++ -c -o myfunc.o myfunc.cpp g++ -g -Wall -o prova main.o myfunc.o -lm Compilazione completata!Se modifichiamo solo un modulo, per esempio myfunc.cpp, il make effettuera' la compilazione di questo file solamente.
make g++ -c -o myfunc.o myfunc.cpp g++ -g -Wall -o prova main.o myfunc.o -lm Compilazione completata! |
Esistono alcuni target standard usati da programmatori Linux e GNU. Fra questi:
- install, viene utilizzato per installare i file di un progetto e puo' comprendere la creazione di nuove directory e la assegnazione di diritti di accesso ai file.
- clean, viene utilizzato per rimuovere dal sistema i file oggetto (*.o), i file core, e altri file tempornei creati in fase di compilazione
- all, di solito utilizzato per richiamare altri target con lo scopo di costruire l'intero progetto.
Aggiungiamo il target clean al nostro Makefile:
OBJECTS=main.o myfunc.o CC=g++ CFLAGS=-g -Wall LIBS=-lm PROGRAM_NAME=prova $(PROGRAM_NAME):$(OBJECTS) $(CC) $(CFLAGS) -o $(PROGRAM_NAME) $(OBJECTS) $(LIBS) @echo " " @echo "Compilazione completata!" @echo " " clean: rm -f *.o rm -f coreInvocare il target clean comporta la cancellazione di tutti i file oggetto e del file core.
make cleanrm -f *.o rm -f core