# Сгенерируйте зависимости файлов .cpp от файлов .hpp
include $(subst .cpp,.d,$(SOURCES))
%.d: %.cpp
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's. \($*\)\.o[ :]*.\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
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) -о $@ $^
Здесь $(OUTPUTFILE)
раскрывается как libgeorgeringo.so
, а выражение $(subst.cpp, .o, $(SOURCES))
раскрывается как george.о
, ringo.о
и georgeringo.o
, как показано в рецепте 1.16. Командный сценарий $(CXX) -shared -fPIC $(LDFLAGS) -о
— это адаптация командной строки GCC, показанной в табл. 1.11.
Рецепты 1.4, 1.9, 1.12, 1.19 и 1.23.
1.18. Сборка сложного приложения с помощью GNU make
Вы хотите использовать GNU make для сборки исполняемого файла, зависящего от нескольких статических и динамических библиотек.
Выполните следующие действия.
1. Создайте make-файлы для библиотек, используемых приложением, как описано в рецептах 1.16 и 1.17. Эти make-файлы должны находиться в отдельных директориях.
2. Создайте make-файл в еще одной директории. Этот make-файл будет использоваться для сборки приложения, но только после того, как будут выполнены make-файлы из шага 1. Укажите в этом make-файле фиктивную цель all
, чьим пререквизитом будет являться исполняемый файл. Объявите цель исполняемого файла с пререквизитами, состоящими из библиотек, используемых приложением, а также объектных файлов, которые собираются из .cpp-файлов приложения. Напишите командный сценарий для сборки исполняемого файла из набора библиотек и объектных файлов, как описано в рецепте 1.5. Если необходимо, напишите шаблонное правило для генерации объектных файлов из .cpp-файлов, как показано в рецепте 1.16. Добавьте цели install и clean, как показано в рецепте 1.15, и механизм для автоматической генерации зависимостей исходных файлов, как показано в рецепте 1.16.
3. В директории, родительской по отношению к директориям, содержащим все остальные make-файлы, создайте новый make-файл — давайте называть его главным (top-level) make-файлом, а все остальные — подчиненными. Объявите цель по умолчанию all с пререквизитами в виде директории, содержащей make файл, созданный на шаге 2. Объявите правило, чьи цели состоят из директорий, содержащих подчиненные make-файлы, а командный сценарий вызывает make в каждой целевой директории для цели, указанной в виде значения переменной TARGET
. Наконец, объявите цели, указывающие зависимости между целями по умолчанию подчиненных make-файлов.
Например, чтобы из исходных файлов из примера 1.3 собрать исполняемый файл с помощью GCC в Unix, создайте такой make-файл, как показанный в примере 1.23.
Пример 1.23. make файл для hellobeatles.exe с использованием GCC
# Укажите исходные файлы, целевой файл, директории сборки
# и директорию установки
SOURCES = hellobeatles.cpp
OUTPUTFILE = hellobeatles
LIBJOHNPAUL = libjohnpaul.a
LIBGEORGERINGO = libgeorgeringo.so
JOHNPAULDIR = ../johnpaul
GEORGERINGODIR = ../georgeringo
INSTALLDIR = ../binaries
#
# Добавьте в путь поиска заголовочных файлов родительскую директорию
#
CPPFLAGS += -I..
#
# Цель по умолчанию
#
.PHONY: all
alclass="underline" $(HELLOBEATLES)
#
# Цель для сборки исполняемого файла.
#
$(OUTPUTFILE): $(subst .cpp,.о,$(SOURCES)) \
$(JOHNPAULDIR)/$(LIBJOHNPAUL) \
$(GEORGERINGODIR)/$(LIBGEORGERINGO)
$(CXX) $(LDFLAGS) -o $@ $^
.PHONY: install
instalclass="underline"
mkdir -p $(INSTALLDIR)
cp -p $(OUTPUTFILE) $(INSTALLDIR)
.PHONY: clean
clean:
rm -f *.o
rm -f $(OUTPUTFILE)
#Сгенерируйте зависимости .cpp-файлов от .hpp-файлов
include $(subst .cpp,.d,$(SOURCES))
%.d: %.cpp
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@..$$$$ > $@; \
rm -f $@.$$$$
Далее в директории, содержащей johnpaul, georgeringo, hellobeatles и binaries, создайте главный make-файл, как показано в примере 1.24.
Пример 1.24. Главный make-файл для исходного кода из примеров 1.1, 1.2 и 1.3
# Все цели в этом make-файле — фиктивные
PHONY: all johnpaul georgeringo hellobeatles
# Цель по умолчанию
alclass="underline" hellobeatles
# Цели johnpaul, georgeringo и hellobeatles представляют
# директории, командный сценарий вызывает make в каждой из них
johnpaul georgeringo hellobeatles
$(MAKE) --directory=$@ $(TARGET)
# Это правило указывает что цель по умолчанию make-файла
# в директории hellobeatles зависит от целей по умолчанию
# make-файлов из директорий johnpaul и georgeringo
.PHONY: hellobeatles
hellobeatles: johnpaul georgeringo
Чтобы собрать hellobeatles, перейдите в директорию, содержащую главный make-файл, и введите make
. Чтобы скопировать файлы libjohnpaul.a, libgeorgeringo.so и hellobeatles в директорию binaries, введите make TARGET=install
. Чтобы очистить проект, введите make TARGET=clean
.
Подход к управлению сложными проектами, продемонстрированный в этом рецепте, известен как рекурсивный make (recursive make). Он позволяет организовать проект в виде набора модулей, каждый со своим собственным make-файлом, и указать зависимости между этими модулями. Он не ограничен одним главным make-файлом с набором дочерних make-файлов: эта методика может применяться для обработки многоуровневых древовидных структур. В то время, пока рекурсивный make был стандартной методикой управления большими проектами с помощью make, появились другие методы, которые теперь рассматриваются как более качественные. За подробностями снова обратитесь к Managing Projects with GNU make, Third Edition Роберта Мекленбурга (O'Reilly).