Come posso fare in modo che un Makefile ricostruisca automaticamente i file sorgente che includono un file di intestazione modificato? (In C / C ++)


92

Ho il seguente makefile che uso per creare un programma (un kernel, in realtà) su cui sto lavorando. È da zero e sto imparando il processo, quindi non è perfetto, ma penso che sia abbastanza potente a questo punto per il mio livello di esperienza nella scrittura di makefile.

AS  =   nasm
CC  =   gcc
LD  =   ld

TARGET      =   core
BUILD       =   build
SOURCES     =   source
INCLUDE     =   include
ASM         =   assembly

VPATH = $(SOURCES)

CFLAGS  =   -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions \
            -nostdinc -fno-builtin -I $(INCLUDE)
ASFLAGS =   -f elf

#CFILES     =   core.c consoleio.c system.c
CFILES      =   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
SFILES      =   assembly/start.asm

SOBJS   =   $(SFILES:.asm=.o)
COBJS   =   $(CFILES:.c=.o)
OBJS    =   $(SOBJS) $(COBJS)

build : $(TARGET).img

$(TARGET).img : $(TARGET).elf
    c:/python26/python.exe concat.py stage1 stage2 pad.bin core.elf floppy.img

$(TARGET).elf : $(OBJS)
    $(LD) -T link.ld -o $@ $^

$(SOBJS) : $(SFILES)
    $(AS) $(ASFLAGS) $< -o $@

%.o: %.c
    @echo Compiling $<...
    $(CC) $(CFLAGS) -c -o $@ $<

#Clean Script - Should clear out all .o files everywhere and all that.
clean:
    -del *.img
    -del *.o
    -del assembly\*.o
    -del core.elf

Il mio problema principale con questo makefile è che quando modifico un file di intestazione che include uno o più file C, i file C non vengono ricostruiti. Posso risolvere questo problema abbastanza facilmente facendo in modo che tutti i miei file di intestazione siano dipendenze per tutti i miei file C, ma ciò causerebbe effettivamente una ricostruzione completa del progetto ogni volta che ho modificato / aggiunto un file di intestazione, il che non sarebbe molto grazioso.

Quello che voglio è che vengano ricostruiti solo i file C che includono il file di intestazione che cambio e che l'intero progetto sia nuovamente collegato. Posso eseguire il collegamento facendo in modo che tutti i file di intestazione siano dipendenze della destinazione, ma non riesco a capire come rendere i file C invalidati quando i loro file di intestazione inclusi sono più recenti.

Ho sentito che GCC ha alcuni comandi per renderlo possibile (quindi il makefile può in qualche modo capire quali file devono essere ricostruiti) ma non riesco a trovare un vero esempio di implementazione da guardare. Qualcuno può pubblicare una soluzione che abiliterà questo comportamento in un makefile?

EDIT: dovrei chiarire, ho familiarità con il concetto di inserire i singoli target e avere ogni target.o richiedono i file di intestazione. Ciò richiede che modifichi il makefile ogni volta che includo un file di intestazione da qualche parte, il che è un po 'fastidioso. Sto cercando una soluzione che possa derivare da sola le dipendenze del file di intestazione, cosa che sono abbastanza certo di aver visto in altri progetti.

Risposte:


30

Come già sottolineato altrove in questo sito, vedere questa pagina: Generazione di dipendenza automatica

In breve, gcc può creare automaticamente file di dipendenze .d, che sono piccoli frammenti di makefile contenenti le dipendenze del file .c che hai compilato. Ogni volta che si modifica il file .c e lo si compila, il file .d verrà aggiornato.

Oltre ad aggiungere il flag -M a gcc, dovrai includere i file .d nel makefile (come Chris ha scritto sopra). Ci sono alcuni problemi più complicati nella pagina che vengono risolti usando sed, ma puoi ignorarli e fare una "pulizia" per cancellare i file .d ogni volta che ti lamenti di non essere in grado di costruire un file di intestazione che non esiste più .


2
Potrei sbagliarmi, ma penso che GCC abbia effettivamente aggiunto una funzionalità per cercare di aggirare il problema di sed. Controlla gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html in particolare -MP.
Eugene Marcotte

Sì, -MP esiste da GCC 3, esiste in clang e icc e annulla la necessità di sed. bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…
hmijail piange le dimissioni il

L'ultima versione del collegamento nella risposta contiene esempi che utilizzano i flag di GCC.
MadScientist

20

Potresti aggiungere un comando 'make depend' come altri hanno affermato, ma perché non ottenere gcc per creare dipendenze e compilare allo stesso tempo:

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

-include $(DEPS)

%.o: %.c
    $(CC) -c $(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 imposti il ​​nome del file dell'oggetto in modo che dica, obj/_file__c.oil file generato _file_.dconterrà comunque _file_.o, no obj/_file_c.o.


18
Questo non ha funzionato per me. Ad esempio, il makefile generato ed eseguito questo: g++ -c -Wall -Werror -MM -MF main.d -o main.o main.cpp ho ottenuto il file main.d, ma main.o era di 0 byte. Tuttavia il flag -MMD sembra fare esattamente ciò che era richiesto. Quindi la mia regola del makefile funzionante è diventata:$(CC) -c $(CFLAGS) -MMD -o $@ $<
Darren Cook

17

Questo è equivalente alla risposta di Chris Dodd , ma usa una diversa convenzione di denominazione (e guarda caso non richiede la sedmagia. Copiato da un duplicato successivo .


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

depend: .depend

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

include .depend

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


4
Perché non scrivi FONTI? Non richiede in particolare molti più caratteri e non è offuscato come "SRCS", che può sembrare un acronimo.
Hello

@ HelloGoodbye Un po 'di stile. Ho sempre usato, e sempre visto, SRCSe OBJS. Sono d'accordo per la maggior parte del tempo, ma tutti dovrebbero sapere cosa sono.
sherrellbc

@sherrellbc Ciò che le persone "dovrebbero" sapere e ciò che le persone sanno effettivamente sono spesso due cose diverse: P
HelloGoodbye

7

Dovrai creare obiettivi individuali per ogni file C, quindi elencare il file di intestazione come dipendenza. Puoi ancora utilizzare i tuoi obiettivi generici e posizionare le .hdipendenze in seguito, in questo modo:

%.o: %.c
        @echo Compiling $<...
        $(CC) $(CFLAGS) -c -o $@ $<

foo.c: bar.h
# And so on...

4

Fondamentalmente, è necessario creare dinamicamente le regole di makefile per ricostruire i file oggetto quando i file di intestazione cambiano. Se usi gcc e gnumake, questo è abbastanza facile; metti qualcosa come:

$(OBJDIR)/%.d: %.c
        $(CC) -MM -MG $(CPPFLAGS) $< | sed -e 's,^\([^:]*\)\.o[ ]*:,$(@D)/\1.o $(@D)/\1.d:,' >$@

ifneq ($(MAKECMDGOALS),clean)
include $(SRCS:%.c=$(OBJDIR)/%.d)
endif

nel tuo makefile.


2
In un certo senso lo capisco, tranne che (a parte che i flag -MM e -MG sono nuovi) non capisco a cosa serva la riga di testo criptico della regex. Questo non mi renderà felice i compagni di squadra ... ^ _ ^ Ci proverò e vedrò se ho dei risultati.
Nicholas Flynt

sed è l'abbreviazione di "stream editor" che può modificare un flusso di testo senza la necessità di utilizzare un file. È uno strumento Unix standard e più piccolo e veloce, quindi viene utilizzato più spesso di awk o perl.
Zan Lynx

Ah, c'è il problema: lo sto facendo sotto Windows.
Nicholas Flynt

3

Oltre a quanto detto da @mipadi, puoi anche esplorare l'uso -Mdell'opzione " " per generare un record delle dipendenze. Potresti anche generarli in un file separato (forse "depend.mk") che poi includerai nel makefile. Oppure puoi trovare una make dependregola " " che modifica il makefile con le dipendenze corrette (termini di Google: "non rimuovere questa riga" e dipendere).


1

Nessuna delle risposte ha funzionato per me. Ad esempio, la risposta di Martin Fido suggerisce che gcc può creare file delle dipendenze, ma quando ho provato a generare file oggetto vuoti (zero byte) per me senza avvisi o errori. Potrebbe essere un bug di gcc. ci sono

$ gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)

Quindi ecco il mio Makefile completo che funziona per me; è una combinazione di soluzioni + qualcosa che non è stato menzionato da nessun altro (ad esempio "regola di sostituzione del suffisso" specificata come .cc.o :):

CC = g++
CFLAGS = -Wall -g -std=c++0x
INCLUDES = -I./includes/

# LFLAGS = -L../lib
# LIBS = -lmylib -lm

# List of all source files
SRCS = main.cc cache.cc

# Object files defined from source files
OBJS = $(SRCS:.cc=.o)

# # define the executable file 
MAIN = cache_test

#List of non-file based targets:
.PHONY: depend clean all

##  .DEFAULT_GOAL := all

# List of dependencies defined from list of object files
DEPS := $(OBJS:.o=.d)

all: $(MAIN)

-include $(DEPS)

$(MAIN): $(OBJS)
    $(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LFLAGS) $(LIBS)

#suffix replacement rule for building .o's from .cc's
#build dependency files first, second line actually compiles into .o
.cc.o:
    $(CC) $(CFLAGS) $(INCLUDES) -c -MM -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

clean:
    $(RM) *.o *~ $(MAIN) *.d

Si noti che ho usato .cc .. Il Makefile sopra è facile da regolare per i file .c.

Notare anche l'importanza di queste due righe:

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

quindi gcc viene chiamato una volta per creare prima un file delle dipendenze, quindi compila effettivamente un file .cc. E così via per ogni file sorgente.


0

Soluzione più semplice: basta usare il Makefile per fare in modo che la regola di compilazione da .c a .o dipenda dai file di intestazione e da qualsiasi altra cosa sia rilevante nel tuo progetto come dipendenza.

Ad esempio, nel Makefile da qualche parte:

DEPENDENCIES=mydefs.h yourdefs.h Makefile GameOfThrones.S07E01.mkv

::: (your other Makefile statements like rules 
:::  for constructing executables or libraries)

# Compile any .c to the corresponding .o file:
%.o: %.c $(DEPENDENCIES)
        $(CC) $(CFLAGS) -c -o $@ $<

-1

Credo che il mkdepcomando sia quello che vuoi. Effettua la scansione dei file .c per le #includelinee e crea un albero delle dipendenze per loro. Credo che i progetti Automake / Autoconf lo usino di default.

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.