Se desideri eseguire o aggiornare un'attività quando vengono aggiornati determinati file, make
l'utilità può tornare utile. Il make
l'utilità richiede un file, Makefile
(o makefile
), che definisce l'insieme delle attività da eseguire. Potresti aver usato make
per compilare un programma dal codice sorgente. La maggior parte dei progetti open source utilizza make
per compilare un binario eseguibile finale, che può quindi essere installato utilizzando make install
.
In questo articolo esploreremo make
e Makefile
utilizzando esempi di base e avanzati. Prima di iniziare, assicurati che make
è installato nel tuo sistema.
Esempi di base
Iniziamo stampando sul terminale il classico "Hello World". Crea una directory vuota myproject
contenente un file Makefile
con questo contenuto:
say_hello:
echo "Hello World"
Ora esegui il file digitando make
all'interno della directory myproject
. L'output sarà:
$ make
echo "Hello World"
Hello World
Nell'esempio sopra, say_hello
si comporta come un nome di funzione, come in qualsiasi linguaggio di programmazione. Questo è chiamato obiettivo . I prerequisiti o dipendenze seguire l'obiettivo. Per semplicità, in questo esempio non abbiamo definito alcun prerequisito. Il comando echo "Hello World"
è chiamata la ricetta . La ricetta utilizza prerequisiti per creare un bersaglio . L'obiettivo, i prerequisiti e le ricette insieme costituiscono una regola .
Per riassumere, di seguito è riportata la sintassi di una regola tipica:
target: prerequisites
<TAB> recipe
Ad esempio, una destinazione potrebbe essere un file binario che dipende dai prerequisiti (file di origine). D'altra parte, un prerequisito può anche essere una destinazione che dipende da altre dipendenze:
final_target: sub_target final_target.c
Recipe_to_create_final_target
sub_target: sub_target.c
Recipe_to_create_sub_target
Non è necessario che la destinazione sia un file; potrebbe essere solo un nome per la ricetta, come nel nostro esempio. Li chiamiamo "bersagli falsi".
Tornando all'esempio sopra, quando make
è stato eseguito, l'intero comando echo "Hello World"
è stato visualizzato, seguito dall'output del comando effettivo. Spesso non lo vogliamo. Per sopprimere l'eco del comando vero e proprio, dobbiamo avviare echo
con @
:
say_hello:
@echo "Hello World"
Ora prova a eseguire make
ancora. L'output dovrebbe visualizzare solo questo:
$ make
Hello World
Aggiungiamo altri target fasulli:generate
e clean
al Makefile
:
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt
Se proviamo a eseguire make
dopo le modifiche, solo il target say_hello
sarà eseguito. Questo perché solo il primo target nel makefile è il target predefinito. Spesso chiamato obiettivo predefinito , questo è il motivo per cui vedrai all
come primo obiettivo nella maggior parte dei progetti. È responsabilità di all
chiamare altri bersagli. Possiamo ignorare questo comportamento utilizzando uno speciale target fasullo chiamato .DEFAULT_GOAL
.
Includiamolo all'inizio del nostro makefile:
.DEFAULT_GOAL := generate
Questo eseguirà la destinazione generate
come predefinito:
$ make
Creating empty text files...
touch file-{1..10}.txt
Come suggerisce il nome, il target fasullo .DEFAULT_GOAL
può eseguire solo un target alla volta. Questo è il motivo per cui la maggior parte dei makefile include all
come target che può richiamare tutti i target necessari.
Includiamo il target fasullo all
e rimuovi .DEFAULT_GOAL
:
all: say_hello generate
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt
Prima di eseguire make
, includiamo un altro target fasullo speciale, .PHONY
, dove definiamo tutti i target che non sono file. make
eseguirà la sua ricetta indipendentemente dal fatto che esista un file con quel nome o quale sia l'ora dell'ultima modifica. Ecco il makefile completo:
.PHONY: all say_hello generate clean
all: say_hello generate
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt
Il make
dovrebbe chiamare say_hello
e generate
:
$ make
Hello World
Creating empty text files...
touch file-{1..10}.txt
È buona norma non chiamare clean
in all
o mettilo come primo obiettivo. clean
dovrebbe essere chiamato manualmente quando la pulizia è necessaria come primo argomento per make
:
$ make clean
Cleaning up...
rm *.txt
Ora che hai un'idea di come funziona un makefile di base e di come scrivere un makefile semplice, diamo un'occhiata ad alcuni esempi più avanzati.
Esempi avanzati
Variabili
Più risorse Linux
- Comandi Linux cheat sheet
- Cheat sheet sui comandi avanzati di Linux
- Corso online gratuito:Panoramica tecnica RHEL
- Cheat sheet della rete Linux
- Cheat sheet di SELinux
- Cheat sheet dei comandi comuni di Linux
- Cosa sono i container Linux?
- I nostri ultimi articoli su Linux
Nell'esempio precedente, la maggior parte dei valori target e prerequisiti sono codificati, ma nei progetti reali questi vengono sostituiti con variabili e modelli.
Il modo più semplice per definire una variabile in un makefile è usare =
operatore. Ad esempio, per assegnare il comando gcc
a una variabile CC
:
CC = gcc
Questa è anche chiamata variabile espansa ricorsiva , e viene utilizzato in una regola come mostrato di seguito:
hello: hello.c
${CC} hello.c -o hello
Come avrai intuito, la ricetta si espande come di seguito quando viene passata al terminale:
gcc hello.c -o hello
Entrambi ${CC}
e $(CC)
sono riferimenti validi per chiamare gcc
. Ma se si tenta di riassegnare una variabile a se stessa, si verificherà un ciclo infinito. Verifichiamo questo:
CC = gcc
CC = ${CC}
all:
@echo ${CC}
Esecuzione di make
risulterà in:
$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
Per evitare questo scenario, possiamo usare il :=
operatore (questo è anche chiamato la variabile semplicemente espansa ). Non dovremmo avere problemi a eseguire il makefile di seguito:
CC := gcc
CC := ${CC}
all:
@echo ${CC}
Modelli e funzioni
Il makefile seguente può compilare tutti i programmi C utilizzando variabili, modelli e funzioni. Esploriamolo riga per riga:
# Usage:
# make # compile all binary
# make clean # remove ALL binaries and objects
.PHONY = all clean
CC = gcc # compiler to use
LINKERFLAG = -lm
SRCS := $(wildcard *.c)
BINS := $(SRCS:%.c=%)
all: ${BINS}
%: %.o
@echo "Checking.."
${CC} ${LINKERFLAG} $< -o $@
%.o: %.c
@echo "Creating object.."
${CC} -c $<
clean:
@echo "Cleaning up..."
rm -rvf *.o ${BINS}
-
Righe che iniziano con
#
sono commenti. -
Riga
.PHONY = all clean
definisce gli obiettivi falsiall
eclean
. -
Variabile
LINKERFLAG
definisce i flag da usare congcc
in una ricetta. -
SRCS := $(wildcard *.c)
:$(wildcard pattern)
è una delle funzioni per i nomi di file . In questo caso, tutti i file con.c
l'estensione verrà memorizzata in una variabileSRCS
. -
BINS := $(SRCS:%.c=%)
:Questo è chiamato come riferimento di sostituzione . In questo caso, seSRCS
ha valori'foo.c bar.c'
,BINS
avrà'foo bar'
. -
Riga
all: ${BINS}
:Il bersaglio fasulloall
chiama i valori in${BINS}
come obiettivi individuali. -
Regola:
%: %.o
@echo "Checking.."
${CC} ${LINKERFLAG} $< -o $@Diamo un'occhiata a un esempio per capire questa regola. Supponiamo
foo
è uno dei valori in${BINS}
. Quindi%
corrisponderà afoo
(%
può corrispondere a qualsiasi nome di destinazione). Di seguito è riportata la regola nella sua forma estesa:foo: foo.o
@echo "Checking.."
gcc -lm foo.o -o fooCome mostrato,
%
è sostituito dafoo
.$<
è sostituito dafoo.o
.$<
è modellato per soddisfare i prerequisiti e$@
corrisponde all'obiettivo. Questa regola verrà chiamata per ogni valore in${BINS}
-
Regola:
%.o: %.c
@echo "Creating object.."
${CC} -c $<Ogni prerequisito nella regola precedente è considerato un obiettivo per questa regola. Di seguito è riportata la regola nella sua forma estesa:
foo.o: foo.c
@echo "Creating object.."
gcc -c foo.c -
Infine, rimuoviamo tutti i binari e i file oggetto nella destinazione
clean
.
Di seguito è riportata la riscrittura del makefile sopra, supponendo che sia posizionato nella directory con un unico file foo.c:
# Usage:
# make # compile all binary
# make clean # remove ALL binaries and objects
.PHONY = all clean
CC = gcc # compiler to use
LINKERFLAG = -lm
SRCS := foo.c
BINS := foo
all: foo
foo: foo.o
@echo "Checking.."
gcc -lm foo.o -o foo
foo.o: foo.c
@echo "Creating object.."
gcc -c foo.c
clean:
@echo "Cleaning up..."
rm -rvf foo.o foo
Per ulteriori informazioni sui makefile, fare riferimento al manuale GNU Make, che offre un riferimento completo ed esempi.
Puoi anche leggere la nostra Introduzione a GNU Autotools per imparare come automatizzare la generazione di un makefile per il tuo progetto di programmazione.