Makefile, dependências de cabeçalho

97

Digamos que eu tenha um makefile com a regra

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

Quero que * .o seja reconstruído sempre que um arquivo de cabeçalho for alterado. Em vez de elaborar uma lista de dependências, sempre que qualquer arquivo de cabeçalho for /includealterado, todos os objetos no diretório devem ser reconstruídos.

Não consigo pensar em uma maneira legal de mudar a regra para acomodar isso, estou aberto a sugestões. Pontos de bônus se a lista de cabeçalhos não precisar ser codificada

Mike
fonte
Tendo escrito minha resposta abaixo, olhei na lista relacionada e encontrei: stackoverflow.com/questions/297514/… que parece ser uma duplicata. A resposta de Chris Dodd é equivalente à minha, embora use uma convenção de nomenclatura diferente.
dmckee --- ex-moderador gatinho

Respostas:

116

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

depend: .depend

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

include .depend

ou

depend: .depend

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

include .depend

onde SRCSé uma variável apontando para sua lista inteira de arquivos de origem.

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

dmckee
fonte
2
Gosto desse truque, mas como posso dependexecutá-lo apenas quando os arquivos de origem foram alterados? Parece funcionar sempre, independentemente ...
perseguir
2
@chase: Bem, eu cometi erroneamente a dependência dos arquivos de objeto, quando deveria estar obviamente nas fontes e tinha a ordem de dependência errada para os dois destinos também. Isso é o que ganho por digitar de memória. Tente agora.
dmckee --- ex-moderador gatinho
4
É uma maneira de adicionar antes de cada arquivo algum prefixo para mostrar que ele está em outro diretório, por exemplo build/file.o?
RiaD
Mudei SRCS para OBJECTS, onde OBJECTS é uma lista dos meus arquivos * .o. Isso parecia impedir que o depend rodasse todas as vezes e também detectava alterações apenas nos arquivos de cabeçalho. Isso parece contrário aos comentários anteriores ... estou faltando alguma coisa?
BigBrownBear00
2
Por que o ponto-e-vírgula é necessário? se eu tentar sem ele, ou com -MF ./.depend não sendo o último argumento, ele salva apenas as dependências do último arquivo em $ (SRCS).
humodz
72

A maioria das respostas é surpreendentemente complicada ou errônea. No entanto, exemplos simples e robustos foram postados em outro lugar [ codereview ]. É certo que as opções fornecidas pelo pré-processador GNU são um pouco confusas. No entanto, a remoção de todos os diretórios do destino de construção com -MMestá documentada e não é um bug [ gpp ]:

Por padrão, o CPP recebe o nome do arquivo de entrada principal, exclui quaisquer componentes do diretório e qualquer sufixo de arquivo, como '.c', e anexa o sufixo de objeto usual da plataforma.

A opção (um pouco mais recente) -MMDé provavelmente o que você deseja. Para completar, um exemplo de um makefile que suporta vários diretórios src e diretórios de construção com alguns comentários. Para uma versão simples sem diretórios de compilação, consulte [ 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)

Este método funciona porque se houver várias linhas de dependência para um único destino, as dependências serão simplesmente unidas, por exemplo:

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

é equivalente a:

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

conforme mencionado em: Makefile várias linhas de dependência para um único destino?

Sophie
fonte
1
Eu gosto dessa solução. Não quero digitar o comando make depend. Útil !!
Robert de
1
Há um erro de grafia no valor da variável OBJ: o CPPdeve ser lidoCPPS
ctrucza
1
Esta é minha resposta preferida; 1 para você. Este é o único nesta página que faz sentido e cobre (pelo que posso ver) todas as situações em que a recompilação é necessária (evitando a compilação desnecessária, mas suficiente)
Joost
1
Fora da caixa, isso não conseguiu localizar os cabeçalhos para mim, embora hpp e cpp estejam no mesmo diretório.
villasv
1
se você tiver seus arquivos de origem ( a.cpp, b.cpp) em ./src/, essa substituição não faria $(OBJ)=./build/src/a.o ./build/src/b.o?
galois
26

Como postei aqui, o gcc pode criar dependências e compilar ao mesmo tempo:

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

-include $(DEPS)

%.o: %.c
    $(CC) $(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 como obj / _file__c.o, o arquivo .d gerado ainda conterá o arquivo .o, não obj / _file__c.o.

Martin Fido
fonte
4
Quando tento fazer isso, todos os meus arquivos .o são criados como arquivos vazios. Eu tenho meus objetos em uma subpasta build (então $ OBJECTS contém build / main.o build / smbus.o build / etc ...) e isso certamente cria os arquivos .d como você descreveu com o bug aparente, mas certamente não está construindo os arquivos .o, mas sim se eu remover o -MM e -MF.
bobpaul
1
Usar -MT resolverá a nota nas últimas linhas de sua resposta, que atualiza o destino de cada lista de dependências.
Godric Seer
3
@bobpaul porque man gccdiz -MMimplica -E, que "para após o pré-processamento". Em -MMDvez disso, você precisa : stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
23

Que tal algo como:

includes = $(wildcard include/*.h)

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

Você também pode usar os curingas diretamente, mas tendo a descobrir que preciso deles em mais de um lugar.

Observe que isso só funciona bem em projetos pequenos, uma vez que assume que todo arquivo de objeto depende de todo arquivo de cabeçalho.

Michael Williamson
fonte
obrigado, eu estava tentando parecer muito mais complicado do que o necessário
Mike,
15
Isso funciona, no entanto, o problema com isso é que todo arquivo de objeto é recompilado, toda vez que uma pequena mudança é feita, ou seja, se você tiver 100 arquivos de origem / cabeçalho e fizer uma pequena mudança em apenas um, todos os 100 serão recompilados .
Nicholas Hamilton
1
Você realmente deve atualizar sua resposta para dizer que esta é uma maneira muito ineficiente de fazer isso, porque ela reconstrói TODOS os arquivos toda vez que QUALQUER arquivo de cabeçalho é alterado. As outras respostas são muito melhores.
xaxxon
2
Esta é uma solução muito ruim. Claro que funcionará em um projeto pequeno, mas para qualquer equipe de tamanho de produção e build, isso levará a um tempo de compilação terrível e se tornará o equivalente a executar make clean alltodas as vezes.
Julien Guertault
No meu teste, isso não funciona de jeito nenhum. A gcclinha não é executada, mas a regra interna ( %o: %.cregra) é executada.
Penghe Geng
4

A solução de Martin acima funciona muito bem, mas não lida com arquivos .o que residem em subdiretórios. Godric aponta que o sinalizador -MT cuida desse problema, mas simultaneamente impede que o arquivo .o seja escrito corretamente. O seguinte cuidará de ambos os problemas:

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

-include $(DEPS)

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

Isso fará o trabalho muito bem e até mesmo manipulará subdiretórios sendo especificados:

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

testei com gcc 4.8.3

g24l
fonte
3

Aqui está uma linha dupla:

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

Isso funciona com a receita padrão do make, contanto que você tenha uma lista de todos os seus arquivos de objeto em OBJS.

tbodt
fonte
1

Eu prefiro esta solução, em vez da resposta aceita por Michael Williamson, ela captura as alterações em fontes + arquivos embutidos, em seguida, fontes + cabeçalhos e, finalmente, apenas fontes. A vantagem aqui é que a biblioteca inteira não é recompilada se apenas algumas mudanças forem feitas. Não é uma grande consideração para um projeto com alguns arquivos, mas se você tiver 10 ou 100 fontes, notará a diferença.

COMMAND= gcc -Wall -Iinclude ...

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

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

%.o: %.cpp
    $(COMMAND)
Nicholas Hamilton
fonte
2
Isso funciona apenas se você não tiver nada em seus arquivos de cabeçalho que exija a recompilação de quaisquer arquivos cpp que não sejam o arquivo de implementação correspondente.
matec
0

O seguinte funciona para mim:

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

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<
Marcel Keller
fonte
0

Uma versão ligeiramente modificada da resposta de Sophie que permite enviar os arquivos * .d para uma pasta diferente (vou colar apenas a parte interessante que gera os arquivos de dependência):

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

Observe que o parâmetro

-MT $@

é usado para garantir que os destinos (ou seja, os nomes dos arquivos de objeto) nos arquivos * .d gerados contenham o caminho completo para os arquivos * .o e não apenas o nome do arquivo.

Não sei por que este parâmetro NÃO é necessário ao usar -MMD em combinação com -c (como na versão de Sophie ). Nesta combinação, parece gravar o caminho completo dos arquivos * .o nos arquivos * .d. Sem essa combinação, -MMD também grava apenas os nomes de arquivo puros sem quaisquer componentes de diretório nos arquivos * .d. Talvez alguém saiba por que -MMD grava o caminho completo quando combinado com -c. Não encontrei nenhuma dica na página de manual do g ++.

FPS máximo
fonte