Правило для сборки статической библиотеки выглядит так.
$(OUTPUTFILE): john.o paul.o johnpaul.о
ar ru $@ $^
ranlib $@
Это непосредственная адаптация записи для GCC из табл. 1.10. Здесь $(OUTPUTFILE)и $@раскрываются как libjohnpaul.a, а $^раскрывается в виде списка пререквизитов john.o paul.o johnpaul.о.
Следующие два правила объявляют цели installи clean, как в рецепте 1.15. Единственное отличие состоит в том, что в примере 1.20 для удаления всех файлов, чьи расширения имеются в списке о а— т. е. все объектные файлы и файлы статической библиотеки, - я использую цикл оболочки.
for file in $(CLEANEXTS); do rm -f *.$$file; done
Двойной знак доллара я использовал для того, чтобы запретить make раскрывать переменную $$fileпри передаче ее оболочке.
Три последних правила указывают отношения зависимостей между файлами .cpp библиотеки и включаемыми в них заголовочными файлами. Здесь указано по одному правилу для каждого .cpp -файла. Целью такого правила является объектный файл, собираемый из .cpp -файла, а пререквизитами являются заголовочные файлы, явно или неявно включаемые .cpp -файлом.
john.o: john.hpp
paul.o: paul.hpp
johnpaul.o. john.hpp paul.hpp johnpaul.hpp
Это можно понять следующим образом. Если .cpp -файл явно или косвенно включает заголовочный файл, то он должен быть пересобран при каждом изменении этого заголовочного файла. Однако, так как .cpp -файл существует и не является целью какого-либо правила, он никогда не устаревает, как описано в рецепте 1.15. Следовательно, при изменении заголовочного файла перекомпиляции не происходит. Чтобы исправить эту ситуацию, требуется объявить правило, сделав эти зависимости явными; когда один из используемых заголовочных файлов изменяется, объектный файл, соответствующий .cpp -файлу, устаревает, что приводит к перекомпиляции .cpp -файла.
Это решение удобно только для очень небольших проектов, так как очень сложно постоянно отслеживать зависимости целей, представляющих собой исходные файлы, входящие в большую базу кода. К счастью, имеется несколько способов автоматической генерации этих зависимостей. Например, три последних правила примера 1.20 можно заменить на следующие.
# Генерируем зависимости .cpp-файлов от .hpp-файлов
include john.o paul.o johnpaul.о
%.d: %.cpp
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's.\($*\)\.o[ :]*.\1.o $@ : ,g' < $@.$$$$ > $@, \
rm -f $@.$$$$
Этот фрагмент кода основан на опции компилятора -M , которая заставляет GCC вывести в make-файл информацию о зависимостях. За подробным описанием того, как это работает, и почему иногда не подходит, обратитесь к книге Managing Projects with GNU make , Third Edition, написанной Робертом Мекленбургом (Robert Mecklenburg) (O'Reilly).
Код для генерации зависимостей помещайте в конец make-файла.
Так как большинство компиляторов имеет опцию, аналогичную опции -М GCC, этот метод может быть адаптирован для работы с большинством инструментов. На самом деле обычно эта опция называется -М или -m . Однако Visual C++ не имеет опции для генерации зависимостей в make-файле. При использовании Visual C++ есть две возможности. Можно использовать опцию -Gm совместно с опциями -Zi или -ZI , обсуждаемыми в рецепте 1.21. Опция -Gm говорит компилятору создать базу данных, сохраняемую в файле с расширением idb , содержащую информацию о зависимостях между исходными файлами. Файл . idb создается при первоначальной компиляции файла или набора файлов .cpp . При последующих компиляциях перекомпилируются только те исходные файлы, которые были изменены или зависят от изменившихся заголовочных файлов.
Кроме того, можно использовать опцию -showIncludes совместно с опцией -E . Опция -showIncludes приводит к тому, что компилятор при каждом обнаружении директивы include выводит в стандартный поток ошибок сообщение. Опция -E говорит компилятору запустить препроцессор и выйти, не создавая никаких двоичных файлов. С помощью небольшого сценария оболочки можно использовать вывод, сгенерированный -showIncludes ; для создания зависимостей в make-файле.
include john.d paul.d johnpaul.d
%d: %.cpp
"$(MSVCDIR)/bin/cl" -E -showIncludes $< 2> $@.$$$$ > /dev/null; \
sed -n 's/^Note: including file: *\(.*\)/$*.obj•$*.d:\1/gp' \
< $@.$$$$ | sed "s:\\:/:g:s: :\\ :gp" > $@; \
rm -f $@.$$$$
В этом примере символ • обозначает Tab.
Давайте сделаем еще одно последнее усовершенствование примера 1.20. В настоящий момент последовательность john paul johnpaulсодержится в двух местах — в пререквизитах правила для сборки статической библиотеки и в директиве include, используемой для генерации зависимостей. Если список исходных файлов изменится, вам придется вносить изменения в двух местах make-файла. Гораздо лучше определить переменную SOURCESи заменить оба использования последовательности john paul johnpaulна выражения, использующие SOURCES.
Читать дальше