Makefile, зависимости заголовков


97

Скажем, у меня есть make-файл с правилом

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

Я хочу, чтобы * .o перестраивался при изменении файла заголовка. Вместо того, чтобы составлять список зависимостей, всякий раз, когда /includeизменяется какой-либо файл заголовка , все объекты в каталоге должны быть перестроены.

Я не могу придумать хороший способ изменить правило, чтобы учесть это, я открыт для предложений. Бонусные баллы, если список заголовков не нужно жестко задавать


Написав свой ответ ниже, я просмотрел связанный список и обнаружил: stackoverflow.com/questions/297514/…, который, похоже, является дубликатом. Ответ Криса Додда эквивалентен моему, хотя в нем используется другое соглашение об именах.
dmckee --- котенок экс-модератора

Ответы:


116

Если вы используете компилятор GNU, он может составить для вас список зависимостей. Фрагмент Makefile:

depend: .depend

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

include .depend

или

depend: .depend

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

include .depend

где SRCS- переменная, указывающая на весь ваш список исходных файлов.

Есть еще инструмент makedepend, но он мне никогда не нравился так сильно, какgcc -MM


2
Мне нравится этот трюк, но как я могу dependзапустить его только тогда, когда исходные файлы изменились? Кажется, он запускается каждый раз, несмотря ни на что ...
chase

2
@chase: Ну, я ошибочно установил зависимость от объектных файлов, хотя очевидно, что она должна быть от источников, а также неправильный порядок зависимости для двух целей. Вот что я получаю за набор текста по памяти. Попробуй это сейчас.
dmckee --- котенок экс-модератора

4
Можно ли добавить перед каждым файлом некоторый префикс, чтобы показать, что он находится в другом каталоге, например build/file.o?
RiaD

Я изменил SRCS на OBJECTS, где OBJECTS - это список моих файлов * .o. Это, казалось, предотвращало запуск зависимости каждый раз, а также обнаруживало изменения только в файлах заголовков. Это кажется противоречащим предыдущим комментариям. Я что-то упустил?
BigBrownBear00

2
Зачем нужна точка с запятой? если я попробую сделать это без него или если -MF ./.depend не будет последним аргументом, он сохранит только зависимости последнего файла в $ (SRCS).
humodz 04

72

Большинство ответов на удивление сложны или ошибочны. Однако простые и надежные примеры были опубликованы в другом месте [ codereview ]. По общему признанию, параметры, предоставляемые препроцессором GNU, немного сбивают с толку. Однако удаление всех каталогов из целевой сборки с помощью -MMзадокументировано и не является ошибкой [ gpp ]:

По умолчанию CPP принимает имя основного входного файла, удаляет все компоненты каталога и любой суффикс файла, такой как '.c', и добавляет обычный суффикс объекта платформы.

-MMDВозможно, вам нужен вариант (несколько более новый) . Для полноты картины приведен пример файла makefile, который поддерживает несколько директорий src и директории сборки с некоторыми комментариями. Простую версию без директорий сборки см. В [ 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)

Этот метод работает, потому что, если существует несколько строк зависимостей для одной цели, зависимости просто объединяются, например:

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

эквивалентно:

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

как упоминалось в: Makefile нескольких строк зависимостей для одной цели?


1
Мне нравится это решение. Я не хочу вводить команду make зависимой. Полезно !!
Роберт

1
В значении переменной OBJ есть орфографическая ошибка: CPPдолжно читатьсяCPPS
ctrucza

1
Это мой предпочтительный ответ; +1 для вас. Это единственный вариант на этой странице, который имеет смысл и охватывает (насколько я могу судить) все ситуации, когда необходима перекомпиляция (избегая ненужной компиляции, но все же достаточной)
Joost

1
По умолчанию мне не удалось найти заголовки, хотя hpp и cpp находятся в одном каталоге.
villasv

1
если у вас есть исходные файлы ( a.cpp, b.cpp) ./src/, не сделает $(OBJ)=./build/src/a.o ./build/src/b.oли эта замена ?
galois

26

Как я писал здесь, gcc может одновременно создавать зависимости и компилировать:

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

-include $(DEPS)

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

Параметр -MF указывает файл, в котором будут храниться зависимости.

Тире в начале '-include' говорит Make продолжить, когда файл .d не существует (например, при первой компиляции).

Обратите внимание, что в gcc есть ошибка в отношении параметра -o. Если вы установите имя файла объекта как obj / _file__c.o, тогда сгенерированный файл .d по-прежнему будет содержать файл .o, а не obj / _file__c.o.


4
Когда я пытаюсь это сделать, все мои файлы .o создаются как пустые. У меня есть мои объекты во вложенной папке сборки (так что $ OBJECTS содержит build / main.o build / smbus.o build / etc ...), и это, безусловно, создает файлы .d, как вы описали с очевидной ошибкой, но это, безусловно, вообще не создает файлы .o, тогда как это произойдет, если я удалю -MM и -MF.
bobpaul

1
Использование -MT разрешит примечание в последних строках вашего ответа, которое обновляет цель каждого списка зависимостей.
Godric Seer

3
@bobpaul, потому что man gccговорит -MMподразумевает -E, что "останавливается после предварительной обработки". -MMDВместо этого вам нужно : stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

23

Как насчет чего-то вроде:

includes = $(wildcard include/*.h)

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

Вы также можете использовать подстановочные знаки напрямую, но я часто обнаруживаю, что они мне нужны более чем в одном месте.

Обратите внимание, что это хорошо работает только в небольших проектах, поскольку предполагает, что каждый объектный файл зависит от каждого файла заголовка.


спасибо, я придумал, что это будет намного сложнее, чем нужно
Майк

15
Это работает, однако проблема заключается в том, что каждый объектный файл перекомпилируется каждый раз, когда вносятся небольшие изменения, то есть, если у вас есть 100 исходных файлов / файлов заголовков, и вы вносите небольшое изменение только в один, все 100 перекомпилируются. .
Николас Гамильтон

1
Вам действительно следует обновить свой ответ, чтобы сказать, что это очень неэффективный способ сделать это, потому что он перестраивает ВСЕ файлы каждый раз, когда изменяется ЛЮБОЙ файл заголовка. Другие ответы намного лучше.
xaxxon

2
Это очень плохое решение. Конечно, он будет работать над небольшим проектом, но для любой рабочей группы и сборки это приведет к ужасному времени компиляции и станет эквивалентом запускаmake clean all .
Жюльен Герто

В моем тесте это вообще не работает. gccЛиния не выполняется вообще, но встроенный в правиле (%o: %.c правило) выполняется вместо.
Penghe Geng

4

Приведенное выше решение Martin отлично работает, но не обрабатывает файлы .o, которые находятся в подкаталогах. Годрик указывает, что флаг -MT решает эту проблему, но одновременно предотвращает правильную запись файла .o. Обе эти проблемы решаются следующим образом:

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

-include $(DEPS)

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

3

Это отлично справится со своей задачей и даже обработает указанные подкаталоги:

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

протестировал его с помощью gcc 4.8.3


3

Вот двухстрочный вариант:

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

Это работает с рецептом make по умолчанию, если у вас есть список всех ваших объектных файлов в OBJS.


1

Я предпочитаю это решение, а не принятый ответ Майкла Уильямсона, он улавливает изменения в источниках + встроенных файлах, затем в источниках + заголовках и, наконец, только в источниках. Преимущество здесь в том, что вся библиотека не перекомпилируется, если внесены только несколько изменений. Не слишком большое значение для проекта с парой файлов, но если у вас есть 10 или 100 источников, вы заметите разницу.

COMMAND= gcc -Wall -Iinclude ...

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

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

%.o: %.cpp
    $(COMMAND)

2
Это работает только в том случае, если в ваших файлах заголовков нет ничего, что потребовало бы перекомпиляции любых cpp-файлов, кроме соответствующего файла реализации.
matec 04


0

Слегка измененная версия ответа Софи, которая позволяет выводить файлы * .d в другую папку (я вставлю только ту интересную часть, которая генерирует файлы зависимостей):

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

Обратите внимание, что параметр

-MT $@

используется для обеспечения того, чтобы цели (т. е. имена объектных файлов) в сгенерированных файлах * .d содержали полный путь к файлам * .o, а не только имя файла.

Я не знаю, почему этот параметр НЕ нужен при использовании -MMD в сочетании с -c (как в версии Софи ). В этой комбинации кажется, что полный путь к файлам * .o записывается в файлы * .d. Без этой комбинации -MMD также записывает только чистые имена файлов без каких-либо компонентов каталога в файлы * .d. Может быть, кто-нибудь знает, почему -MMD записывает полный путь в сочетании с -c. Я не нашел никаких подсказок на странице руководства g ++.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.