MAKE

Breve introduzione all'utility make
Autore: Matteo Lucarelli
ultima versione su: matteolucarelli.altervista.org
Documento in costruzione

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.

Utilità di Make

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).

Utilizzo elementare

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:

Caratteri speciali

Regole

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).
Phony Target: il target può essere una etichetta (install, clean, ecc) nel qual caso la lista delle dipendenze è vuota. I comandi sono espressi nella forma accettata dalla shell. E' evidente che non essendoci nessuna dipendenza da soddisfare il phony target è un in pratica uno shortcut ad un comando.

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.

Un makefile lineare

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.o
Spiegazione:

Variabili

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
...

Regole Implicite

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.

Un makefile con variabili e regole implicite

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.c
Questo makefile funziona affidandosi interamente alle regole implicite.

Un paio di makefile più completi

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

Nota finale

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).

matteolucarelli.altervista.org
©opyright info