.PHONY: clean

clean:

    for file in $(CLEANEXTS); do rm -f *.$$file; done

# Укажите зависимости файлов .cpp от файлов .hpp

john.obj: john.hpp

paul.obj: paul.hpp

johnpaul.obj: john.hpp paul.hpp johnpaul.hpp

В примере 1.21 я с помощью переменной среды MSVCDIR, устанавливаемой в vcvars32.bat, показал команду Visual C++ link.exe как '$(MSVCDIR)/bin/link'. Это позволяет избежать путаницы между компоновщиком Visual C++ и командой Unix link, поддерживаемой Cygwin и MSYS. Для полноты картины я также использовал MSVCDIR для команды компиляции Visual С++.

Обсуждение

Давайте подробно рассмотрим пример 1.20. Вначале я определяю переменные, представляющие выходной файл, директорию установки и расширения файлов, которые должны удаляться при сборке цели clean. Затем я объявляю цель по умолчанию all, как в примере 1.14.

Правило для сборки статической библиотеки выглядит так.

$(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 и заменить оба

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату