Makefile перекомпилируется каждый раз

Я новичок в C и пытался решить несколько университетских задач, связанных с make-файлом. Это многофайловый проект, и идея состоит в том, чтобы скомпилировать nave.cporto.cmeteo.c. и master.c.

Все, кроме последнего, должны быть связаны с common_ipcs и utils и скомпилированы отдельно. Тем не менее, все это, похоже, работает нормально, и все, но моя проблема заключается в том, что если я запускаю любую из двух целей (отладку или выпуск) несколько раз, они продолжают компилировать все с нуля, даже если ничего не было изменено.

Есть идеи? Вот мой make-файл с удаленными ненужными целями. dbg также удален, так как он идентичен rls.


# basic rules
CC      = gcc
LDFLAGS = -lm
COMMON  = common_ipcs.c utils.c
TARGET  = master
SOURCE  = master.c nave.c porto.c meteo.c
 
# directories
DIRSRC  = source
DIRHDR  = headers
DIRDBG  = debug
DIRRLS  = release
 
# compiling flags
CFLAGS  = -std=c89
DCFLAGS = $(CFLAGS) -g -O0 -pedantic -DDEBUG
RCFLAGS = $(CFLAGS) -O2 -Wall -Wextra -DNDEBUG
 
# $< is the first prerequisite
# $@ is the target
# $^ is all the prerequisites
 
.PHONY: all prep rls dbg drun run clean
 
# build all the prerequisites for compiling the project
all: clean prep rls
 
prep:
    @mkdir -p $(DIRDBG) $(DIRRLS)
 
rls: $(addprefix $(DIRSRC)/, $(SOURCE))
    $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/nave.c -o $(DIRRLS)/nave.o $(LDFLAGS)
    $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/porto.c -o $(DIRRLS)/porto.o $(LDFLAGS)
    $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/meteo.c -o $(DIRRLS)/meteo.o $(LDFLAGS)
    $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/master.c -o $(DIRRLS)/$(TARGET) $(LDFLAGS)


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

Ваш рецепт для rls явно создает nave.o, porto.o и meteo.o, так что да, это будет происходить каждый раз, когда создается цель rls.

John Bollinger 23.11.2022 16:32

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

John Bollinger 23.11.2022 16:35

Общий make совет: минимизируйте использование фальшивых целей. То есть те, где целевое имя не является именем файла, который будет создан по связанному правилу. Фальшивые цели имеют место быть, и есть несколько обычных целей, но новички часто попадают в беду, думая об именах целей, как если бы они были именами подпрограмм, а на самом деле это не так.

John Bollinger 23.11.2022 16:38
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

У вас есть это правило:

rls: $(addprefix $(DIRSRC)/, $(SOURCE))
        $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/nave.c -o $(DIRRLS)/nave.o $(LDFLAGS)
        $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/porto.c -o $(DIRRLS)/porto.o $(LDFLAGS)
        $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/meteo.c -o $(DIRRLS)/meteo.o $(LDFLAGS)
        $(CC) $(RCFLAGS) $(addprefix $(DIRSRC)/, $(COMMON)) $(DIRSRC)/master.c -o $(DIRRLS)/$(TARGET) $(LDFLAGS)

На данный момент мы проигнорируем неправильный вызов компилятора (вы создаете программы, но называете их .o файлами, что неправильно или, по крайней мере, не очень хорошая идея).

Что говорит это правило? Он сообщает make, что существует цель с именем rls и у нее есть предварительные условия source/master.c, source/nave.c, source/porto.c и source/meteo.c, и что если цель rls устарела, он должен запустить рецепт, состоящий из четырех вызовов компилятора, который создаст четыре разные программы: три с именами release/nave.o, release/porto.o, release/meteo.o и, наконец, одна с именем release/master.

Таким образом, make добросовестно проверяет метку времени файла на вашем диске с именем rls, которого не существует, поэтому он никогда не может считаться актуальным, и поскольку он устарел, он запускает рецепт, который вызывает все четыре строки компиляции. Каждый раз.

Когда вы пишете правило make-файла, вы должны следовать следующим правилам: (a) каждый рецепт должен создавать ТОЛЬКО ОДНУ цель, и никогда более одной; и (b) целевое имя правила должно быть точным именем файла, который создает рецепт: если рецепт создает файл с именем blahblah.o, то целью правила должно быть blahblah.o. Если рецепт создает файл с именем some/other/dir/foo, то целью правила должен быть some/other/dir/foo.

Когда вы станете лучше разбираться в make-файлах, вы поймете, что иногда полезно и неплохо нарушать эти правила. Но это хорошая отправная точка.

Вышеприведенное правило (b) лучше всего выполняется, если ваш рецепт создает файл $@, который представляет собой переменную, которую make установит на имя, которое, как ожидается, будет создано вашим рецептом. Так что, если вы используете $(CC) ... -o $@, например, то вы знаете, что ваш рецепт всегда будет создавать файл, который make должен был создать.

MadScientist 23.11.2022 16:41

Эй, фантастический ответ. Большое спасибо! Итак, правильно ли предположить, что мне нужно сделать 8 целей, 4 для отладки и 4 для конфигурации выпуска? Или я могу использовать более сложный способ сделать это? Я хотел бы проголосовать за ваш ответ, но пока не могу этого сделать. Я отмечу это как принятый ответ, как только применю эти предложения на практике через минуту.

rith 23.11.2022 16:50

Создание разных целей — один из способов сделать это. Это, безусловно, просто для понимания, поэтому, возможно, стоит начать. Другой способ — использовать шаблонные правила, определяющие один шаблон, который make может использовать для создания всех целей, соответствующих этому шаблону, поэтому вам не нужно записывать их все по отдельности. Таким образом, шаблон будет выглядеть примерно так: $(DIRDBG)/%.o : $(DIRSRC)/%.c затем рецепт, который создает эту цель $@ как объект отладки. Затем еще одно правило шаблона для $(DIRRLS)/%.o : $(DIRSRC)/%.c

MadScientist 23.11.2022 16:58

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

rith 23.11.2022 18:16

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

MadScientist 23.11.2022 21:14

Я не понимаю вопроса «Исключает ли это каталоги». Make не делает различий между файлами и каталогами: если вы используете каталог в качестве цели, то его рецепт будет запущен, если он не существует или если его предварительные условия новее, чем он. Как правило, вы не хотите использовать каталоги в качестве целевых или предварительных условий, потому что правила обновления времени изменения каталогов могут сделать поведение неожиданным.

MadScientist 23.11.2022 21:14

Хорошо, я неправильно понял функциональность .PHONY, и после повторного просмотра страницы GNU я вижу, где было недоразумение. Спасибо.

rith 24.11.2022 13:39

Другие вопросы по теме