Makefile, dipendenze dell'intestazione


97

Diciamo che ho un makefile con la regola

%.o: %.c
 gcc -Wall -Iinclude ...

Voglio che * .o venga ricostruito ogni volta che cambia un file di intestazione. Piuttosto che elaborare un elenco di dipendenze, ogni volta che viene modificato un file di intestazione /include, tutti gli oggetti nella directory devono essere ricostruiti.

Non riesco a pensare a un modo carino per cambiare la regola per adattarlo, sono aperto a suggerimenti. Punti bonus se l'elenco delle intestazioni non deve essere codificato


Dopo aver scritto la mia risposta di seguito, ho cercato nell'elenco correlato e ho trovato: stackoverflow.com/questions/297514/… che sembra essere un duplicato. La risposta di Chris Dodd è equivalente alla mia, sebbene utilizzi una convenzione di denominazione diversa.
dmckee --- gattino ex moderatore

Risposte:


116

Se stai usando un compilatore GNU, il compilatore può assemblare un elenco di dipendenze per te. Frammento di makefile:

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

o

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ > ./.depend;

include .depend

dove SRCSè una variabile che punta all'intero elenco di file di origine.

C'è anche lo strumento makedepend, ma non mi è mai piaciuto così tantogcc -MM


2
Mi piace questo trucco, ma come posso dependeseguire solo quando i file sorgente sono cambiati? Sembra funzionare ogni volta a prescindere ...
insegui il

2
@chase: Beh, ho erroneamente fatto la dipendenza dai file oggetto, quando ovviamente dovrebbe essere sui sorgenti e anche l'ordine di dipendenza era sbagliato per i due obiettivi. Questo è quello che ottengo digitando dalla memoria. Provalo ora.
dmckee --- gattino ex moderatore

4
È un modo per aggiungere prima di ogni file un prefisso per mostrare che si trova in un'altra directory, ad esempio build/file.o?
RiaD

Ho cambiato SRCS in OBJECTS, dove OBJECTS è un elenco dei miei file * .o. Ciò sembrava impedire a depend di essere eseguito ogni volta e ha anche rilevato modifiche solo ai file di intestazione. Questo sembra contrario ai commenti precedenti .. mi manca qualcosa?
BigBrownBear00

2
Perché è necessario il punto e virgola? se lo provo senza di esso, o con -MF ./.depend non è l'ultimo argomento, salva solo le dipendenze dell'ultimo file in $ (SRCS).
humodz

72

La maggior parte delle risposte sono sorprendentemente complicate o errate. Tuttavia esempi semplici e robusti sono stati pubblicati altrove [ codereview ]. Certamente le opzioni fornite dal preprocessore gnu sono un po 'confuse. Tuttavia, la rimozione di tutte le directory dalla destinazione di compilazione con -MMè documentata e non è un bug [ gpp ]:

Per impostazione predefinita, CPP prende il nome del file di input principale, elimina tutti i componenti della directory e qualsiasi suffisso di file come ".c" e aggiunge il solito suffisso dell'oggetto della piattaforma.

L' -MMDopzione (un po 'più recente) è probabilmente quello che vuoi. Per completezza un esempio di makefile che supporta più directory src e directory build con alcuni commenti. Per una versione semplice senza directory di compilazione vedere [ codereview ].

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

Questo metodo funziona perché se ci sono più linee di dipendenza per un singolo target, le dipendenze vengono semplicemente unite, ad esempio:

a.o: a.h
a.o: a.c
    ./cmd

è equivalente a:

a.o: a.c a.h
    ./cmd

come menzionato in: Makefile più linee di dipendenza per un singolo obiettivo?


1
Mi piace questa soluzione. Non voglio digitare il comando make depend. Utile !!
Robert

1
C'è un errore di ortografia nel valore della variabile OBJ: CPPdovrebbe leggereCPPS
ctrucza

1
Questa è la mia risposta preferita; +1 per te. Questo è l'unico in questa pagina che ha senso e copre (per quello che posso vedere) tutte le situazioni in cui è necessaria la ricompilazione (evitando compilazioni non necessarie, ma sufficienti)
Joost

1
Per impostazione predefinita, questo non è riuscito a individuare le intestazioni per me anche se hpp e cpp sono entrambi nella stessa directory.
villasv

1
se hai i tuoi file sorgente ( a.cpp, b.cpp) in ./src/, non sarebbe quella sostituzione $(OBJ)=./build/src/a.o ./build/src/b.o?
galois

26

Come ho postato qui, gcc può creare dipendenze e compilare allo stesso tempo:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

Il parametro "-MF" specifica un file in cui memorizzare le dipendenze.

Il trattino all'inizio di "-include" dice a Make di continuare quando il file .d non esiste (es. Alla prima compilazione).

Nota che sembra esserci un bug in gcc riguardo all'opzione -o. Se si imposta il nome del file dell'oggetto in modo che dica obj / _file__c.o, il file .d generato conterrà ancora il file .o, non obj / _file__c.o.


4
Quando provo questo, tutti i miei file .o vengono creati come file vuoti. Ho i miei oggetti in una sottocartella build (quindi $ OBJECTS contiene build / main.o build / smbus.o build / etc ...) e questo certamente crea i file .d come hai descritto con l'apparente bug, ma certamente non sta costruendo affatto i file .o, mentre lo fa se rimuovo -MM e -MF.
bobpaul

1
L'utilizzo di -MT risolverà la nota nelle ultime righe della risposta che aggiorna l'obiettivo di ogni elenco di dipendenze.
Godric Seer

3
@bobpaul perché man gccdice -MMimplica -E, che "si interrompe dopo la pre-elaborazione". È necessario -MMDinvece: stackoverflow.com/a/30142139/895245
Ciro Santilli郝海东冠状病六四事件法轮功

23

Che ne dici di qualcosa come:

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

Puoi anche usare i caratteri jolly direttamente, ma tendo a scoprire che ne ho bisogno in più di un posto.

Si noti che questo funziona bene solo su piccoli progetti, poiché si presume che ogni file oggetto dipenda da ogni file di intestazione.


grazie, stavo pensando che fosse molto più complicato del necessario
Mike

15
Funziona, tuttavia, il problema è che ogni file oggetto viene ricompilato, ogni volta che viene apportata una piccola modifica, ovvero, se si hanno 100 file sorgente / intestazione e si apporta una piccola modifica a uno solo, tutti e 100 vengono ricompilati .
Nicholas Hamilton

1
Dovresti davvero aggiornare la tua risposta per dire che questo è un modo molto inefficiente per farlo perché ricostruisce TUTTI i file ogni volta che QUALSIASI file di intestazione viene modificato. Le altre risposte sono molto migliori.
xaxxon

2
Questa è una pessima soluzione. Sicuramente funzionerà su un piccolo progetto, ma per team e build di qualsiasi dimensione di produzione, questo porterà a tempi di compilazione terribili e diventerà l'equivalente della corsa make clean allogni volta.
Julien Guertault

Nel mio test, questo non funziona affatto. La gccriga non viene eseguita affatto, ma %o: %.cviene invece eseguita la regola incorporata ( regola).
Penghe Geng

4

La soluzione di Martin sopra funziona alla grande, ma non gestisce i file .o che risiedono nelle sottodirectory. Godric sottolinea che il flag -MT si prende cura di quel problema, ma allo stesso tempo impedisce che il file .o venga scritto correttamente. Quanto segue si prenderà cura di entrambi questi problemi:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<

3

Questo farà bene il lavoro e gestirà anche le sottodirectory specificate:

    $(CC) $(CFLAGS) -MD -o $@ $<

testato con gcc 4.8.3


3

Ecco una doppia riga:

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

Funziona con la ricetta predefinita di make, purché tu abbia un elenco di tutti i tuoi file oggetto in formato OBJS.


1

Preferisco questa soluzione, rispetto alla risposta accettata da Michael Williamson, cattura le modifiche a fonti + file inline, quindi fonti + intestazioni e infine solo fonti. Il vantaggio qui è che l'intera libreria non viene ricompilata se vengono apportate solo poche modifiche. Non è una grande considerazione per un progetto con un paio di file, ma se hai 10 o 100 sorgenti, noterai la differenza.

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)

2
Funziona solo se non hai nulla nei tuoi file di intestazione che richiederebbe la ricompilazione di qualsiasi file cpp diverso dal file di implementazione corrispondente.
matec

0

Il seguente funziona per me:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<

0

Una versione leggermente modificata della risposta di Sophie che consente di esportare i file * .d in una cartella diversa (incollerò solo la parte interessante che genera i file di dipendenza):

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

Notare che il parametro

-MT $@

viene utilizzato per garantire che le destinazioni (ovvero i nomi dei file oggetto) nei file * .d generati contengano il percorso completo dei file * .o e non solo il nome del file.

Non so perché questo parametro NON sia necessario quando si utilizza -MMD in combinazione con -c (come nella versione di Sophie ). In questa combinazione sembra scrivere il percorso completo dei file * .o nei file * .d. Senza questa combinazione, -MMD scrive anche solo i nomi di file puri senza componenti di directory nei file * .d. Forse qualcuno sa perché -MMD scrive il percorso completo quando combinato con -c. Non ho trovato alcun suggerimento nella pagina man di g ++.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.