Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

И наоборот, пререквизит, соответствующий существующему файлу, никогда не устаревает при условии, что этот файл не указан в качестве цели одного из правил.

Теперь давайте посмотрим на то, что происходит при выполнении make-файла из примера 1.14. Фиктивная цель

all
всегда устаревшая: единственная ее цель — сказать make собрать hello.exe. В таком простом make-файле нет необходимости в цели
all
, но в более сложных примерах цель
all
может иметь несколько пререквизитов, правило с целью
hello
говорит make собрать, если требуется, hello с помощью g++. Если предположить, что текущая директория не содержит
ничего, кроме файлов makefile и hello.cpp, цель
hello
будет устаревшей. Однако пререквизит не устарел, так как файл hello.cpp существует и так как
hello.cpp
не является целью одного из правил. Следовательно, make для компиляции и компоновки hello.cpp вызывает g++, генерируя тем самым файл hello. Пререквизит цели
all
обновляется, так что make собирает цель
all
 — исполняя пустой командный сценарий — и выходит.

При вызове make с аргументом командной строки, соответствующим цели, make пытается собрать эту цель. Следовательно, выполнение

make install
приводит к выполнению следующих команд:

mkdir -p binaries

cp -p hello binarie
s

Первая команда создает, если она не существует, директорию binaries, а вторая команда копирует в эту директорию файл hello. Точно так же

make clean
вызывает команду

rm -f hello

которая удаляет hello.

При использовании Windows команды
mkdir
,
cp
и
rm
, используемые целями
install
и
clean
, указывают на инструменты GNU, поставляющиеся в составе Cygwin или MSYS

После того как вы поймете, как make анализирует зависимости, пример 1.14 покажется очень простым. Однако на самом деле он значительно сложнее, чем требуется. Рассмотрение различных методов его упрощения является хорошим способом узнать некоторые из основ make-файлов.

Переменные make

GNU make поддерживает переменные, чьими значениями являются строки. Наиболее часто переменные используются в make-файлах как символьные константы. Вместо того чтобы жестко указывать в нескольких местах make-файла имя файла или команды оболочки, вы можете присвоить имя файла или команды переменной и далее использовать эту переменную. Это дает возможность облегчить сопровождение make-файлов. Например, make-файл из примера 1.14 можно переписать с помощью переменных make так, как показано в примере 1.16.

Пример 1.16. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью переменных

# Указываем целевой файл и директорию установки

OUTPUTFILE=hello

INSTALLDIR=binaries

# Цель по умолчанию

.PHONY all

all: $(OUTPUTFILE)

# Собрать hello из hello.cpp

$(OUTPUTFILE): hello cpp

g++ -o hello hello.cpp

#Скопировать hello в поддиректорию binaries

.PHONY: install

install:

mkdir -p $(INSTALLDIR)

cd -p $(OUTPUTFILE) $(INSTALLDIR)

# Удалить hello

.PHONY: clean

clean:

rm -f $(OUTPUTFILE)

Здесь

я ввел две переменные make —
OUTPUTFILE
и
INSTALLDIR
. Как вы можете видеть, значения переменным make присваиваются с помощью оператора присвоения =, и они вычисляются с помощью заключения их в круглые скобки с префиксом в виде знака доллара.

Также установить значение переменной make можно в командной строке с помощью записи make X=Y. Кроме того, при запуске make все переменные среды используются для инициализации переменных make с такими же именами и значениями. Значения, указанные в командной строке, имеют приоритет перед значениями, унаследованными от переменных среды. Значения, указанные в самом make-файле, имеют приоритет перед значениями, указанными в командной строке.

Также GNU make поддерживает автоматические переменные (automatic variables), имеющие специальные значения при выполнении командного сценария. Наиболее важные из них — это переменная

$@
, представляющая имя файла цели, переменная
$<
, представляющая имя файла первого пререквизита, и переменная
$^
,представляющая последовательность пререквизитов, разделенных пробелами. Используя эти переменные, мы можем еще сильнее упростить make-файл из примера 1.16, как показано в примере 1.17.

Пример 1.17. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью автоматических переменных

# Указываем целевой файл и директорию установки

OUTPUTFILE=hellо

INSTALLDIR=binaries

# Цель по умолчанию

.PHONY all

all: $(OUTPUTFILE)

# Собрать hello из hello.cpp

$(OUTPUTFILE) hello.cpp

g++ -o $@ $<

# Цели Install и clean как в примере 1 16

В командном сценарии

g++ -o $@ $<
переменная
$@
раскрывается как
hello
, а переменная
$<
раскрывается как
hello.cpp
. Следовательно, make-файл из примера 1.17 эквивалентен файлу из примера 1.16, но содержит меньше дублирующегося кода.

Неявные правила

make-файл в примере 1.17 может быть еще проще. На самом деле командный сценарий, связанный с целью

hello
, избыточен, что демонстрируется выполнением make-файла из примера 1.18.

Пример 1.18. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью неявных правил

# Указываем целевой файл и директорию установки

OUTPUTFILE=hello

INSTALLDIR=binaries

# Цель по умолчанию

.PHONY: all

all: $(OUTPUTFILE)

# Говорим make пересобрать hello тогда, когда изменяется hello.cpp

$(OUTPUTFILE): hello.cpp

# Цели Install и clean как в примере 1.16

Откуда make знает, как собирать исполняемый файл hello из исходного файла hello.cpp, без явного указания? Ответ состоит в том, что make содержит внутреннюю базу данных неявных правил, представляющих операции, часто выполняемые при сборке приложений, написанных на С и С++. Например, неявное правило для генерации исполняемого файла из файла .cpp выглядит так, как в примере 1.19.

Поделиться с друзьями: