Makefile, залежності заголовка


97

Скажімо, у мене є make-файл із правилом

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

Я хочу, щоб * .o перебудовувався щоразу, коли змінюється файл заголовка. Замість того, щоб розробляти список залежностей, щоразу, коли будь-який файл заголовка /includeзмінюється, тоді всі об’єкти в папці мають бути відновлені.

Я не можу придумати хорошого способу змінити правило, щоб це врахувати, я відкритий для пропозицій. Бонусні бали, якщо список заголовків не повинен бути жорстко закодованим


Написавши свою відповідь нижче, я переглянув відповідний список і виявив: stackoverflow.com/questions/297514/…, який видається дублікатом. Відповідь Кріса Додда еквівалентна моїй, хоча вона використовує іншу конвенцію іменування.
dmckee --- екс-модератор кошеня 07.03.10

Відповіді:


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запустити лише тоді, коли вихідні файли змінилися? Здається, він запускається кожного разу незалежно ...
погоня

2
@chase: Ну, я помилково зробив залежність від об'єктних файлів, коли вона, очевидно, повинна бути від джерел, і порядок залежності теж був неправильним для двох цілей. Це те, що я отримую за набір тексту з пам'яті. Спробуй зараз.
dmckee --- екс-модератор кошеня

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

Я змінив SRCS на OBJECTS, де OBJECTS - це список моїх * .o файлів. Здавалося, це заважало запускатись щоразу, а також зафіксувало зміни лише у файлах заголовків. Це здається суперечливим попереднім коментарям .. я чогось пропустив?
BigBrownBear00

2
Навіщо потрібна крапка з комою? якщо я спробую, що без цього або з -MF ./.depend не є останнім аргументом, він зберігає лише залежності останнього файлу в $ (SRCS).
humodz

72

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

За замовчуванням CPP бере ім'я основного вхідного файлу, видаляє будь-які компоненти каталогу та будь-який суфікс файлу, такий як '.c', та додає звичайний суфікс об'єкта платформи.

Варіант (дещо новіший) -MMD- це, мабуть, те, що ви хочете. Для повноти наведемо приклад make-файлу, який підтримує декілька директорій 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 depend. Корисно !!
Роберт

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?
Галоа

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 вирішить примітку в останніх рядках вашої відповіді, яка оновлює ціль кожного списку залежностей.
Годрік Сір

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

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

Наведене вище рішення Мартіна чудово працює, але не обробляє файли .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


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 + man сторінці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.