C++. Сборник рецептов - Д. Стефенс
Шрифт:
Интервал:
Закладка:
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) $< > [email protected]$$$$;
sed 's.($*).o[ :]*.1.o [email protected] : ,g' < [email protected]$$$$ > [email protected],
rm -f [email protected]$$$$
Этот фрагмент кода основан на опции компилятора -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> [email protected]$$$$ > /dev/null;
sed -n 's/^Note: including file: *(.*)/$*.obj•$*.d:1/gp'
< [email protected]$$$$ | sed "s:\:/:g:s: :\ :gp" > [email protected];
rm -f [email protected]$$$$
В этом примере символ • обозначает Tab.
Давайте сделаем еще одно последнее усовершенствование примера 1.20. В настоящий момент последовательность john paul johnpaul содержится в двух местах — в пререквизитах правила для сборки статической библиотеки и в директиве include, используемой для генерации зависимостей. Если список исходных файлов изменится, вам придется вносить изменения в двух местах make-файла. Гораздо лучше определить переменную SOURCES и заменить оба использования последовательности john paul johnpaul на выражения, использующие SOURCES.
SOURCES = john.cpp paul.cpp johnpaul.cpp
...
# Собираем libjohnpaul.a из john.о, paul.o и johnpaul.о
$(OUTPUTFILE): $(subst .cpp, .o,$(SOURCES))
ar ru [email protected] $^
ranlib [email protected]
...
# Генерируем зависимости .cpp-файлов от .hpp-файлов
include $(subst .cpp,.d,$(SOURCES))
%d: %.cpp
$(CC) -M $(CPPFLAGS) $< > [email protected]$$$$;
sed 's,($*).o[ :]*.1.o [email protected] : .g' < [email protected] $$$$ > [email protected];
rm -f [email protected]$$$$
Здесь я использую функцию make $(subst x, y, str), которая заменяет в строке str все вхождения x на y.
GNU make поддерживает большое количество функций обработки строк и имен файлов, а также много других. Также она поддерживает определение пользовательских функций. Как обычно, за подробным описанием обратитесь к Managing Projects with GNU make, Third Edition Роберта Мекленбурга (O'Reilly).
Смотри такжеРецепты 1.2 и 1.7.
1.17. Сборка динамической библиотеки с помощью GNU Make
ПроблемаВы хотите использовать GNU make для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.
РешениеВначале в директории, где должна быть создана динамическая библиотека, создайте make-файл и объявите фиктивную цель all, единственным пререквизитом которой будет эта динамическая библиотека. Затем объявите цель динамической библиотеки. Ее пререквизитами должны быть объектные файлы, из которых она собирается, а ее командный сценарий должен представлять собой командную строку для сборки библиотеки из набора объектных файлов, аналогично показанному в рецепте 1.4. При использовании GCC или компилятора с похожим синтаксисом командной строки настройте, если требуется, неявные правила шаблонов, изменив одну или более переменных CXX, CXXFLAGS и т.п., используемых в базе данных неявных правил make, как показано в рецепте 1.15. В противном случае, используя синтаксис шаблонных правил, описанный в рецепте 1.16, напишите шаблонное правило, говорящее make, как с помощью командной строки из табл. 1.8 скомпилировать .cpp-файлы в объектные. Наконец добавьте цели install и clean, как показано в рецепте 1.15, и механизм для автоматической генерации зависимостей исходных файлов, как показано в рецепте 1.16.
Например, чтобы из исходных файлов, перечисленных в примере 1.2, собрать динамическую библиотеку с помощью GCC в Unix, в директории georgeringo создайте make-файл, показанный в примере 1.22.
Пример 1.22. make-файл для libgeorgeringo.so с использованием GCC
# Укажите расширения файлов, удаляемых при очистке
CLEANEXTS = o so
# Укажите исходные файлы, целевой файл и директорию установки
SOURCES = george.cpp ringo.cpp georgeringo.cpp
OUTPUTFILE = libgeorgeringo.so
INSTALLDIR = ../binaries
.PHONY: all
all: $(OUTPUTFILE)
# Соберите libgeorgeringo.so из george.o, ringo.о
# и georgeringo.o; subst - это функция поиска и замены.
# показанная в рецепте 1.16
$(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES))
$(CXX) -shared -fPIC $(LDFLAGS) -о
.PHONY: install
install:
mkdir -p $(INSTALLDIR)
cp -p $(OUTPUTFILE) $(INSTALLDIR)
.PHONY: clean
clean:
for file in $(CLEANEXTS); do rm -f *.$$file; done
# Сгенерируйте зависимости файлов .cpp от файлов .hpp
include $(subst .cpp,.d,$(SOURCES))
%.d: %.cpp
$(CC) -M $(CPPFLAGS) $< > [email protected]$$$$;
sed 's. ($*).o[ :]*.1.o [email protected] : ,g' < [email protected]$$$$ > [email protected];
rm -f [email protected]$$$$
Обсуждениеmake-файл из примера 1.22 — это прямое применение идей из рецептов 1.4, 1.15 и 1.16. Главным отличием между примерами 1.22 и 1.20 является правило для сборки libgeorgeringo.so из объектных файлов george.o, ringo.o и georgeringo.о.
$(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES))
$(CXX) -shared -fPIC $(LDFLAGS) -о [email protected] $^
Здесь $(OUTPUTFILE) раскрывается как libgeorgeringo.so, а выражение $(subst.cpp, .o, $(SOURCES)) раскрывается как george.о, ringo.о и georgeringo.o, как показано в рецепте 1.16. Командный сценарий $(CXX) -shared -fPIC $(LDFLAGS) -о — это адаптация командной строки GCC, показанной в табл. 1.11.