Como posso fazer com que um Makefile reconstrua automaticamente os arquivos de origem que incluem um arquivo de cabeçalho modificado? (Em C / C ++)

91

Eu tenho o seguinte makefile que uso para construir um programa (um kernel, na verdade) no qual estou trabalhando. É do zero e estou aprendendo sobre o processo, então não é perfeito, mas acho que é poderoso o suficiente neste ponto para o meu nível de experiência escrevendo makefiles.

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

Meu principal problema com este makefile é que quando eu modifico um arquivo de cabeçalho que um ou mais arquivos C incluem, os arquivos C não são reconstruídos. Posso consertar isso facilmente fazendo com que todos os meus arquivos de cabeçalho sejam dependências de todos os meus arquivos C, mas isso efetivamente causaria uma reconstrução completa do projeto sempre que eu alterasse / adicionasse um arquivo de cabeçalho, o que não seria muito elegante.

O que eu quero é que apenas os arquivos C que incluem o arquivo de cabeçalho que alterei sejam reconstruídos e que todo o projeto seja vinculado novamente. Posso fazer a vinculação fazendo com que todos os arquivos de cabeçalho sejam dependências do destino, mas não consigo descobrir como fazer com que os arquivos C sejam invalidados quando seus arquivos de cabeçalho incluídos são mais recentes.

Ouvi dizer que o GCC tem alguns comandos para tornar isso possível (para que o makefile possa de alguma forma descobrir quais arquivos precisam ser reconstruídos), mas não consigo encontrar um exemplo de implementação real para examinar. Alguém pode postar uma solução que permitirá esse comportamento em um makefile?

EDIT: Devo esclarecer, estou familiarizado com o conceito de colocar os destinos individuais e ter cada destino.o exigir os arquivos de cabeçalho. Isso requer que eu edite o makefile toda vez que incluo um arquivo de cabeçalho em algum lugar, o que é um pouco chato. Estou procurando uma solução que possa derivar as dependências do arquivo de cabeçalho por conta própria, o que tenho quase certeza de ter visto em outros projetos.

Nicholas Flynt
fonte

Respostas:

30

Conforme já apontado em outro lugar neste site, consulte esta página: Geração de Autodependência

Resumindo, o gcc pode criar automaticamente arquivos de dependência .d para você, que são fragmentos de mini makefile contendo as dependências do arquivo .c que você compilou. Cada vez que você alterar o arquivo .c e compilá-lo, o arquivo .d será atualizado.

Além de adicionar o sinalizador -M ao gcc, você precisará incluir os arquivos .d no makefile (como Chris escreveu acima). Existem alguns problemas mais complicados na página que são resolvidos usando sed, mas você pode ignorá-los e fazer um "make clean" para limpar os arquivos .d sempre que o make reclamar de não ser capaz de construir um arquivo de cabeçalho que não existe mais .

Udi Meiri
fonte
2
Posso estar enganado, mas acho que o GCC realmente adicionou um recurso para tentar contornar o problema do sed. Verifique gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html especificamente -MP.
Eugene Marcotte,
Sim, -MP existe desde GCC 3, existe em clang e icc e anula a necessidade de sed. bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…
hmijail lamenta resignados
A versão mais recente do link na resposta contém exemplos de uso de sinalizadores GCC.
MadScientist
20

Você poderia adicionar um comando 'make depend' como outros declararam, mas por que não fazer o gcc criar dependências e compilar ao mesmo tempo:

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

-include $(DEPS)

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

O parâmetro '-MF' especifica um arquivo para armazenar as dependências.

O traço no início de '-include' diz ao Make para continuar quando o arquivo .d não existir (por exemplo, na primeira compilação).

Observe que parece haver um bug no gcc em relação à opção -o. Se você definir o nome do arquivo do objeto para dizer obj/_file__c.o, o gerado _file_.dainda conterá _file_.o, não obj/_file_c.o.

Martin Fido
fonte
18
Isto não funcionou para mim. Por exemplo, o makefile gerou e executou este: g++ -c -Wall -Werror -MM -MF main.d -o main.o main.cpp Peguei o arquivo main.d, mas main.o tinha 0 bytes. No entanto, o sinalizador -MMD parece fazer exatamente o que era necessário. Então, minha regra de trabalho makefile tornou-se:$(CC) -c $(CFLAGS) -MMD -o $@ $<
Darren Cook
17

Isso é equivalente à resposta de Chris Dodd , mas usa uma convenção de nomenclatura diferente (e coincidentemente não requer a sedmágica. Copiado de uma duplicata posterior .


Se você estiver usando um compilador GNU, o compilador pode montar uma lista de dependências para você. Fragmento de Makefile:

depend: .depend

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

include .depend

Existe também a ferramenta makedepend, mas nunca gostei tanto quantogcc -MM

dmckee --- gatinho ex-moderador
fonte
4
Por que você não soletra FONTES? Não requer muito mais caracteres e não é tão ofuscado quanto "SRCS", que pode parecer um acrônimo.
HelloGoodbye
@HelloGoodbye Um pouco de estilo. Sempre usei e sempre vi SRCSe OBJS. Eu concordaria na maioria das vezes, mas todos deveriam saber o que são.
Sherrellbc
@sherrellbc O que as pessoas "deveriam" saber e o que as pessoas realmente sabem são geralmente duas coisas diferentes: P
HelloGoodbye
7

Você terá que criar destinos individuais para cada arquivo C e, em seguida, listar o arquivo de cabeçalho como uma dependência. Você ainda pode usar seus destinos genéricos e apenas colocar as .hdependências depois, como:

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

foo.c: bar.h
# And so on...
mipadi
fonte
4

Basicamente, você precisa criar dinamicamente as regras do makefile para reconstruir os arquivos-objeto quando os arquivos de cabeçalho forem alterados. Se você usar gcc e gnumake, isso é bastante fácil; basta colocar algo como:

$(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

em seu makefile.

Chris Dodd
fonte
2
Eu meio que entendo isso, exceto que (além dos sinalizadores -MM e -MG serem novos), eu não entendo para que serve a linha de aparência regex de texto criptografado. Isso não vai me deixar feliz, companheiros de equipe ... ^ _ ^ Vou tentar e ver se obtenho algum resultado.
Nicholas Flynt
sed é a abreviação de "editor de fluxo", que pode modificar um fluxo de texto sem a necessidade de usar um arquivo. É uma ferramenta Unix padrão e menor e mais rápida, por isso é usada com mais frequência do que awk ou perl.
Zan Lynx
Ah, aí está o problema: estou fazendo isso no Windows.
Nicholas Flynt de
3

Além do que @mipadi disse, você também pode explorar o uso da -Mopção ' ' para gerar um registro das dependências. Você pode até mesmo gerá-los em um arquivo separado (talvez 'depend.mk') que você então inclui no makefile. Ou você pode encontrar uma make dependregra ' ' que edita o makefile com as dependências corretas (termos do Google: "não remova esta linha" e depende).

Jonathan Leffler
fonte
0

Nenhuma das respostas funcionou para mim. Por exemplo, a resposta de Martin Fido sugere que o gcc pode criar um arquivo de dependência, mas quando tentei, ele estava gerando arquivos de objeto vazios (zero bytes) para mim sem quaisquer avisos ou erros. Pode ser um bug do gcc. Eu estou em

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

Então aqui está meu Makefile completo que funciona para mim; é uma combinação de soluções + algo que não foi mencionado por ninguém (por exemplo, "regra de substituição de sufixo" especificada como .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

Observe que usei .cc .. O Makefile acima é fácil de ajustar para arquivos .c.

Também é importante observar a importância dessas duas linhas:

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

então gcc é chamado uma vez para construir um arquivo de dependência primeiro, e então realmente compila um arquivo .cc. E assim por diante para cada arquivo de origem.

Tagar
fonte
0

Solução mais simples: basta usar o Makefile para que a regra de compilação .c para .o seja dependente do (s) arquivo (s) de cabeçalho e de tudo o mais que seja relevante em seu projeto como uma dependência.

Por exemplo, no Makefile em algum lugar:

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 $@ $<
Richard Elkins
fonte
-1

Eu acredito que o mkdepcomando é o que você quer. Na verdade, ele verifica os arquivos .c em busca de #includelinhas e cria uma árvore de dependências para eles. Eu acredito que os projetos do Automake / Autoconf usam isso por padrão.

jdizzle
fonte