GNU make: генерация автоматических зависимостей с помощью сгенерированных заголовочных файлов - PullRequest
11 голосов
/ 08 марта 2011

Итак, я следовал за Advanced Auto-Dependency Generation paper -

Makefile

SRCS := main.c foo.c

main: main.o foo.o

%.o: %.c
    $(CC) -MMD -MG -MT '$@ $*.d' -c $< -o $@
    cp $*.d $*.tmp
    sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
        -e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
    rm $*.tmp

clean::
    -rm *.o *.d main

-include $(SRCS:.c=.d)

main.c :

#include "foo.h"

int main(int argc, char** argv) {
  foo() ;
  return 0 ;
}

foo.h

#ifndef __FOO_H__
#define __FOO_H__

void foo() ;

#endif

- и это работает как шарм.


Но когда foo.h становится сгенерированным файлом -

Makefile:

...

HDRS := foo.h

$(HDRS):
    mk_header.sh $*

clean::
    -rm $(HDRS)
...

mk_header.sh:

#!/bin/bash
UP=$(tr "[:lower:]" "[:upper:]" <<< $1)

cat <<EOF > $1.h
#ifndef __${UP}_H__
#define __${UP}_H__

void $1() ;

#endif
EOF

В первый раз, когда я запускаю make, main.d еще не сгенерирован, и, таким образом, foo.h не считается обязательным условием и, следовательно, не генерируется:

$ ls
foo.c  main.c  Makefile  mk_header.sh*

$ make
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
    -e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc -MMD -MG -MT 'foo.o foo.d' -c foo.c -o foo.o
cp foo.d foo.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
    -e '/^$/d' -e 's;$; :;' < foo.tmp >> foo.d
rm foo.tmp
cc   main.o foo.o   -o main

$ ls
foo.c  foo.d  foo.o  
main*  main.c  main.d  main.o  
Makefile  mk_header.sh*

Только во 2-м вызове make генерируется foo.h, и в результате другие каскады сборки.

$ make
./mk_header.sh foo
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
    -e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc   main.o foo.o   -o main

$ ls
foo.c  foo.d  foo.h  foo.o  
main*  main.c  main.d  main.o  
Makefile  mk_header.sh*

И только после этого make понимает, что:

$ make
make: `main' is up to date.

Итак, мой вопрос: Есть ли способ расширить рецепт, предложенный в приведенном выше документе, чтобы обеспечить возможность создания сгенерированных заголовочных файлов, без устранения увеличения производительности, достигнутого за счет того, что нет необходимости переоценивать всю сборку дерево при включении *.d фрагментов?

Ответы [ 4 ]

12 голосов
/ 20 апреля 2011

Проблема заключается в том, что генерация *.d Makefile-фрагментов должна выполняться после . Вся генерация заголовка завершена. Говоря так, можно использовать зависимости make для наведения правильного порядка:

SRCS := main.c foo.c
HDRS := foo.h

main: main.o foo.o

%.o: %.c | generated_headers
    $(CC) -MMD -MG -MT '$@ $*.d' -c $< -o $@
    cp $*.d $*.tmp
    sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
        -e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
    rm $*.tmp

-include $(SRCS:.c=.d)

$(HDRS):
    mk_header.sh $*

generated_headers: $(HDRS)

clean:
    -rm $(HDRS) *.o *.d main

.PHONY: clean generated_headers

Примечания:

  1. Я использую зависимость только для заказа .

  2. Это решение является достаточно масштабируемым: каждое правило генерирования заголовка должно быть лишь обязательным условием цели generated_headers .PHONY. Предполагая, что правило генерации заголовка написано правильно, после того, как оно было сгенерировано правильно, удовлетворение цели generated_headers должно быть невозможным.

  3. Невозможно скомпилировать отдельный объект, даже если этот объект не требует каких-либо сгенерированных заголовков, без генерации всех сгенерированных заголовков проекта в первую очередь. Хотя это технически обоснованно, ваши разработчики будут жаловаться.

    Так что вам следует подумать о наличии флага FAST_AND_LOOSE, который отключит эту функцию:

    %.o: %.c | $(if $(FAST_AND_LOOSE),,generated_headers)
        ...
    

    Таким образом, разработчик может выпустить:

    make FAST_AND_LOOSE=1 main.o
    
2 голосов
/ 13 апреля 2015

Makefile в исходном вопросе не работает для меня с gcc 4.8.2:

cc -MMD -MG -MT main.d -c main.c -o main.o
cc1: error: -MG may only be used with -M or -MM

Я думаю, что gcc изменил поведение -MG в какой-то момент за последние 4 года.

Похоже, что если вы хотите поддерживать сгенерированные заголовочные файлы, больше нет любой способ создать файл ".d" и файл ".o" одновременно, без дважды вызывая препроцессор C.

Итак, я обновил рецепт:

%.o: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $*.d $<
    $(CC) -c $< -o $@

(Обратите внимание также, что gcc теперь имеет -MP для генерации фальшивых целей для каждого заголовка, так что вам больше не нужно запускать sed для вывода gcc.)

У нас все еще есть та же проблема, что и в первоначальном вопросе - запуск make первый раз не удается сгенерировать foo.h:

$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
main.c:1:17: fatal error: foo.h: No such file or directory
 #include "foo.h"
                 ^
compilation terminated.
Makefile:7: recipe for target 'main.o' failed
make: *** [main.o] Error 1

Запуск снова работает:

$ make
./mk_header.sh foo
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
cc   main.o   -o main

Так как мы все равно должны дважды запустить препроцессор C, давайте сгенерируем .d файл в отдельном правиле:

%.d: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $@ $<

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

Теперь он правильно генерирует заголовочный файл:

$ make clean
rm -f *.o *.d main foo.h
$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
./mk_header.sh foo
cc -c main.c -o main.o
cc   main.o   -o main

Страдает ли это от проблемы производительности, из-за которой исходный вопрос был пытаясь избежать? По сути, это решение «Базовые автозависимости» описано в Расширенное автозависимость Поколение бумага.

Эта статья требует 3 проблем с этим решением:

  1. Мы повторно выполняем make, если что-то меняется.
  2. Уродливое, но безобидное предупреждение: "main.d: такого файла или каталога нет"
  3. Неустранимая ошибка "нет правила для создания цели foo.h", если файл foo.h удален, даже если упоминание об этом удалено из файла .c.

Проблема 2 решается с помощью -include вместо include. Насколько я могу скажем, это ортогонально технике бумаги, чтобы избежать повторного выполнения make. По крайней мере, у меня не было проблем с использованием -include вместо include.

Проблема 3 решается с помощью -MP GCC (или эквивалентного сценария sed) - это также ортогонально технике, позволяющей избежать повторного исполнения make.

Проблема 1 может быть несколько улучшена чем-то вроде этого:

%.d: %.c
    $(CC) -MM -MG -MP -MT $*.o -MF $@.new $<
    cmp $@.new $@ 2>/dev/null || mv $@.new $@; rm -f $@.new

До этого изменения:

$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d

После этого изменения:

$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing

Чуть лучше. Конечно, если будет введена новая зависимость, make все равно нужно заново выполнить. Может быть, ничего нельзя сделать, чтобы улучшить это; похоже, что это компромисс между скоростью и правильностью.

Все вышеперечисленное было протестировано с маркой 3.81.

2 голосов
/ 09 марта 2011

Краткий ответ: нет. Рецепт, описанный в статье, очень умный, один из моих любимых, но это сложное использование грубого инструмента. Он использует преимущества обычной схемы, в которой существуют все необходимые заголовки; он пытается решить проблему определения того, какие заголовки, если они были недавно изменены, требуют перестройки данного объектного файла. В частности, если объектный файл не существует, он должен быть восстановлен - и в этом случае нет причин беспокоиться о заголовочных файлах, потому что компилятор обязательно их найдет.

Теперь файлы заголовков генерируются. Так что foo.h может не существовать, поэтому кто-то должен будет запустить скрипт для его генерации, и только Make может сделать это. Но Make не может знать, что foo.h необходимо без выполнения некоторого анализа main.c. Но это действительно не может произойти, пока Make не начнет выполнять main связанные правила (например, main.o или main.o.d), которые он не может выполнить до после , когда он определит, какие цели он собирается выполнить строить.

Так что нам придется использовать ... рекурсивную make! [ Dun-серовато-dunnnn! ]

Мы не можем достичь цели статьи избежать повторного вызова Make, но мы, по крайней мере, можем избежать (некоторой) ненужной перестройки. Вы можете сделать что-то вроде «Базовых автозависимостей», описанных в статье; в документе описываются проблемы такого подхода. Или вы можете использовать команду, подобную той, что приведена в «Расширенном» рецепте, для генерации списка заголовков, а затем передать ее в $(MAKE); этот подход аккуратен, но может вызывать Make много раз в одном и том же заголовке, в зависимости от того, как выглядит ваше кодовое дерево.

1 голос
/ 08 марта 2011

Вы можете создать явное правило зависимости для вашего сгенерированного заголовка:

main.o: foo.h

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...