Как сделать так, чтобы Makefile автоматически перестраивал исходные файлы, содержащие измененный файл заголовка? (В C / C ++)

92

У меня есть следующий файл makefile, который я использую для сборки программы (фактически ядра), над которой я работаю. Это с нуля, и я изучаю этот процесс, поэтому он не идеален, но я думаю, что на данный момент он достаточно мощный для моего уровня опыта написания make-файлов.

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

Моя основная проблема с этим make-файлом заключается в том, что когда я изменяю файл заголовка, который включает один или несколько файлов C, файлы C не восстанавливаются. Я могу исправить это довольно легко, если все мои файлы заголовков будут зависимостями для всех моих файлов C, но это фактически приведет к полной перестройке проекта каждый раз, когда я изменяю / добавляю файл заголовка, что было бы не очень изящно.

Я хочу, чтобы были перестроены только файлы C, которые включают файл заголовка, который я изменяю, и чтобы весь проект был снова связан. Я могу выполнить связывание, заставив все файлы заголовков быть зависимостями цели, но я не могу понять, как сделать файлы C недействительными, если их включенные файлы заголовков новее.

Я слышал, что в GCC есть несколько команд, чтобы сделать это возможным (чтобы make-файл мог каким-то образом определить, какие файлы нужно перестроить), но я не могу найти реальный пример реализации, чтобы посмотреть. Может ли кто-нибудь опубликовать решение, которое включит такое поведение в make-файле?

РЕДАКТИРОВАТЬ: Я должен уточнить, я знаком с концепцией размещения отдельных целей и наличия каждой цели. Поэтому требуются файлы заголовков. Это требует, чтобы я редактировал make-файл каждый раз, когда я куда-то включаю файл заголовка, что немного неудобно. Я ищу решение, которое может самостоятельно определять зависимости файла заголовка, и я уверен, что видел это в других проектах.

Николас Флинт
источник

Ответы:

30

Как уже указывалось в другом месте на этом сайте, см. Эту страницу: Генерация автоматической зависимости

Короче говоря, gcc может автоматически создавать для вас файлы зависимостей .d, которые представляют собой мини-фрагменты make-файла, содержащие зависимости скомпилированного вами файла .c. Каждый раз, когда вы изменяете файл .c и компилируете его, файл .d будет обновляться.

Помимо добавления флага -M в gcc, вам необходимо включить файлы .d в make-файл (как писал Крис выше). На странице есть несколько более сложных проблем, которые решаются с помощью sed, но вы можете игнорировать их и выполнить «очистку», чтобы удалить файлы .d всякий раз, когда make жалуется на невозможность создания файла заголовка, который больше не существует. .

Уди Мейри
источник
2
Я могу ошибаться, но я думаю, что GCC действительно добавил функцию, чтобы попытаться обойти эту проблему с sed. Ознакомьтесь с gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html конкретно -MP.
Eugene Marcotte
Да, -MP существует с GCC 3, существует в clang и icc и устраняет необходимость в sed. bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…
hmijail оплакивает уволившихся
Последняя версия ссылки в ответе содержит примеры использования флагов GCC.
MadScientist
20

Вы можете добавить команду 'make зависимой', как заявляли другие, но почему бы не заставить gcc создавать зависимости и компилировать одновременно:

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

-include $(DEPS)

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

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

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

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

Мартин Фидо
источник
18
У меня это не сработало. Например, make-файл сгенерировал и запустил следующее: g++ -c -Wall -Werror -MM -MF main.d -o main.o main.cpp Я получил файл main.d, но main.o был равен 0 байтам. Однако флаг -MMD, похоже, делает именно то, что требовалось. Итак, моим рабочим правилом makefile стало:$(CC) -c $(CFLAGS) -MMD -o $@ $<
Даррен Кук
17

Это эквивалентно ответу Криса Додда , но использует другое соглашение об именах (и по совпадению не требует sedмагии. Скопировано из более позднего дубликата .


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

depend: .depend

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

include .depend

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

dmckee --- котенок экс-модератора
источник
4
Почему бы вам не указать ИСТОЧНИКИ? Не требует особенно большого количества символов и не так запутан, как "SRCS", который может выглядеть как акроним.
HelloGoodbye
@HelloGoodbye Немного стиля. Я всегда использовал, и всегда видел, SRCSи OBJS. Я бы согласен в большинстве случаев, но каждый должен знать, что это такое.
sherrellbc
@sherrellbc То, что люди «должны» знать, и то, что они знают на самом деле, часто являются двумя разными вещами: P
HelloGoodbye
7

Вам нужно будет создать индивидуальные цели для каждого файла C, а затем указать файл заголовка как зависимость. Вы по-прежнему можете использовать свои общие цели, а .hпотом просто разместить зависимости, например:

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

foo.c: bar.h
# And so on...
мипади
источник
4

По сути, вам нужно динамически создавать правила make-файла для перестройки объектных файлов при изменении файлов заголовков. Если вы используете gcc и gnumake, это довольно просто; просто введите что-то вроде:

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

в вашем make-файле.

Крис Додд
источник
2
Я как бы понимаю это, за исключением того, что (помимо новых флагов -MM и -MG) я не понимаю, для чего нужна строка загадочного текста с регулярным выражением. Это не сделает меня счастливыми, товарищи по команде ... ^ _ ^ Я попробую, но посмотрю, есть ли у меня какие-нибудь результаты.
Николас Флинт
sed - это сокращение от «редактор потока», который может изменять поток текста без необходимости использования файла. Это стандартный инструмент Unix, он меньше и быстрее, поэтому он используется чаще, чем awk или perl.
Zan Lynx
Ах, вот в чем проблема: я делаю это под Windows.
Николас Флинт,
3

Помимо того, что сказал @mipadi, вы также можете изучить использование -Mопции ' ' для создания записи о зависимостях. Вы даже можете сгенерировать их в отдельный файл (возможно, «depends.mk»), который затем включите в make-файл. Или вы можете найти make dependправило, которое редактирует make-файл с правильными зависимостями (термины Google: «не удалять эту строку» и зависеть).

Джонатан Леффлер
источник
1

Ни один из ответов не помог мне. Например, ответ Мартина Фидо предполагает, что gcc может создавать файл зависимостей, но когда я попытался, он генерировал для меня пустые (нулевые байты) объектные файлы без каких-либо предупреждений или ошибок. Это может быть ошибка gcc. Я на

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

Итак, вот мой полный Makefile, который мне подходит; это комбинация решений + что-то, о чем никто не упоминал (например, "правило замены суффикса", указанное как .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

Обратите внимание, что я использовал .cc .. Вышеупомянутый Makefile легко настроить для файлов .c.

Также обратите внимание на важность этих двух строк:

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

поэтому gcc вызывается один раз для создания файла зависимостей, а затем фактически компилирует файл .cc. И так для каждого исходного файла.

Tagar
источник
0

Более простое решение: просто используйте Makefile, чтобы правило компиляции .c в .o зависело от файла (ов) заголовка и всего остального, что имеет отношение к вашему проекту в качестве зависимости.

Например, в Makefile где-нибудь:

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

Я считаю, что mkdepкоманда - это то, что вы хотите. Фактически он сканирует файлы .c на наличие #includeстрок и создает для них дерево зависимостей. Я считаю, что проекты Automake / Autoconf используют это по умолчанию.

трясти
источник