Il Comando 'make' in ambiente Linux


Perche' utilizzare il comando make?

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.
Quello che segue e' un semplice Makefile in cui sono stati definiti tre target e tre azioni corrispondenti:


# 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>!
Il seguente Makefile non e' valido perche' la riga seguente la definizione del target non inizia con un  separatore <TAB>:


# Un esempio di Makefile mal scritto

one:
  @echo UNO!
 
make one
Makefile: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!


Macro e variabili ambiente

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!


Compiliamo con make (finalmente)

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.o
CFLAGS=-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
 

make
g++    -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!


Alcuni target standard
Esistono alcuni target standard usati da programmatori Linux e GNU. Fra questi:

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 core

Invocare il target clean comporta la cancellazione di tutti i file oggetto e del file core. 
 

make clean
rm -f *.o
rm -f core

 


 

Torna all'Indice