Программирование PC Руководство GNU make: главы 4-6 Sat, February 22 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Руководство GNU make: главы 4-6 Печать
Добавил(а) microsin   

Продолжение перевода руководства GNU make [1].

Примечание: объяснение некоторых специфических терминов make/makefile см. в разделе "Словарик Makefile", в конце статьи [2].

[4. Написание правил]

Правило в makefile говорит, когда и как пересобрать определенные файлы, которые называются целями правила (rule targets, чаще всего это один target-файл на одно правило). В нем перечислены другие файлы, которые являются зависимостями (prerequisites) для целевого объекта (target), и рецепт, используемый для создания или обновления целевого объекта.

Порядок следования правил не важен, кроме определения default goal: это целевой объект по умолчанию (default target), который выберет сама make, если вы не указали что-то другое. Default goal это первая target первого rule в первом makefile. Здесь есть два исключения: целевой объект, начинающийся с точки, не будет целевым объектом по умолчанию, если он также не содержит один или большее количество слешей '/'; и целевой объект, который определяет правило шаблона, не будет default goal (см. 10.5. Определение и переопределение правил шаблона).

Так что мы обычно пишем makefile так, чтобы первое правило в нем было тем, которое компилирует всю программу (программы), описанную в makefile (часто эта цель называется all, и её компиляция запустится командой make без аргументов или командой make all). См. 9.2. Аргументы, указывающие goals.

4.1. Пример правила

Вот пример правила:

foo.o : foo.c defs.h
        cc -c -g foo.c

Здесь целевой объект (target) это файл foo.o, и у него prerequisites файлы foo.c и defs.h. Его рецепт содержит только одну команду: cc -c -g foo.c. Рецепт начинается с символа табуляции, чтобы показать для make, что это строка рецепта.

Это правило говорит 2 вещи:

• Как определить, является ли актуальным файл foo.o: если файл foo.o устаревший (дата модификации у него более старая, чем у foo.c или defs.h), или если foo.o не существует, то его необходимо обновить.
• Как обновить фал foo.o: с помощью запуска компилятора cc, как это указано. Рецепт не имеет явного упоминания файла defs.h, однако мы знаем, что foo.c подключает его, и по этой причине defs.h был добавлен в список prerequisites.

4.2. Синтаксис правила

В общем виде правило выглядит примерно так:

targets : prerequisites
        строка рецепта
        ...

.. или так:

targets : prerequisites ; рецепт
        строка рецепта
        ...

Здесь targets это имена файлов (целевые объекты), отделенные друг от друга пробелами. Могут использоваться символы wildcard (см. 4.4. Использование Wildcard в именах файлов) и имя в форме a(m) представляющее участника m в файле библиотеки a (см. 11.1. Элементы архива в качестве целевых объектов). Обычно используют только одну target на правило, но иногда есть причины использовать большее количество (см. 4.10. Несколько целей в правиле).

Строки рецепта начинаются с символа табуляции (либо с первого символа переменной .RECIPEPREFIX; см. 6.14. Другие специальные переменные). Первая строка рецепта может появляться после списка prerequisites через символ табуляции, или на той же строке через точку с запятой. В любом случае эффект будет один и тот же. Есть и другие отличия в синтаксисе рецепта, см. 5. Написание списков рецептов (recipe) в правилах.

Из-за того, что символ доллара используется для начала ссылки на переменную make, если вы реально хотите использовать символ доллара в целевом объекте или prerequisite, то вы должны его написать дважды: $$ (см. 6. Как использовать переменные в makefile). Если у вас разрешено вторичное расширение (см. 3.9. Вторичное расширение (Secondary Expansion)), и вы хотите также иметь букву доллара в списке prerequisites, то должны написать 4 знака доллара ($$$$).

Вы можете разбить длинную строку вставкой backslash, за которым идет символ новой строки (newline), однако это не требуется, и следует использовать только для удобства чтения, поскольку make не вводит ограничение на длину строки в makefile.

Правило говорит make две вещи: когда цели устарели, и как их при необходимости обновить.

Критерием, по которому определяется устаревание, служит список prerequisites, в котором перечислены имена файлов, разделенные пробелами (здесь также разрешаются wildcards и участники библиотек, archive members. См. 11. Использование make для обновления файлов архива (библиотеки)). Целевой объект считается устаревшим, если он не существует, или его файл более старый, чем любой файл из prerequisites (проверяется путем сравнения меток времени последней модификации файла). Идея состоит в том, что файл целевого объекта вычисляется на основе информации в prerequisites, так что если prerequisites поменялись, то содержимое файла скорее всего недостоверное.

Как обновлять целевой объект, указано в рецепте. Это одна или несколько строк команд, выполняемых шеллом (обычно sh), но с некоторыми дополнительными фичами (см. 5. Написание списков рецептов (recipe) в правилах).

4.3. Типы prerequisites

Существует два типа prerequisites, которые понимает GNU make: обычные (normal prerequisites), описанные в предыдущей секции, и заказные (order-only prerequisites). Normal prerequisite делает два утверждения: во-первых вводит порядок, в котором будут вызываться рецепты: рецепты для всех prerequisites цели будут завершены до того, как будет запущен рецепт цели. Во-вторых, он налагает отношение зависимости: если какая-либо prerequisite является более новой, чем цель, то цель считается устаревшей, и должна быть пересобрана.

Обычно это именно то, что вы хотите: если у целевого объекта обновилось prerequisite, то целевой объект должен быть также обновлен.

Иногда может потребоваться, чтобы prerequisite было собрано перед целевым объектом, но без его принудительного обновления, если prerequisite обновлено. Для создания этого типа отношений используются order-only prerequisites. Order-only prerequisites можно указать, поместив символ пайпа (|) в список prerequisites: любые prerequisites слева от пайпа являются normal prerequisites; любые предпосылки справа являются order-only prerequisites:

targets : normal-prerequisites | order-only-prerequisites

Конечно, часть normal prerequisites может быть пустой. Также вы все еще можете декларировать несколько строк prerequisites для одного и того же целевого объекта: они добавляются соответствующим образом (normal prerequisites добавляются в список normal prerequisites; order-only prerequisites добавляются в список order-only prerequisites). Обратите внимание, что если вы декларируете один и тот же файл и как normal prerequisite, и как order-only prerequisite, то normal prerequisite имеет приоритет (поскольку они имеют строгое дополнение к поведению order-only prerequisite).

Order-only prerequisites никогда не проверяются, когда происходит определение, является ли целевой объект устаревшим; даже order-only prerequisites, помеченные как фальшивые (см. 4.6. Фальшивые цели (Phony Targets)), не приведут к повторной сборке целевого объекта.

Рассмотрим пример, где ваши целевые объекты помещены в отдельную директорию, и эта директория может не существовать перед запуском make. В этой ситуации вы хотите, чтобы директория была создана перед тем, как в неё будут помещены любые целевые объекты. Однако поскольку метки времени на директориях меняются всякий раз, когда в ней добавляется, удаляется или переименовывается файл, мы определенно не хотим пересобирать все целевые объекты всякий раз, когда поменялась метка времени их директории. Один из способов управлять этим - использование order-only prerequisites: сделать директорию order-only prerequisite на всех целевых объектах:

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c $(COMPILE.c) $(OUTPUT_OPTION) $<

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR): mkdir $(OBJDIR)

Теперь правило создания директории objdir (при необходимости) будет запущено перед сборкой любого '.o', однако '.o' не будет собираться из-за изменения метки времени директории objdir.

4.4. Использование Wildcard в именах файлов

С помощью символов wildcard можно сразу указать несколько файлов. Этими символами являются для make '*', ‘?' и '[…]', как и для Bourne shell. Например, *.c указывает список всех файлов (в рабочей директории), у которых имена оканчиваются на '.c'.

Если выражение соответствует нескольким файлам, то результат будет отсортирован(2). Однако несколько выражений не сортируются глобально. Например, *.c *.h создадут список, где сначала идет отсортированный список файлов '.c', и за ним идет отсортированный список файлов '.h'.

Символ '~' в начале имени файла также имеет специальное значение. Если он один, или за ним идет slash (/), то он представляет домашнюю директорию текущего пользователя. Например, ~/bin расширяется до полного пути /home/you/bin. Если за '~' идет слово, то строка представляет домашнюю директорию того пользователя, имя которого совпадает с этим словом. Например, ~john/bin расширяется в /home/john/bin. На системах, где нет директории home для каждого пользователя (таких как MS-DOS или MS-Windows), этот функционал может симулироваться установкой переменной окружения HOME.

Утилита make автоматически выполняет расширение wildcard в целевых объектах и в prerequisites. В рецептах за расширение wildcard отвечает шелл. В других контекстах расширение wildcard происходит только если вы явно запрашиваете это функцией wildcard (см. 4.4.3. Функция wildcard).

Специальное значение символа wildcard может быть выключено путем экранирования его с помощью backslash. Таким образом, foo\*bar будет относиться к специфическому файлу, имя которого состоит из 'foo', символа звездочки и 'bar'.

Примечание (2): некоторые старые версии GNU make не сортируют результаты расширения wildcard.

4.4.1. Примеры wildcard. Символы wildcards могут использоваться в рецепте правила, где они расширяются шеллом. Например, вот правило для удаления всех промежуточных объектных файлов (очистка проекта, запускаемая командой make clean):

clean:
        rm -f *.o

Wildcards также полезны в prerequisites правила. Со следующим правилом в makefile команда make print напечатает все файлы '.c', которые были изменены с момента последнего запуска их печати:

print: *.c
        lpr -p $?
        touch print

Это правило использует файл print как пустой файл целевого объекта; см. 4.8. Пустые target-файлы для записи событий (автоматическая переменная '$?' используется для печати только тех файлов, которые были изменены; см. 10.5.3. Автоматические переменные make).

Расширение wildcard не происходит, когда вы определяете переменную. Таким образом, если вы напишете это:

objects = *.o

.. то значение переменной objects получится реальной строкой '*.o'. Однако если вы используете значение objects в целевом объекте или prerequisite, то там расширение wildcard произойдет. Если вы используете значение objects в рецепте, то расширение wildcard может выполнить шелл, когда запустится рецепт. Для установки objects в расширение используйте вместо этого следующее:

objects := $(wildcard *.o)

См. 4.4.3. Функция wildcard.

4.4.2. Распространенные ошибки в использовании wildcard

Теперь рассмотрим пример наивного использования расширения wildcard, которое приведет совсем не к тому, чего вы хотели добиться. Представим, что вы хотели бы указать, что исполняемый файл foo создается из всех объектных файлов, расположенных в директории проекта, и пишете для этого:

objects = *.o
foo : $(objects) cc -o foo $(CFLAGS) $(objects)

Значение objects будет представлено реальной строкой '*.o'. Расширение wildcard произойдет в правиле для foo, так что каждый существующий файл '.o' станет prerequisite для foo и будет при необходимости перекомпилирован.

Но что если вы удалите все файлы '.o'? Когда wildcard не совпадет ни с одним из файлов, то он останется как есть, так что will будет зависеть только от файла со странным именем *.o. Поскольку скорее всего такой файл не существует, make выдаст ошибку, что не знает, как создать *.o. Это не то, что вы хотели!

Фактически можно получить желаемый результат через расширение wildcard, но вам нужна более продвинутая техника, включающая использование функции wildcard и подстановку строки. См. 4.4.3. Функция wildcard.

Операционные системы Microsoft (MS-DOS и MS-Windows) используют символы обратного слеша (backslash) для отделения друг от друга имен файлов и директорий в путях файловой системы, примерно так:

c:\foo\bar\baz.c

Это эквивалентно Unix-стилю пути c:/foo/bar/baz.c (часть c: это так называемая буква диска). Когда make запускается на этих системах, она поддерживает в именах файлов как символы обратного слеша, так и символы прямого слеша стиля Unix. Однако эта поддержка не включает расширение wildcard, где backslash является символом кавычки. Таким образом, в этих случаях вы должны использовать слеши стиля Unix.

4.4.3. Функция wildcard. В правилах расширение wildcard происходит автоматически. Но расширение wildcard не осуществляется при установке переменной или внутри аргументов функции. Если вы хотите выполнить в этих местах расширение wildcard, то необходимо использовать функцию wildcard, примерно так:

$(wildcard шаблон...)

Эта строка, используемая в любом месте makefile, заменяется списком имен существующих файлов, отделяемых пробелом, которые совпадут с одним из заданных шаблонов имен файлов. Если ни одного совпадения не произошло, то шаблон опускается из вывода функции wildcard. Обратите внимание, что это отличается от того, как не совпавшие wildcard ведут себя в правилах, где они подставляются дословно вместо того, чтобы игнорироваться (см. 4.4.2. Распространенные ошибки в использовании wildcard).

Как с расширением wildcard в правилах, результаты функции wildcard сортируются. И опять-таки, каждое отдельное расширение сортируется по отдельности, так что '$(wildcard *.c *.h)' расширится всеми совпавшими сортированными файлами '.c', за которыми будут идти все совпавшие отсортированные файлы '.h'.

Одно из применений функции wildcard - получить список всех исходных файлов языка C, находящихся в директории проекта, примерно так:

$(wildcard *.c)

Мы можем преобразовать список исходных файлов C результата функции wildcard в список объектных файлов путем замены суффикса '.c' на суффикс '.o', например:

$(patsubst %.c,%.o,$(wildcard *.c))

В этом примере использовалась другая функция patsubst. См. 8.2. Функции для подстановки и анализа строк.

Таким образом, makefile для компиляции всех исходных файлов в директории и затем в линковке их друг с другом мог бы выглядеть следующим образом:

objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects) cc -o foo $(objects)

Здесь используется преимущество неявного правила для компиляции программ на языке C, так что не нужно писать явные правила для компиляции файлов. См. 6.2. Две разновидности (flavor) переменных, для объяснения ':=', что является вариацией '='.

4.5. Директории поиска для prerequisites

Для больших проектов часто желательно помещать исходные файлы отдельно от бинарников и промежуточных файлов. Функционал поиска по директориям make упрощает это путем автоматического просмотра определенных директорий, чтобы найти prerequisite. Когда вы перераспределяете файлы между директориями, нет необходимости менять отдельные правила, надо всего лишь поменять пути поиска.

4.5.1. VPATH: путь поиска для всех prerequisites. Значение make-переменной VPATH указывает список директорий, которые должна просматривать make. Чаще всего ожидается, что это директории, где содержатся файлы prerequisite, которые находятся не только в текущей директории; однако make использует VPATH в качестве списка для поиска как prerequisite, так и целевых объектов для правил.

Таким образом, если файл, перечисленный как целевой объект или prerequisite, не существует в текущей директории, то make просматривает директории, перечисленные в VPATH, чтобы найти файл с таким именем. Если файл был найден в одной из них, то файл может стать prerequisite (см. далее). Затем правила могут указать имена файлов в списке prerequisite как если бы они существовали в текущей директории. См. 4.5.4. Написание рецептов с использованием Directory Search.

В переменной VPATH имена директорий отделяются двоеточиями или пробелами. Порядок, в котором директории перечислены это порядок поиска для make (на MS-DOS и MS-Windows в качестве разделителей имен директорий в VPATH используется символ точки с запятой, поскольку двоеточие используется в самом пути для буквы диска).

Например,

VPATH = src:../headers

.. задает путь, содержащий две директории src и ../headers, которые будут просматриваться при в таком порядке.

С этим значением VPATH следующее правило,

foo.o : foo.c

.. интерпретируется как если бы оно было записано так:

foo.o : src/foo.c

Предположим, что файл foo.c отсутствует в текущей директории, тогда он может находиться в директории src (обычная практика для сложных проектов).

4.5.2. Директива vpath. Подобно переменной VPATH, но более выборочно, директива vpath (обратите внимание на нижний регистр имени директивы) позволит вам задать путь поиска для определенного класса имен файлов: тех, которые совпадут с определенным шаблоном. Так что вы можете предоставить определенные директории поиска для одного класса имен файлов, и другие директории (или никакие) для других имен файлов.

Существует 3 формы директивы vpath:

vpath pattern directories
    Указывает директории поиска для имен файлов, соответствующих шаблону pattern. Путь поиска directories это список имен директорий, где осуществляется поиск, где имена отделены друг от друга двоеточием (или точкой с запятой на MS-DOS и MS-Windows) или пробелом, так же как путь поиска, используемый в переменной VPATH.

vpath pattern
    Очистит путь поиска, связанный с шаблоном pattern.

vpath
    Очистит все пути поиска, установленные ранее директивой vpath.

Шаблон pattern в директиве vpath это строка, содержащая символ '%'. Эта строка должна соответствовать имени файла prerequisite, для которого происходит поиск, а символ '%' соответствует любой последовательности из 0 или большего количества символов (как в правилах шаблонов; см. 10.5. Определение и переопределение правил шаблона). Например, %.h соответствует файлам, имена которых заканчиваются на .h (если символа '%' нет, то шаблон должен точно совпасть с prerequisite, что часто не особо полезно).

Символы '%' в директиве vpath могут быть экранированы предшествующими символами backslash (\). Символы backslash, которые не экранируют символы '%', могут быть экранированы дополнительным backslash. Символы backslash, которые экранируют символы '%' или другие backslash, удаляются из pattern перед сравнением с именами файлов. Символы backslash, которым не угрожает экранирование символов '%', остаются неизменными.

Когда prerequisite не найден в текущей директории, если pattern в директиве vpath совпадет с именем файла prerequisite, то директории в этой директиве просматриваются так же, как (и до) директории в переменной VPATH.

Например,

vpath %.h ../headers

.. говорит make просматривать любые prerequisite, имена у которых заканчиваются на .h, в директории ../headers, если файл prerequisite не найден в текущей директории.

Если несколько шаблонов vpath соответствуют имени файла prerequisite, то make обрабатывает каждую директиву vpath одну за одной, осуществляя поиск по директориям, упомянутым в каждой директиве. Утилита make обработает несколько директив vpath в том порядке, в каком они были указаны makefile; несколько директив с одинаковым pattern не зависят друг от друга.

Таким образом,

vpath %.c foo
vpath % blish
vpath %.c bar

.. приведет к поиску файла, оканчивающегося на '.c', в папке foo, затем в blish, затем в bar, в то время как

vpath %.c foo:barvpath %   blish

.. приведет к поиску файла, оканчивающегося на '.c' в foo, затем в bar, затем в blish.

4.5.3. Как происходит поиск по директориям. Когда prerequisite найден через через directory search, независимо от типа (общий или селективный), найденный путь может не соответствовать, которое фактически вы предоставили в списке prerequisite. Иногда путь, обнаруженный через directory search, выбрасывается.

Алгоритм, который используется для принятия решения о сохранении или отказе от пути, найденного с помощью directory search, работает следующим образом:

1. Если целевой файл не существует по пути, указанном в makefile, то выполняется directory search.

2. Если directory search успешен, то сохраняется найденный путь, и этот путь предварительно сохраняется как целевой объект.

3. Все prerequisites этого целевого объекта проверяются с помощью того же самого метода.

4. После обработки всех prerequisites, целевой объект может или не может потребовать пересборки:

   - Если целевой объект пересобирать не нужно, то путь, найденный во время directory search, используется для любого списка prerequisite, содержащего этот целевой объект. Короче, если make не нужно пересобирать целевой объект, то в используете путь, найденный через directory search.
   - Если целевой объект нуждается в пересборке (устарела), то путь, найденный через directory search, отбрасывается, и целевой объект пересобирается с использованием имени файла, указанного в makefile. Короче, если make должна выполнить пересборку, то целевой объект пересобирается локально, не в директории, найденной через directory search.

Этот алгоритм может выглядеть несколько сложным, однако на практике это часто почти именно то, что вы хотите.

Другие версии make используют более простой алгоритм: если файл не существует, и был найден через directory search, то это имя пути используется всегда, независимо от того, нужно ли пересобирать целевой объект. Таким образом, если целевой объект пересобирается, то её файл создается по пути, обнаруженному через directory search.

Если по факту такое поведение это то, что вам нужно для поиска в некоторой вашей директории или всех ваших директориях, вы можете использовать переменную GPATH, чтобы показать это для make.

У переменной GPATH такой же синтаксис и формат, что и у VPATH (т. е. в качестве разделителя используется пробел или двоеточие). Если устаревший целевой объект найден через directory search в директории, также указанной в GPATH, то этот путь не отбрасывается. Файл целевой объект пересоберется с использованием расширенного пути.

4.5.4. Написание рецептов с использованием Directory Search. Когда prerequisite найден в другой директории через directory search, это не может поменять рецепт правила; оно будет запущено так, как записано. Таким образом, вы должны писать рецепт с осторожностью, чтобы prerequisite бралось из директории, где утилита make его нашла.

Это осуществляется через автоматические переменные, такие как '$^' (см. 10.5.3. Автоматические переменные make). Например, значение '$^' это список всех prerequisites правила, включая имена директорий, в которых они найдены, а значение '$@' это целевой объект. Таким образом:

foo.o : foo.c
        cc -c $(CFLAGS) $^ -o $@

Примечание: переменная CFLAGS существует, поэтому вы можете указать флаги для компиляции C-файлов с помощью неявных правил; мы используем его здесь для согласованности, поэтому он будет одинаково влиять на все компиляции C; см. 10.3. Переменные, используемые неявными правилами.

Часто prerequisites включают также и заголовочные файлы, которые вы не хотите упоминать в рецепте. Автоматическая переменная $< это первый prerequisite:

VPATH = src:../headers
foo.o : foo.c defs.h hack.h
        cc -c $(CFLAGS) $< -o $@

4.5.5. Поиск по директориям и неявные правила. Поиск по директориям, заданный в VPATH или директивой vpath (см. выше) также происходит при рассмотрении неявных правил (см. 10. Использование неявных правил).

Например, когда у файла foo.o нет явного правила (explicit rule), утилита make рассматривает неявные правила (implicit rules), такие как встроенное правило для компиляции foo.c, если этот файл существует. Если такой файл отсутствует в текущей директории, то он ищется в подходящих директориях. Если foo.c существует (или упомянут в makefile) в любой из этих директорий, то применяется неявное правило для компиляции кода языка C.

Рецепты неявных правил нормально используют автоматические переменные как по мере необходимости; следовательно, они будут без дополнительных усилий использовать имена файлов, найденные в процессе directory search.

4.5.6. Поиск по директориям для линковки библиотек. Directory search применяется специальным образом к библиотекам, используемым линкером. Эта специальная фича вступает в игру, когда вы пишете prerequisite с именем в форме -lname (вы можете сказать, что здесь происходит нечто странное, потому что prerequisite это обычно имя файла, а имя библиотеки обычно выглядит наподобие libname.a, но не '-lname').

Когда у prerequisite имя в форме '-lname', утилита make обрабатывает это специальным образом путем поиска в текущей директории файла libname.so, и если он не найден, то файла libname.a. Если в текущей директории поиск потерпел неудачу, то происходит поиск по директориям, соответствующим указанным через vpath и VPATH путям поиска, и затем в директориях /lib, /usr/lib и prefix/lib (обычно /usr/local/lib, однако MS-DOS/MS-Windows версии make ведут себя как если префикс был определен как корень дерева каталога инсталляции DJGPP [10]).

Например, если в вашей системе есть библиотека /usr/lib/libcurses.a (и нет файла /usr/lib/libcurses.so), то:

foo : foo.c -lcurses
        cc $^ -o $@

.. приведет к команде 'cc foo.c /usr/lib/libcurses.a -o foo', выполненной в случае, если foo старее, чем foo.c или чем /usr/lib/libcurses.a.

Хотя по умолчанию искомые файлы это имена наподобие libname.so и libname.a, это настраивается через переменную .LIBPATTERNS. Каждое слово в значении этой переменной является строкой шаблона. Когда make видит prerequisite наподобие '-lname', она заменит символ процента в каждом шаблоне из списка и выполнит описанный выше directory search, используя каждое имя файла библиотеки.

Значение по умолчанию для .LIBPATTERNS это 'lib%.so lib%.a', что обеспечивает вышеописанное поведение по умолчанию.

Вы можете полностью выключить расширение линковки библиотеки путем установки этой переменной в пустое значение.

4.6. Фальшивые цели (Phony Targets)

Phony target это такая цель, которая на самом деле не имя файла; вместо этого цель просто имя для выполняемого рецепта, когда вы делаете для make явный запрос. Есть две причины существования phony target: во-первых, чтобы избежать конфликта с именем файла, у которого имя совпадает с phony target, и во-вторых для улучшения производительности.

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

clean:
        rm *.o temp

Поскольку команда rm не создает файл с именем clean, то скорее всего такой файл не существует никогда. Таким образом, команда rm запустится каждый раз, когда вы выполняете команду 'make clean'.

В этом примере цель clean не будет работать правильно, если файл с именем clean создан в этой директории. Поскольку здесь нет prerequisites, файл clean будет всегда считаться обновленным, и рецепт не запустится. Чтобы избежать этой проблемы, вы можете явно декларировать целевой объект как фальшивый (phony) путем задания его как prerequisite специального целевого объекта .PHONY (см. 4.9. Специальные встроенные имена целей) следующим образом:

.PHONY: clean
clean: rm *.o temp

Когда это сделано, команда 'make clean' запустит рецепт целевого объекта clean независим от существования файла с именем clean.

Prerequisites от .PHONY всегда интерпретируется как литеральные имена целей, и никогда как шаблоны имен (даже если содержат символы '%'). Чтобы всегда пересобирать правило шаблона, рассмотрите использование "force target" (см. 4.7. Правила без рецептов или prerequisites).

Фальшивые цели также полезны в соединении с рекурсивными вызовами make (см. 5.7. Рекурсивное использование make). В такой ситуации makefile часто будет содержать переменную, в которой содержится список нескольких подкаталогов для сборки. Самый простой способ обработки этого - определение одного правила с рецептом, где содержится цикл по подкаталогам, примерно так:

SUBDIRS = foo bar baz

subdirs: for dir in $(SUBDIRS); do \ $(MAKE) -C $$dir; \ done

Однако с этим методом есть проблемы. Во-первых, любая ошибка, произошедшая в одном из рекурсивных вызовов make, этим правилом игнорируется, так что процесс сборки продолжится по остальным перечисленным подкаталогам, даже когда в одном из подкаталогов произошел сбой. Это исправить добавлением команд шелла, чтобы обозначить ошибку и произвести выход, но тогда это будет происходить даже тогда, когда make запущена с опцией -k, чего не хотелось бы. Во-вторых, и что возможно важнее, вы не можете использовать преимущества параллельной сборки (parallel build targets, см. 5.4. Параллельное выполнение), поскольку здесь только одно правило. Каждый makefile по отдельности будет запускаться в параллельном режиме, но будет собирать каждый раз только один подкаталог.

Декларированием подкаталогов как .PHONY целей (вы должны сделать это, поскольку очевидно что каждый их этих подкаталогов существует; иначе он не будет собран) вы можете решить эти проблемы:

SUBDIRS = foo bar baz

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS): $(MAKE) -C $@

foo: baz

Здесь мы также декларируем, что подкаталог foo не сможет быть собран, пока не будет собран подкаталог baz; этот вид декларации отношений в частности важен, когда делается попытка параллельных сборок.

Неявное правило поиска (см. 10. Использование неявных правил) пропускается для .PHONY-целей. Это причина, по которой декларация цели как .PHONY полезна для улучшения производительности, даже если вы не беспокоитесь о существовании реального файла.

Цель phony target не должна быть prerequisite реального целевого файла; если это выполняется, то его рецепт запустится каждый раз, когда make рассматривает этот файл. До тех пор, пока phony target никогда не является prerequisite реального целевого объекта, рецепт для phony target будет выполняться только тогда, когда phony target является указанной целью goal (см. 9.2. Аргументы, указывающие goals).

Вы не должны декларировать подключаемый makefile как phony. Phony targets не предназначены представлять реальные файлы, и поскольку целевой объект всегда считается устаревшим, make всегда будет заново себя перезапускать (см. 3.5. Как заново создаются файлы Makefile). Чтобы этого избежать, утилита make не будет запускать саму себя, если пересобирается подключенный файл, помеченный как phony.

Phony targets могут иметь prerequisites. Когда одна директория содержит несколько программ, часто удобно описать все программы в одном makefile-файле ./Makefile. Поскольку переделываемый по умолчанию целевой объект первый в makefile, обычно это делается через phony target с именем 'all' и этому целевому объекту в качестве prerequisites предоставляются все отдельные программы. Например:

all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o cc -o prog1 prog1.o utils.o
prog2 : prog2.o cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o cc -o prog3 prog3.o sort.o utils.o

Теперь вы можете просто запустить 'make', чтобы пересобрать все три программы, или указать в качестве аргументов только те, которые должны быть пересобраны (например 'make prog1 prog3'). Фальшивость не наследуется: prerequisites для phony target не являются сами по себе phony, если для них это не декларировано явно.

Когда одна phony target является prerequisite другого целевого объекта, она служит подпрограммой для другого целевого объекта. Например, здесь 'make cleanall' удалит объектные файлы, diff-файлы и файл program:

.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff rm program
cleanobj : rm *.o
cleandiff : rm *.diff

4.7. Правила без рецептов или prerequisites

Если у правила нет prerequisites или рецепта, и целевой объект у правила несуществующий файл, то make представляет этот целевой объект так, что она должна быть обновлена каждый раз, когда её правило запускается. Это подразумевает, что у всех целевых объектов, зависящих от от него, будут запускаться их рецепт.

Это иллюстрирует пример:

clean: FORCE
        rm $(objects)
FORCE:

Здесь цель FORCE удовлетворяет специальным условиям, так что зависящая от неё цель clean принудительно запустит свой рецепт. В имени 'FORCE' нет ничего специального, однако это обычно используемое имя для такого случая.

Как вы можете видеть, использование таким способом 'FORCE' дает те результаты, что и использование '.PHONY: clean'.

Использование '.PHONY' более наглядно и более эффективно. Однако другие версии make не поддерживают '.PHONY'; поэтому 'FORCE' встречается во многих файлах makefile. См. 4.6. Фальшивые цели (Phony Targets).

4.8. Пустые target-файлы для записи событий

Пустая цель (empty target) это вариант фальшивой цели (phony target); она используется для хранения рецептов действий, которые вы время от времени запрашиваете. В отличие фальшивой цели этот файл пустой цели может реально существовать; однако содержимое файла не имеет значения, и он обычно пустой.

Назначение файла empty target - записать в его метке время последней модификации файла, когда рецепт правила выполнялось последний раз. Это происходит, потому что одна из команд в рецепте это команда touch, предназначенная для обновления файла empty target.

Файл empty target должен иметь какие-нибудь prerequisites (иначе это не имеет смысла). Когда вы запрашиваете пересборку empty target, рецепт выполняется, если любой из файлов prerequisite более свежий, чем файл empty target; другими словами, если prerequisite поменялся с момента последней пересборки целевого объекта. Вот пример:

print: foo.c bar.c
        lpr -p $?
        touch print

С этим правилом команда 'make print' запустит команду lpr, если любой из исходных файлов (prerequisite-файлы foo.c и bar.c) были изменены с момента последней предыдущей команды 'make print'. Автоматическая переменная '$?' используется для печати только тех файлов, которые были изменены (см. 10.5.3. Автоматические переменные make).

4.9. Специальные встроенные имена целей

Некоторые имена обладают специальным значением, если они появляются в Makefile как цели компиляции (targets).

.PHONY

Prerequisites, необходимые файлы (см. "Словарик") специального целевого объекта .PHONY рассматриваются как фиктивные, не файловые цели (phony targets). Когда пришло время рассмотреть такой целевой объект, утилита make будет выполнять свой рецепт безоговорочно, независимо от того, существует ли файл с этим именем, или каково его время последней модификации. См. 4.6. Фальшивые цели (Phony Targets).

.SUFFIXES

Prerequisites специального целевого объекта .SUFFIXES представляют собой список суффиксов, используемые в проверках правил суффиксов. См. 10.7. Старомодные правила суффиксов.

.DEFAULT

Prerequisite, указанное для .DEFAULT, используется для любого целевого объекта, для которого не найдены правила (явные или неявные), см. 10.6. Определение правил по умолчанию для последней инстанции. Если указан рецепт .DEFAULT, то каждый файл, упомянутый как prerequisite, но не как целевой объект в правиле, будет иметь этот рецепт, выполняемый от его имени. См. 10.8. Алгоритм поиска неявного правила.

.PRECIOUS

Целевые объекты, от которых зависит .PRECIOUS, получают следующий специальный режим: если make прибит или прерван во время выполнения его рецептов, то целевой файл не удаляется. См. 5.6. Прерывание, или аварийное завершение работы make. Также, если целевой объект это промежуточный файл, то он не будет удален после того, как больше не будет нужен, как это обычно делалось. См. 10.4. Цепочки неявных правил. В этом отношении он перекрывает специальный целевой объект .SECONDARY.

Можно также указать целевой шаблон неявного правила (такого как '% .o') в качестве prerequisite специального целевого файла .PRECIOUS для сохранения промежуточных файлов, созданных правилами, целевые шаблоны которых соответствуют имени этого файла.

.INTERMEDIATE

Целевые объекты, от которых зависит .INTERMEDIATE, рассматриваются как промежуточные файлы. См. 10.4. Цепочки неявных правил. .INTERMEDIATE без prerequisites не имеет никакого эффекта.

.NOTINTERMEDIATE

Prerequisites специального целевого объекта .NOTINTERMEDIATE никогда не рассматриваются как промежуточные файлы. См. 10.4. Цепочки неявных правил. .NOTINTERMEDIATE без prerequisites приводит к тому, что все целевые объекты обрабатываются как не промежуточные.

Если prerequisite это шаблон целевого объекта, то целевые объекты, построенные с использованием этого правила шаблона, не считаются промежуточными.

.SECONDARY

Целевые объекты, от которых зависит .SECONDARY, рассматриваются как промежуточные файлы за исключением того, что они никогда не удаляются автоматически. См. 10.4. Цепочки неявных правил.

.SECONDARY может использоваться, чтобы избежать избыточных перестроений в некоторых необычных ситуациях. Например:

hello.bin: hello.o bye.o
        $(CC) -o $@ $^

%.o: %.c $(CC) -c -o $@ $<

.SECONDARY: hello.o bye.o

Предположим, что файл hello.bin обновлен по отношению к файлам исходного кода, однако объектный файл hello.o отсутствует. Без .SECONDARY make пересоберет hello.o, затем пересоберет hello.bin, даже если файлы исходного кода не изменились. Объявляя hello.o как .SECONDARY, не нужно будет пересобирать его, и не нужно будет пересобирать hello.bin. Конечно, если один из файлов исходного кода обновился, все объектные файлы были бы пересобраны так, чтобы мог быть успешно собран hello.bin.

.SECONDARY без prerequisites приведет к тому, что все целевые объекты обрабатываются как secondary (т. е. целевой объект не удаляется, потому что считается промежуточным).

.SECONDEXPANSION

Если в любом месте makefile упомянуто .SECONDEXPANSION как целевой объект, то все списки prerequisite, определенные после него, будут расширены второй раз после прочтения всех makefile. См. 3.9. Вторичное расширение (Secondary Expansion).

.DELETE_ON_ERROR

Если в любом месте makefile упомянуто .DELETE_ON_ERROR как целевой объект, то make удалит целевой объект правила, если он был изменен, и его рецепт совершил выход с ненулевым статусом, как и при получении сигнала. См. 5.5. Ошибки в списках команд рецепта.

.IGNORE

Если вы указали prerequisites для .IGNORE, то make будет игнорировать ошибки в выполнении рецепта для этих определенных файлов. Рецепт для .IGNORE (если это имеет место) игнорируется.

Если .IGNORE используется как целевой объект без prerequisites, то .IGNORE говорит игнорировать ошибки в выполнении рецептов для всех файлов. Это использование .IGNORE поддерживается только для исторической совместимости. Поскольку это влияет на каждый рецепт в makefile, то не слишком полезно; рекомендуется использовать более выборочные способы игнорировать ошибки в определенных рецептах. См. [5].

.LOW_RESOLUTION_TIME

Если вы указываете prerequisites для .LOW_RESOLUTION_TIME, то make подразумевает, что эти файлы созданы командами, которые генерируют метки времени низкого разрешения. Рецепт для целевого объекта .LOW_RESOLUTION_TIME игнорируется.

Метки времени высокого разрешения многих современных файловых систем уменьшают вероятность ошибочного вывода о том, что файл обновлен. К сожалению, некоторые хосты не предоставляют способа установить метку времени высокого разрешения, так что команды наподобие cp -p, явно устанавливающие метку времени файла, должен отбрасывать его субсекундную часть. Если файл был создан такой командой, следует перечислить его как prerequisite .LOW_RESOLUTION_TIME, чтобы make ошибочно не заключил, что файл устарел. Для примера:

.LOW_RESOLUTION_TIME: dst
dst: src cp -p src dst

Поскольку cp -p отбрасывает субсекундную часть метки времени, то место назначения для копирования оказывается более старым, чем источник, даже когда файл обновлен. Строка .LOW_RESOLUTION_TIME приводит к тому, что make считает файл назначения обновленным, если его метка времени находится в начале той же секунды, в которой находится метка времени источника.

Из-за ограничений формата архива, файлы в архиве содержат метки низкого разрешения. Вам не нужно перечислять файлы архива как prerequisites .LOW_RESOLUTION_TIME, поскольку make делает это автоматически.

.SILENT

Если вы укажете prerequisites для .SILENT, то make не напечатает рецепт, используемый для перестроения этих определенных файлов перед их выполнением. Рецепт для .SILENT игнорируется.

Если .SILENT упоминалось как целевой объект без prerequisites, то оно говорит не печатать никакие рецепты перед их выполнением. Вы можете также использовать более избирательные пути подавить вывод определенных командных строк рецепта. См. 5.2. Эхо в рецепте. Если вы хотите подавить вывод все рецептов для определенного запуска make, то используйте опцию -s или --silent (см. 9.8. Сводка по опциям make).

.EXPORT_ALL_VARIABLES

При простом упоминании как целевой объект: это говорит о том, что make по умолчанию экспортирует все переменные в дочерние процессы. Это альтернатива использованию export без аргументов. См. 5.7.2. Передача переменных в sub-make.

.NOTPARALLEL

Если .NOTPARALLEL упоминается как целевой объект без prerequisites, то все целевые объекты в этом вызове make запустятся последовательно, даже если указана опция -j. Любая рекурсивно вызванная команда make все еще будет запускать рецепты параллельно (за исключением, если их makefile также содержит этот целевой объект).

Если .NOTPARALLEL имеет целевые объекты в качестве prerequisites, то все prerequisites этих целей будут запущены последовательно. Это неявно добавит .WAIT между каждым prerequisite перечисленных целевых объектов. См. 5.4.1. Отключение параллельного выполнения.

.ONESHELL

Если .ONESHELL упоминается как целевой объект, то при построении целевого объекта все строки рецепта будут переданы одному вызову shell-а, а не каждой строке, вызываемой отдельно. См. 5.3. Выполнение рецепта.

.POSIX

Если .POSIX упоминается как целевой объект, то makefile будет парситься и запускаться в POSIX-совместимом режиме. Это не означает, что будут приниматься только POSIX-совместимые makefile: все расширенные возможности GNU make все еще остаются доступными. Скорее, этот целевой объект вызывает поведение make в соответствии с требованиями POSIX в тех областях, где поведение make по умолчанию отличается.

В частности, если упоминается этот целевой объект, то рецепты будут вызываться так, как если бы оболочке был передан флаг -e: первая неудачная команда в рецепте немедленно приведет к отказу рецепта.

Любой определенный неявный суффикс правила также считается специальным целевым объектом, если он появляется как целевой объект, а также конкатенация двух суффиксов, таких как '.c.o'. Эти целевые объекты являются суффиксными правилами, устаревшим способом определения неявных правил (но все еще широко используемым способом). В принципе, любое целевое имя может быть особым, если разбить его на две части и добавить обе части в список суффиксов. На практике суффиксы обычно начинаются с '.', поэтому эти специальные целевые имена также начинаются с '.'. См. 10.7. Старомодные правила суффиксов.

4.10. Несколько целей в правиле

Когда у явного правила есть несколько целей, они могут обрабатываться двумя возможными способами: как независимые цели или как группированные цели. Способ их обработки определяется сепаратором, который появляется после списка целей.

Правила с независимыми целями. Правила, которые используют стандартный разделитель цели :, определяет независимые цели. Это эквивалентно написанию по одному одинаковому правилу для каждой цели, с дублированием prerequisites и рецептов. Как правило, рецепт будет использовать автоматические переменные, такие как '$@', чтобы указать, какая цель строится.

Правила с независимыми целями полезны в двух случаях:

1. Вам нужны только prerequisites, без рецепта. Например:

kbd.o command.o files.o: command.h

.. дает дополнительный prerequisite (в этом примере command.h) для каждого из трех перечисленных объектных файлов kbd.o, command.o и files.o. Это эквивалентно написанию:

kbd.o: command.h
command.o: command.h
files.o: command.h

2. Одинаковые рецепты работают для всех объектов. Может использоваться автоматическая переменная '$@' для подстановки в команды рецепта определенной перестраиваемой цели (см. 10.5.3. Автоматические переменные make). Например:

bigoutput littleoutput : text.g
        generate text.g -$(subst output,,$@) > $@

.. эквивалентно следующему:

bigoutput : text.g
        generate text.g -big > bigoutput
littleoutput : text.g
        generate text.g -little > littleoutput

Здесь гипотетически подразумевается программа generate, которая создает два типа вывода, один если указана опция '-big', а другой если '-little'. Описание работы функции subst см. в разделе 8.2. Функции для подстановки и анализа строк.

Предположим, что вы хотите изменить prerequisites в соответствии с целевым объектом, так как переменная '$@' позволяет вам менять рецепт. Вы не можете сделать это с несколькими целевыми объектами в обычном правиле, однако можете с использованием статического правила шаблона. См. 4.12. Правила статического шаблона.

Правила сгруппированных целей. Если вместо независимых целей у вас есть рецепт, который генерирует несколько файлов в одном запуске, то вы можете выразить такое взаимодействия декларированием правила для использования сгруппированных целей. Правило группированных целей (grouped target rule) использует разделитель &: (здесь '&' используется, чтобы подразумевать "all").

Когда утилита make строит любую из сгруппированных целей, она понимает, что все другие цели в группе также обновляются в результате вызова рецепта. Кроме того, если только некоторые из сгруппированных целей устарели или отсутствуют, то make поймет, что запуск рецепта обновит все цели. Наконец, если какая-либо из сгруппированных целей устарела, все сгруппированные цели считаются устаревшими.

Пример правила, определяющего сгруппированную цель:

foo bar biz &: baz boz
        echo $^ > foo
        echo $^ > bar
        echo $^ > biz

При выполнении рецепта сгруппированной цели автоматическая переменная '$@' установится в имя определенной цели в группе, для которой сработало правило. Следует соблюдать осторожность при использовании этой переменной в рецепте правила сгруппированных целей.

В отличие от правила независимых целей, правило сгруппированных целей должно включать рецепт. Однако цели, являющиеся членами сгруппированной цели, могут также появляться в независимых определениях правил цели, не имеющих рецепта.

Каждая цель может иметь только один связанный с ним рецепт. Если сгруппированная цель появляется либо в правиле независимых целей, либо в другом правиле сгруппированных целей, то вы получите предупреждение, и последний рецепт заменит предыдущий рецепт. Кроме того, цель будет удалена из предыдущей группы и появится только в новой группе.

Если вы хотите, чтобы цель появлялась в нескольких группах, то вы должны использовать разделитель группированной цели с двумя двоеточиями &::, когда декларируете все группы, содержащие эту цель. Каждая цель, сгруппированная &::, считается независимой, и каждый сгруппированный рецепт правила с двумя двоеточиями выполнится максимум один раз, если хотя бы одна из его нескольких целей требует обновления.

4.11. Несколько правил для одного target-файла

Один файл может быть целью нескольких правил. Все prerequisites, упомянутые во всех правилах, сливаются в один список prerequisites для цели. Если цель более старая, чем любой prerequisite из любого правила, то рецепт выполняется.

Здесь может быть только один выполняемый рецепт для файла. Если больше одного правила предоставляют рецепт для одного и того же файла, то make использует последний из предоставленных, и печатает сообщение об ошибке (в качестве специального случая, если имя файла начинается с точки, то никакое сообщение об ошибке не печатается. Это странное поведение оставлено только для совместимости с другими реализациями make... вам следует избегать подобного использования). Иногда полезно, чтобы одна и та же цель вызывала несколько рецептов, которые определены в разных частях вашего makefile; вы можете использовать для этого правила двойного двоеточия (см. 4.13. Правила двойного двоеточия).

Может использоваться дополнительное правило, где есть только prerequisites, чтобы предоставить дополнительные prerequisites для нескольких файлов одновременно. Например, в makefiles часто есть переменная, такая как objects, содержащая список всех выходных файлов компилятора в создаваемой системе. Простой способ указать, что все они должны быть перекомпилированы, если поменялся config.h, состоит в написании следующего:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

Это можно вставить или удалить без измененеия правил, где реально указано, как скомпилировать объектные файлы, что дает удобную форму для использования, если вы хотите в будущем периодически добавлять дополнительные prerequisite.

Другая проблема заключается в том, что дополнительные prerequisites могут быть заданы с помощью переменной, которую вы установите в аргументе командной строки make (см. 9.5. Переназначение переменных). Например,

extradeps=
$(objects) : $(extradeps)

.. означает, что команда 'make extradeps=foo.h' будет рассматривать foo.h в качестве prerequisite каждого объектного файла, однако простая команда 'make' это делать не будет.

Если ни одно из явных правил для цели не имеет рецепта, то make выполнит поиск применимого неявного правила (см. 10. Использование неявных правил).

4.12. Правила статического шаблона

Правила статического шаблона (static pattern rules) это правила, которые указывают несколько целей, и конструируют имена prerequisite для каждой цели на основе имени цели. Они являются более общими, чем обычные правила с несколькими целями, потому что цели не имеют идентичных prerequisites. Из prerequisites должны быть аналогичными, но не обязательно одинаковыми.

4.12.1. Синтаксис правил статического шаблона. Вот синтаксис для static pattern rule:

targets ...: target-pattern: prereq-patterns ...
        строка рецепта
        ...

Список целей targets указывает цели, к которым применяется правило. Здесь targets может содержать символы wildcard, точно так же, как целевые объекты обычных правил (см. 4.4. Использование Wildcard в именах файлов).

Шаблон цели target-pattern и шаблоны предпосылок prereq-patterns говорят, как вычислить prerequisites для каждой цели. Каждая цель сопоставляется с целевой шаблон (target-pattern) для извлечения части имени цели, которая называется основой (stem). Этот stem подставляется в каждый шаблон prereq-patterns, чтобы создать имена prerequisite (по одному для каждого шаблона prereq-patterns).

Каждый шаблон обычно содержит только один символ '%'. Когда целевой шаблон совпал с целью, '%' может соответствовать любой части имени цели; эта часть также называется основой (stem). Остальная часть шаблона должна совпасть буквально. Например, целевой объект foo.o совпадет с шаблоном '%.o', где 'foo' служит основой. Целевые объекты foo.c и foo.out не совпадут с этим шаблоном.

Имена prerequisite для каждой цели делаются путем подстановки основы для '%' в каждом шаблоне prereq-patterns. Например, если один шаблон prereq-patterns это %.c, то замена основой 'foo' даст prerequisite-имя foo.c. Законно писать шаблон prerequisite, не содержащий '%'; тогда это prerequisite становится одинаковым для всех целей.

Символы '%' вы правилах шаблона могут быть экранированы стоящим впереди символом обратного слеша '\'. Обратные слеши, которые иначе экранировали бы символы '%', могут быть сами экранированы большим количеством слешей. Обратные слеши, которые экранируют символы '%' или другие обратные слеши, удаляются из шаблона перед сравнением с именами файлов или подстановкой в него основы. Обратные слеши, которые не угрожают экранированием символов '%', остаются неизменными. Например, шаблон the\%weird\\%pattern\\ имеет 'the%weird\', предшествующее оперативному символу '%', и последующее 'pattern\\'. Последние два обратных слеша остаются, потому что они не влияют ни на один символ '%'.

Вот пример, который компилирует каждый foo.o и bar.o из соответствующего файла .c:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@

Здесь $< это автоматическая переменная, которая содержит имя prerequisite, а '$@' это автоматическая переменная, которая содержит имя цели; см. 10.5.3. Автоматические переменные make.

Каждый указанный целевой объект (target) должен соответствовать шаблону целевого объекта (target pattern); будет выдано предупреждение для каждой цели, которая не имеет соответствия. Если у вас есть список файлов, из которых только некоторые совпали с шаблоном, то вы можете использовать функцию filter, чтобы удалить не совпавшие имена файлов (см. 8.2. Функции для подстановки и анализа строк):

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el emacs -f batch-byte-compile $<

В этом примере результатом '$(filter %.o,$(files))' будет bar.o lose.o, и первое правило статического шаблона приведет к обновлению каждого из этих объектных файлов путем компилирования соответствующего исходного файла C. Результатом '$(filter %.elc,$(files))' будет foo.elc, так что этот файл будет сделан из файла foo.el.

Другой пример показывает, как использовать $* в правилах статического шаблона:

bigoutput littleoutput : %output : text.g
        generate text.g -$* > $@

Когда запустится команда generate, $* будет расширена в основу, либо в 'big', либо в 'little'.

4.12.2. Отличие правил статического шаблона от неявных правил. Правило статического шаблона имеет много общего с неявным правилом, определенным как правило шаблона (см. 10.5. Определение и переопределение правил шаблона). Оба они имеют шаблон для цели, и шаблоны для конструирования имен prerequisites. Отличие в том, как make решает, когда применяется правило.

Неявное правило может применяться к любой цели, которая совпала с его шаблоном, но одно применяется только тогда, когда цель не имеет рецепта, указанного иным способом, и только когда могут быть найдены prerequisites. Если присутствуют несколько применимых неявных правил то применяется только одно; выбор зависит от порядка следования правил.

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

Правило статического шаблона может быть лучше, чем неявное правило, по следующим причинам:

• Вы можете переопределить обычное неявное правило для нескольких файлов, имена которых не может быть классифицированы синтаксически, но вы можете предоставить явный список.

• Если вы не можете быть уверены в точном содержании использумых директорий, то вы не можете быть уверены в том, какие другие неактуальные файлы могут привести к использованию неправильного неявного правила. Выбор может зависеть от порядка, в котором осуществляется поиск неявного правила. С правилами статического шаблона нет никакой неопределенности: каждое правило применяется точно к указанным целям.

4.13. Правила двойного двоеточия

Эти правила пишутся с двумя символами двоеточия '::' вместо ':' после имен целей, и они являются явными правилами. Правила двойного двоеточия обрабатываются не так, как обычные правила, когда одна и та же цель появляется более чем в одном правиле. Правила шаблона с двойным двоеточием имеют полностью другой смысл (см. 10.5.5. Правила шаблона любого совпадения).

Когда цель появляется в нескольких правилах, все правила должны быть одного и того же типа: все обычные, или все двойного двоеточия. Если они правила двойного двоеточия, то каждое из них не зависит от других. Рецепт каждого правила двойного двоеточия выполняется, если цель старше, чем любое prerequisite этого правила. Если для этого правила нет prerequisites, его рецепт выполнится в любом случае (даже если цель уже существует). Это может привести к выполнению ни одного, любого или всех правил двойного двоеточия.

Правила двойного двоеточия с одинаковой целью по факту полностью отделены друг от друга. Каждое правило двойного двоеточия обрабатывается индивидуально, точно как если бы у правил были разные цели.

Правило двойного двоеточия для цели выполняется в том порядке, в каком они появляются в makefile. Однако случаи, когда правила двойной двоеточия действительно имеют смысл - это те, где порядок выполнения рецептов не имеет значения.

Правила двойного двоеточия несколько сложны для понимания и не часто полезны; они предоставляют механизм для случаев, в которых метод, используемый для обновления цели, отличается в зависимости от того, какие prerequisite-файлы привели к обновлению, и такие случаи редки.

Каждое правило двойного двоеточия должно указать рецепт; иначе то будет использоваться неявное правило, если оно применимо. См. 10. Использование неявных правил.

4.14. Автоматическая генерация prerequisites

В makefile для программы многие правила, которые нужно написать, часто говорят только о том, что какой-то объектный файл зависит от какого-то заголовочного файла. Например, если main.c использует defs.h через #include, то вы бы написали:

main.o: defs.h

Это правило вам нужно, чтобы утилита make знала, что надо заново создать main.o всякий раз, когда изменился defs.h. Вы заметите, что для большой программы придется написать десятки подобных правил в вашем makefile. И вам нужно быть очень внимательным при обновлении makefile всякий раз, когда добавляете или удаляете в исходном коде директиву #include.

Чтобы избежать этих хлопот, современные компиляторы C могут написать для вас эти правила, путем просмотра строк #include и файлах исходного кода. Обычно это делается с помощью опции '-M' компилятора. Например, команда:

cc -M main.c

.. сгенерирует вывод:

main.o : main.c defs.h

Так что вам больше не нужно писать самому все эти правила. Всю работу возьмет на себя компилятор.

Обратите внимание, что такое правило представляет собой упоминание main.o в makefile, поэтому его никогда нельзя считать промежуточным фалов при поиске неявного правила. Это значит, что make никогда не удалит файл после его использования; см. 10.4. Цепочки неявных правил.

Со старыми программами make было традиционной практикой использовать эту фичу компилятора для генерации prerequisites по запросу наподобие 'make depend'. Эта команда создаст файл depend, содержащий все автоматически сгенерированные prerequisites; затем makefile может использовать include для их чтения (см. 3.3. Подключение других файлов Makefile).

В GNU make фича повторного создания makefiles делает эту практику устаревшей - вам никогда не нужно явно говорить утилите make регенерировать prerequisites, потому что она всегда регенерирует любой устаревший makefile. См. 3.5. Как заново создаются файлы Makefile.

Для автоматического создания prerequisites рекомендуется использовать один makefile, соответствующий каждому исходному файлу. Для каждого исходного файла name.c существует makefile-файл name.d, который перечисляет, от каких объектных файлов зависит объектный файл name.o. Таким образом, для создания новых prerequisites требуется повторное сканирование только измененных исходных файлов.

Вот правило шаблона для генерации файла prerequisites (например, makefile) с именем name.d из исходного файла C, у которого имя name.c:

%.d: %.c
        @set -e; rm -f $@; \
         $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
         rm -f $@.$$$$

Для информации об определении правил шаблона см. 10.5. Определение и переопределение правил шаблона. Флаг '-e' для шелла заставит его немедленно выполнить выход, если команда $(CC) (или любая другая команда) потерпит неудачу (завершится с ненулевым статусом).

С компилятором GNU C вы можете захотеть использовать флаг '-MM' вместо '-M'. Это опускает prerequisites на системных заголовочных файлах. Для получения подробностей см. описание опций управления препроцессором gcc [3].

Назначение команды sed состоит в трансляции (для примера):

main.o : main.c defs.h

.. в следующее:

main.o main.d : main.c defs.h

Дто делает каждый файл '.d' зависящим от всех исходных и заголовочных файлов, от которых зависит соответствующий файл '.o'. Тогда утилита make знает, что нужно регенерировать prerequisites всякий раз, когда поменялся любой заголовочный файл.

Как только вы определили правило для повторного создания файлов '.d', вам затем нужно использовать директиву include, чтобы их всех прочитать. См. 3.3. Подключение других файлов Makefile. Например:

sources = foo.c bar.c

include $(sources:.c=.d)

В этом примере используется ссылка на подставляемую переменную, чтобы транслировать список исходных файлов 'foo.c bar.c' в список prerequisite makefile-файлов 'foo.d bar.d'. Полную информацию о ссылках на подстановку см. 6.3.1. Ссылка с подстановкой. Поскольку  .d-файлы это такие же makefile, как и любые другие, make будет заново создавать их при необходимости, без вашего участия. См. 3.5. Как заново создаются файлы Makefile.

Обратите внимание, что .d-файлы содержат определения целевых объектов (target definitions); вы должны убедиться, что помещаете директиву include после первой default goal в своих файлах makefile, или столкнетесь с риском получить ситуацию, когда случайный объектный файл станет default goal. См. 2.3. Как make обрабатывает Makefile.

[5. Написание списков рецептов (recipe) в правилах]

Рецепт правила состоит из списка строк команд шелла, в котором может быть одна команда или несколько команд. Эти команды выполняются последовательно, по одной друг за другом, в том порядке, в каком они появляются в списке. Обычно результатом выполнения этих команд будет создание (или обновление) файла целевого объекта.

Пользователи могут использовать множество различных программ шелла, однако списки рецептов в файлах makefile всегда интерпретируются /bin/sh, если в makefile не указано нечто иное. См. 5.3. Выполнение рецепта.

5.1. Синтаксис рецепта

Файлы Makefile обладают необычным свойством: в одном файле фактически и действует два совершенно разных синтаксиса. По большей части это синтаксис make (см. 3. Написание файлов Makefile). Однако команды обновления цели (составляющие рецепт) предназначены для интерпретации шеллом, и они пишутся в синтаксисе скриптов shell (bash или sh). Утилита make не пытается парсить синтаксис shell: она выполняет только очень специфические трансляции содержимого рецепта перед передачей этого в шелл.

Важное правило: каждая строка рецепта обязательно должна начинаться с символа табуляции (либо значение первого символа должно совпадать со значением переменной .RECIPEPREFIX; см. 6.14. Другие специальные переменные), за исключением того, что первая строка рецепта может быть присоединена к строке цели и и предварительных условий (target-and-prerequisites) с точкой с запятой между ними. Любая строка в makefile, которая начинается с табуляции и появляется в "контексте правила" (то есть после того, как правило было запущено до другого определения правила или переменной), будет считаться частью рецепта для этого правила. Пустые строки и строки только комментариев могут появиться среди строк рецепта; они игнорируются.

Некоторые последствия этих правил включают в себя:

• Пустая строка, которая начинается на символ табуляции, на самом деле не пустая: это пустой рецепт (см. 5.9. Использование пустых рецептов).
• Комментарий в рецепте это не комментарий make; он передается в shell как есть. Будет ли эта строка обработана как комментарий или как-то иначе зависит от вашего shell.
• Определение переменной в "контексте правила", которое идет за символом табуляции в первом символе строки, будет считаться частью рецепта, но не определением переменной make, и это определение будет передано в shell.
• Условные выражения (ifdef, ifeq и т. п., см. 7.2. Синтаксис проверок условия) в "контексте правила", которые идут за символом табуляции в первой позиции строки, считаются частью рецепта, и будут переданы в shell.

5.1.1. Разделение на части строк рецепта. Один из нескольких способов, какими make интерпретирует строки рецепта, является проверка символа backslash непосредственно перед новой строкой. Нормальный синтаксис makefile подразумевает, что одна логическая строка рецепта может быть поделена на несколько физических строк в makefile путем помещения backslash перед каждым символом новой строки. Последовательность строк, оформленных таким образом, считается одной строкой рецепта, и для её запуска будет вовлекаться один экземпляр шелла.

Однако в отличие от того, как обрабатываются другие части в makefile (см. 3.1.1. Разбиение длинных строк), пары backslash/newline не удаляются из рецепта. Символы и backslash, и newline сохраняются и передаются в shell. Как эти backslash/newline интерпретируются, зависит от вашего шелла. Если первый символ на следующей строке после backslash/newline это символ префикса рецепта (по умолчанию это символ табуляции; см. 6.14. Другие специальные переменные), то этот символ (и только он) удаляется. Пробелы никогда не добавляются в рецепт.

Следующий пример для цели all в makefile:

all :
        @echo no\
space
        @echo no\
        space
        @echo one \
        space
        @echo one\
         space

.. состоит из четырех отдельных команд шелла, которые выведут следующее:

nospace
nospace
one space
one space

В качестве более сложного примера makefile:

all : ; @echo 'hello \
        world' ; echo "hello \
    world"

Здесь запустится одна команда шелла:

echo 'hello \
world' ; echo "hello \
    world"

.. которая, в соответствии с правилами кавычек, приведет к следующему выводу:

hello \
world
hello     world

Обратите внимание, как пара backslash/newline была удалена внутри строки, обрамленной двойными кавычками ("..."), однако это не было сделано внутри одинарных кавычек ('...'). Причина в том, как шелл по умолчанию (/bin/sh) обрабатывает пары backslash/newline. Если вы укажете другой шелл в своих makefile, то они могут обрабатываться по-другому.

Иногда вам может понадобиться разделить длинную строку внутри одинарных кавычек, но вы не хотите, чтобы backslash/newline появлялись в закавыченном содержимом. Это частый случай, когда строки передаются на обработку скриптам на языке наподобие Perl, где дополнительные символы backslash внутри скрипта могут поменять свой смысл, или даже привести к ошибкам синтаксиса. Один из простых способов справиться с такой ситуацией - поместить закавыченную строку, или даже всю команду целиком, в переменную make, и затем использовать эту переменную в рецепте. В этой ситуации будут использоваться правила кавычек новой строки для файлов makefile, и пары символов backslash/newline будут удалены. Вот что получится, если мы перепишем наш пример с помощью такого метода:

HELLO = 'hello \
world'
all : ; @echo $(HELLO)

.. это даст нам вывод наподобие следующего:

hello world

Если вы хотите, то можете также использовать специфические для цели переменные (см. 6.11. Значения переменных, специфичных для target), чтобы получить более плотное соответствие между переменной и рецептом, который ее использует.

5.1.2. Использование переменных в recipe. Другой способ, которым make обрабатывает рецепт - расширение ссылок на переменную в строках рецепта (см. 6.1. Ссылки на переменные: общая информация). Это происходит после того, как make завершит чтение всех makefile, и определит, что целевой объект является устаревшим; таким образом, никогда не будут расширяться переменные в списках рецептов для целей, которые не пересобираются.

Примечание: по этой причине невозможно добиться изменения переменной make внутри одного списка рецепта - всегда будет использоваться последнее присвоение переменной.

Ссылки на переменные и функции в рецептах имеют одинаковый синтаксис и семантику со ссылками в других местах makefile. Для них также действуют такие же правила экранирования: если вы хотите, чтобы символ доллара появился в вашем рецепте, вы должны вставлять его дважды ('$$'). Для интерпретаторов команд наподобие шелла по умолчанию, которые используют знак доллара для представления переменных, важно иметь в виду, является ли переменная, на которую вы хотите ссылаться, переменной make (использующей один символ доллара) или переменной шелла (использующей два символа доллара). Например:

LIST = one two three
all: for i in $(LIST); do \ echo $$i; \ done

.. приведет к тому, что в шелл будет передана команда:

for i in one two three; do \
    echo $i; \
done

.. что сгенерирует ожидаемый результат:

one
two
three

5.2. Эхо в рецепте

Обычно make печатает каждую строку рецепта перед её выполнением. Разработчики make называют это эхом, потому что создается впечатление, что вы сами печатаете эти строки.

Когда строка рецепта начинается с символа '@', эхо подавляется. Символ '@' отбрасывается перед передачей на выполнение в шелл. Обычно вы используете это для команд, которые только лишь что-то печатают, таких как команды вывода сообщений прогресса в makefile:

@echo About to make distribution files

Когда в make передана опция '-n' или '--just-print', она только лишь делает эхо большинства рецептов, без их выполнения. См. 9.8. Сводка по опциям make. В этом случае будут напечатаны даже те строки, которые начинаются на '@'. Эта опция полезна для поиска, какие рецепты выполняются, не выполняя их (что часто используется в отладке файлов makefile [4]).

Опция '-s' или '--silent' заставит make подавлять любое эхо, как если бы все строки рецептов начинались с символа '@'. Правило в makefile для специальной цели .SILENT без prerequisites дает тот же эффект (см. 4.9. Специальные встроенные имена целей).

5.3. Выполнение рецепта

Когда приходит время выполнить команды рецепта для обновления цели, они выполняются вовлечением каждого нового суб-шелла для каждой строки рецепта, за исключением ситуации, когда действует специальная цель .ONESHELL, см. 5.3.1. Использование одного шелла (на практике make может принимать ярлыки, которые не влияют на результаты).

Обратите внимание: это подразумевает, что установка переменных шелла и запуск команд шелла, таких как cd, которые устанавливают контекст для каждого процесса, не повлияют на последующие строки в рецепте (на MS-DOS значение текущей рабочей директории глобальное, и её изменение повлияет на последующие строки рецепта). Если вы хотите, чтобы cd повлияла на следующий оператор, то поместите оба этих оператора в одну строку рецепта. Тогда make будет запускать один шелл для всей строки, и этот шелл выполнит все эти операторы последовательно. Например:

foo : bar/lose
        cd $(<D) && gobble $(<F) > ../$@

Здесь мы используем оператор шелла AND (&&), так что если команда cd потерпит неудачу, сценарий завершился неудачей без попытки запуска команды gobble в неправильной директории, что могло бы создать проблемы (в этом случае произошло бы как минимум усечение ../foo).

5.3.1. Использование одного шелла. Иногда вы предпочли бы, чтобы все строки в рецепте передавались одному запущенному экземпляру шелла. Обычно бывают две ситуации, когда это полезно: во-первых, это может увеличить производительность в файлах makefile, где рецепт состоит из множества строк команд, чтобы избежать лишних процессов. Во-вторых, вы можете захотеть, чтобы в ваши команды рецепта были включены символы новой строки newline (например, возможно когда вы используете совершенно другой интерпретатор команд, отличающийся от вашего SHELL). Если в любом месте makefile появится специальная цель .ONESHELL, тогда все строки рецепта для каждой цели будут предоставлены одному запущенному экземпляру шелл. Новые строки между строками рецепта будут сохраняться. Например:

.ONESHELL:
foo : bar/lose
        cd $(<D)
        gobble $(<F) > ../$@

.. будет работать как ожидалось, даже хотя команды находятся на отдельных строках рецепта.

Если предоставлена цель .ONESHELL, то только первая строка рецепта будет проверяться на специальный символ префикса ('@', '-' и '+'). Последующие строки будут включать специальные символы в рецепте, когда вовлекается SHELL. Если вы хотите, чтобы ваш рецепт начинался с одним из этих специальных символов, то вам нужно будет договориться о том, чтобы они не были первыми символами в первой строке, возможно, добавив комментарий или что0то подобное. Например, это будет синтаксическая ошибка в Perl, потому что первый '@' удаляется make:

.ONESHELL:
SHELL = /usr/bin/perl
.SHELLFLAGS = -e
show :
        @f = qw(a b c);
        print "@f\n";

Однако любая из следующих альтернатив будет работать правильно:

.ONESHELL:
SHELL = /usr/bin/perl
.SHELLFLAGS = -e
show :
        # Убедитесь, чтобы "@" не был первым символом в первой строке
        @f = qw(a b c);
        print "@f\n";

.. или:

.ONESHELL:
SHELL = /usr/bin/perl
.SHELLFLAGS = -e
show :
        my @f = qw(a b c);
        print "@f\n";

Как специальная фича, если SHELL определена в шелл POSIX-стиля, специальные символы префикса во "внутренних" строках рецептов будут удалены перед обработкой рецепта. Эта фича предназначена для того, чтобы позволить существующим фалам makefile добавить специальную цель .ONESHELL, и все еще правильно работать без лишних модификаций. Поскольку специальные символы префикса недопустимы в начале строки скрипта POSIX-шелла, это не приведет к потере функциональности. Например, следующее будет работать как ожидалось:

.ONESHELL:
foo : bar/lose
        @cd $(@D)
        @gobble $(@F) > ../$@

Однако даже с этой специальной фичей файлы makefile со специальной целью .ONESHELL будут вести себя по-разному, что может быть заметно. Например, как правило, если какая-либо строка в рецепте терпит неудачу, это приводит к сбою правила и строки рецепта больше не обрабатываются. Под .ONESHELL отказ любой, кроме последней строки рецепта, не будет замечен make. Вы можете изменить .SHELLFLAGS, чтобы добавить параметр -e в шелл, что приведет к любому сбою в любом месте командной строки, и соответственно к сбою шелл. Но это может привести к тому, что ваш рецепт будет вести себя по-другому. В конечном итоге вам может потребоваться укрепить свои строки рецепта, чтобы они могли работать с .ONESHELL.

5.3.2. Выбор шелла. Программа, используемая в качестве шелла (интерпретатор команд операционной системы) берется из значения переменной SHELL. Если в вашем makefile эта переменная не установлена, то будет программа /bin/sh используется в качестве шелла. Аргументы, передаваемые в шелл, берутся из переменной .SHELLFLAGS. Значение по умолчанию .SHELLFLAGS нормальное -c, или -ec в режиме POSIX-совместимости.

В отличие от большинства переменных, переменная SHELL никогда не устанавливается из окружения. Причина в том, что она используется для указания вашего персонального выбора программы шелла для интерактивного использования. Было бы очень плохо, если бы такой личный выбор повлиял на функционирование файлов makefile. См. 6.10. Переменные из окружения.

Кроме того, когда вы делаете установку SHELL в своем makefile, её значение не экспортируется в окружение для строк рецепта, которые запускает make. Вместо этого значение наследуется из окружения пользователя, если оно там было экспортировано. Вы можете переопределить это поведение явным экспортом SHELL (см. 5.7.2. Передача переменных в sub-make), принудительно передавая эту переменную в окружение строк рецепта.

Однако на операционных системах MS-DOS и MS-Windows используется значение SHELL в окружении, поскольку на этих системах большинство пользователей не устанавливают эту переменную, и поэтому она, скорее всего, устанавливается специально для использования make. На MS-DOS, если установка SHELL не подходит для make, вы можете установить переменную MAKESHELL в шелл, который должна использовать make; если эта переменная установлена, то она будет использоваться в качестве шелла вместо значения SHELL.

Выбор шелла в DOS и Windows. Выбор шелла в MS-DOS и MS-Windows более сложен, чем в других системах.

На MS-DOS, если переменная SHELL не установлена, то вместо неё будет использоваться переменная COMSPEC (которая всегда установлена).

На MS-DOS отличается обработка строк, которые устанавливают переменную SHELL в файлах Makefile. Родной шелл command.com смехотворно ограничен по функционалу, и многие пользователи предпочитают установить альтернативную его замену. Таким образом на MS-DOS программа make проверяет значение SHELL, и меняет свое поведение не основе того, на на какой стиль шелла указывает переменная - Unix-style или DOS-style. Это обеспечивает разумную функциональность, даже если SHELL указывает на command.com.

Если SHELL указывает на Unix-style шелл, утилита make на MS-DOS делает дополнительные проверки, действительно ли этот шелл можно найти; если нет, то строка, которая устанавливает SHELL, игнорируется. На MS-DOS утилита GNU make ищет шелл в следующих местах:

1. В точном месте, указанном значением SHELL. Например, если makefile указал 'SHELL = /bin/sh', то make будет смотреть в директорию /bin на текущем диске.
2. В текущей директории.
3. В каждой директории, которые прописаны в переменной PATH, в указанном порядке.

В каждой проверяемой директории make сначала смотрит на наличие определенного файла (sh в примере выше). Если он не найден, то make будет также просматривать эту директорию на предмет наличия файла исполняемого типа. Например .exe, .com, .bat, .btm, .sh и некоторые другие.

Если любая из этих попыток была успешной, то значение SHELL будет установлено в полное имя пути найденного шелла. Однако если ничего из этого найдено нн было, значение SHELL не поменяется, и таким образом строка, которая устанавливает её, фактически игнорируется. Таким образом, make будет поддерживать только функции, специфичные для шелла в стиле Unix, если такая оболочка действительно установлена в системе, где запускается make.

Обратите внимание, что этот расширенный поиск шелла ограничен случаями, когда SHELL установлена из Makefile; если она установлена в окружении или из командной строки, то ожидается, что это полное имя пути для шелла, точно так же, как это имеет место в Unix.

Эффект от описанной выше специфичной для DOS обработки заключается в том, что Makefile, который содержит 'SHELL = /bin/sh' (как делают многие makefile в Unix), будет работать на MS-DOS без изменений, если у вас имеется установленный sh.exe в одном из путей, прописанных в PATH. 

5.4. Параллельное выполнение

GNU make умеет выполнять сразу несколько рецептов. Как правило, make выполняет только один рецепт за раз, ожидая его завершения перед выполнением следующего. Однако опция '-j' или '--jobs' предписывает make выполнять множество рецептов одновременно. Можно запретить параллелизм для некоторых или всех целевых объектов внутри make-файла (см. 5.4.1. Отключение параллельного выполнения).

На MS-DOS опция '-j' не дает эффекта, поскольку система не поддерживает многопоточность.

Если за опцией '-j' следует целое число, то оно задает количество одновременно выполняемых рецептов; это называется количеством рабочих слотов (job slot). Если после '-j' ничего не указано, то на количество рабочих слотов нет ограничений. По умолчанию (если опция '-j' не указана) количество рабочих слотов 1, что подразумевает последовательное выполнение (по одному действию).

Обработка рекурсивных вызовов make создает проблемы для параллельного выполнения. Для дополнительной информации об этом см. 5.7.3. Передача опций в sub-make.

Если рецепт терпит неудачу (прибит сигналом, или произошел выход из команды с ненулевым статусом), и для этого рецепта ошибки не игнорируются (см. 5.5. Ошибки в списках команд рецепта), оставшиеся строки рецепта для повторного создания той же самой цели не запускаются. Если рецепт терпит неудачу, и опция '-k' или '--keep-going' не была предоставлена (cм. 9.8. Сводка по опциям make), то make прекратит выполнение. Если make прервала работу по любой причине (включая остановку по сигналу) с запущенными дочерними процессами, то она ждет их завершения перед своим фактическим выходом.

Когда система сильно загружена, вы вероятно захотите запускать меньше заданий, чем когда система мало загружена. Вы можете использовать опцию '-l', чтобы указать make ограничить количество одновременно запускаемых заданий, основываясь на средней нагрузке. За опцией '-l' или '--max-load' следует число с плавающей точкой. Например,

-l 2.5

.. не даст make запустить больше одного задания, если средняя нагрузка выше 2.5. Опция '-l', за которой не следует число, снимает лимит нагрузки, если таковой был установлен предыдущей опцией '-l'.

Точнее, когда make намеревается запустить задание, у неё уже имеется как минимум одно запущенное задание, и make проверяет текущую среднюю нагрузку; если она не меньше, чем лимит, указанный опцией '-l', то make ждет, пока средняя нагрузка станет ниже лимита, или пока не будут завершены все другие задания.

5.4.1. Отключение параллельного выполнения. Если makefile полно и точно определяет зависимости между всеми своими целями, то make будет корректно строить goals независимо от того, разрешено параллельное выполнение или не разрешено. Это идеальный принцип для написания файлов makefile.

Однако иногда некоторые или все цели makefile не могут выполняться параллельно, и невозможно добавить prerequisites, необходимые для информирования make. В этом случае makefile может использовать различные методы для запрета параллельного выполнения.

Если в любом месте указана специальная цель .NOTPARALLEL без prerequisites, то вся make целиком будет работать последовательно, независимо от настройки параллельности. Например:

all: one two three
one two three: ; @sleep 1; echo $@

.NOTPARALLEL:

.. независимо от того, как была запущена make, цели one, two и three запустятся последовательно.

Если у специальной цели .NOTPARALLEL есть prerequisites, то каждая из этих prerequisites будет считаться целью, и все prerequisites этих целей будут запускаться последовательно. Обратите внимание, что последовательный запуск будет только при сборке этой цели: если какой-то другой список целей перечисляет те же prerequisites, и он не указан в .NOTPARALLEL, то эти prerequisites могут быть запущены параллельно. Например:

all: base notparallel

base: one two three
notparallel: one two three
one two three: ; @sleep 1; echo $@

.NOTPARALLEL: notparallel

Здесь 'make -j base' запустит цели one, two и three параллельно, в то время как 'make -j notparallel' запустит их последовательно. Если вы запустите 'make -j all', то они запустятся параллельно, поскольку base перечисляет их как prerequisites, и они не сериализованы.

Цель .NOTPARALLEL не должна иметь команд.

И наконец, вы можете тонко управлять сериализацией определенных prerequisites с помощью специальной цели .WAIT. Когда эта цель появляется в списке prerequisite, и разрешено параллельное выполнение, утилита make не будет собирать любые prerequisites справа от .WAIT пока не будут завершены все prerequisites слева от .WAIT. Например:

all: one two .WAIT three
one two three: ; @sleep 1; echo $@

Если параллельное выполнение разрешено, то make попытается построить one и two параллельно, но не будет пытаться построить three, пока оба one и two не завершатся.

Как в случае с целями, предоставленными для .NOTPARALLEL, .WAIT вступает в силу только при построении цели, в списке prerequisite которой она появляется. Если такие же prerequisites представлены в других целях без .WAIT, то они все еще могут быть запущены параллельно. По этой причине ни .NOTPARALLEL с целями, ни .WAIT не обладают таким надежным контролем над параллельным выполнением, как определение взаимозависимостей prerequisite. Однако они просты в использовании, и это может быть достаточным в не очень сложных ситуациях.

.WAIT prerequisite не будет присутствовать в любой автоматической переменной для правила.

Вы можете создать реальную цель .WAIT в вашем makefile для совместимости, но использование этой фичи не является обязательным. Если цель .WAIT создана, то она не должна иметь prerequisites или команд.

Фича .WAIT также реализована в других версиях make, и это определено стандартом POSIX для make.

5.4.2. Вывод при параллельном выполнении. Когда несколько рецептов запущены параллельно, вывод из каждого рецепта появляется как только он был сформирован. В результате сообщения из разных рецептов могут перемежаться, и иногда даже появляться на одной строке. Это может значительно усложнить чтение вывода make.

Чтобы избежать этого, вы можете использовать опцию '--output-sync' ('-O'). Эта опция инструктирует утилиту make сохранять вывод из команд, которые make запускает, и печатать их только когда команды полностью завершатся. Кроме того, если параллельно запущено несколько рекурсивных вызовов make, то они будут коммуницировать таким образом, чтобы вывод производила одновременно только их одного вызова make.

Если разрешена печать рабочей директории (см. описание 5.7.4. Опция '--print-directory'), то сообщения enter/leave (вход в директорию и выход из неё) печатаются вокруг каждой группы вывода. Если вы не хотите видеть эти сообщения, то добавьте опцию '--no-print-directory' к MAKEFLAGS.

Существует 4 уровня гранулярности при синхронизации вывода, указываемые аргументами для опции (например '-Oline' или '--output-sync=recurse').

none Это поведение по умолчанию, когда опция синхронизации вывода не используется: весь вывод посылается напрямую, как он был сгенерирован, и никакая синхронизация не выполняется.

line Вывод из каждой отдельной строки рецепта группируется и печатается как только строка была завершена. Если рецепт содержит несколько строк, то может произойти смешивание ввода от строк из других рецептов.

target Группируется вывод из всего рецепта полностью для каждой цели, и печатается при завершении цели. Это поведение по умолчанию, когда опция синхронизации --output-sync или -O указана без аргумента.

recurse Вывод из каждого рекурсивного вызова make группируется и печатается как только рекурсивный вызов завершится.

Независимо о выбранного вывода, общее время сборки будет одинаковым. Отличие только том, как будут появляться выводимые сообщения.

Оба режима 'target' и 'recurse' соберут вывод всего рецепта цели, и отобразят его, когда рецепт завершится. Различие между ними заключается в том, как обрабатываются рецепты, содержащие рекурсивные вызовы make (см. 5.7. Рекурсивное использование make). Для всех рецептов, у которых нет строк с рекурсией, режимы 'target' и 'recurse' ведут себя одинаково.

Если выбран режим 'recurse', то рецепты, которые содержат рекурсивные вызовы make, обрабатываются так же, как и другие цели: вывод из рецепта, включая вывод из рекурсивного make, сохраняется и печатается после завершения всего рецепта. Это гарантирует, что вывод из всех целей, построенных данным рекурсивным экземпляром make, будет совместно сгруппирован, что может облегчить понимание вывода make. Однако это может привести к тому, что в течение большого периода времени вывода не будет, и потом сразу появится большая порция выводимых сообщений. Если вы не наблюдаете визуально за прогрессом сборки, и предпочитаете вместо этого анализировать сборки постфактум, то это может быть для вас подходящей опцией.

Если же вы наблюдаете за выводом, то большие паузы в выводе могут вас разочаровать. Режим синхронизации вывода 'target' будет детектировать ситуации, когда make переходит к рекурсии, используя стандартные методы, и не будет синхронизировать вывод этих строк. Рекурсивный make будет выполнять синхронизацию для своих целей, и вывод из каждой цели будет отображаться немедленно, когда она завершится. Имейте в виду, что вывод из рекурсивных строк рецепта не синхронизируется (например, если рекурсивная строка печатает сообщение перед запуском make, то это сообщение не синхронизируется).

Режим 'line' может быть полезен для фронт-эндов, которые наблюдают за выводом make, чтобы отслеживать, когда рецепты запускаются и завершаются.

Некоторые программы, запускаемые утилитой make, могут вести себя по-разному, если они определят, что осуществляют вывод в терминал, в сравнении с выводом в файл (что часто описывается как режимы "интерактивности" против "не интерактивности"). Например, многие программы, которые могут отображать цветной вывод, не будут этого делать, если определят, что выводят не в терминал. Если ваш makefile запускает подобную программу, то использование опций синхронизации приведет к тому, что программа подумает, что работает в "не интерактивном" режиме, хотя вывод будет полностью осуществляться в терминал.

5.4.3. Ввод при параллельном выполнении. Два процесса не могут оба получать ввод из одного и того же устройства одновременно. Чтобы гарантировать, что одновременно только один рецепт пытается получить ввод из терминала, утилита make игнорирует стандартные потоки ввода из всех работающих рецептов, кроме одного. Если другой рецепт пытается прочитать из стандартного ввода, то он обычно попадет в фатальную ошибку (получит сигнал 'Broken pipe').

Нельзя предсказать, какой рецепт получить действительный стандартный входной поток (который приходит из терминала или другого источника, если вы перенаправили стандартный ввод make). Тот рецепт, который запустился первым, всегда будет первым получателем stdin, а первый рецепт, запущенный после него, всегда будет следующим по очереди получателем, и так далее.

В будущем разработчики make поменяют способ обработки этого аспекта, если будет найдена лучшая альтернатива. В то же время вы не должны полагаться на любой рецепт, используя стандартный ввод вообще, если используется фича параллельного выполнения; однако если параллельное выполнение не используется, то стандартный ввод будет нормально работать во всех рецептах.

5.5. Ошибки в списках команд рецепта

После возврата из каждой команды шелла, запускаемой для обработки целевого объекта, утилита make смотрит на статус выхода команды. Если команда шелла вернула статус успеха (статус exit равен 0), то выполнится следующая строка рецепта в новом shell; после завершения последней строки обработка правила завершена.

Если в какой-то команде произошла ошибка (статус exit не нулевой), то make сдается на текущем правиле, и возможно на всех остальных правилах.

Иногда отказ в определенной строке рецепта не означает наличие проблемы. Например, вы можете использовать команду mkdir, чтобы обеспечить существование директории. Если директория уже существует, то mkdir сообщит об ошибке, однако вероятно вы захотите продолжить работу make, независимо от статуса возврата mkdir.

Чтобы игнорировать ошибку на строке рецепта, начните её текст с символа '-' (после первого символа табуляции). Символ '-' отбрасывается перед передачей команды шеллу.

Например,

clean:
        -rm -f *.o

.. приведет к тому, что make продолжит работу, если даже rm не сможет удалить файл.

Когда вы запускаете make с опцией -i или --ignore-errors, то ошибки игнорируются во всех рецептах всех правил. Правило в makefile для специального целевого объекта .IGNORE дает тот же эффект, если у неё нет prerequisite. Это не настолько гибкое решение, но иногда бывает полезным.

Когда ошибки должны игнорироваться из-за '-' или опции -i, утилита make обрабатывает возвращаемую ошибку так же, как и успех, за исключением того, что он распечатывает сообщение, в котором сообщается код состояния, с которым вышла оболочка, и говорит, что ошибка была проигнорирована.

Когда произошла ошибка, которая не была обозначена для make как игнорируемая, это подразумевает, что не может быть корректно перестроена как текущий целевой объект, так и любой другой целевой объект, которая зависит от неё явно или косвенно. Дальнейшие рецепты для этих целевых файлов не будут выполняться, поскольку их предварительные условия не были достигнуты.

Обычно в этом случае make сразу сдается, и возвращает ненулевой статус. Однако, если указана опция -k или --keep-going, то make продолжает учитывать другие prerequisite ожидающих обработки целевых объектов, перестраивая их при необходимости перед тем, как сдаться и вернуть ненулевой статус. Например, после ошибки в одном компилируемом объектном файле make -k продолжит компиляцию других объектных файлов, хотя уже известно, что выполнить конечную цель сборки будет невозможно. См. 9.8. Сводка по опциям make.

Обычное поведение предполагает, что ваша цель состоит в том, чтобы при сборке поддерживать обновленными указанные цели; как только утилита make обнаружит, что это невозможно, она может немедленно сообщить о проблеме. Опция -k говорит о том, что реальная цель состоит в том, чтобы протестировать как можно больше изменений, сделанных в программе, и возможно обнаружить несколько независимых проблем в компилируемых файлах исходного кода, чтобы можно было исправить их все до следующей попытки запуска компиляции. Именно по этой причине команда компиляции Emacs по умолчанию передает опцию -k; применение этого флага может быть полезно при компиляции очень больших проектов.

Обычно при сбое строки рецепта, если она вообще изменила целевой файл, этот файл будет поврежденным, и его использовать нельзя, или как минимум он не может быть полностью обновлен или даже вообще сгенерирован. Тем не менее, если файл был создан, то метка файла говорит, что файл обновлен, и при следующей попытке запуска этот файл не будет обновляться. Ситуация точно такая же, как если бы shell был прибит сигналом (когда пользователь в процессе компиляции нажал Ctrl+C); см. 5.6. Прерывание, или аварийное завершение работы make. Так что было бы правильно удалить целевой файл, если рецепт терпит неудачу после начала изменения файла. Утилита make может это сделать автоматически, если в качестве целевого объекта появляется .DELETE_ON_ERROR. Это почти всегда то, что вы хотите сделать, но это не историческая практика; поэтому для обеспечения совместимости вы должны явно запросить это действие.

5.6. Прерывание, или аварийное завершение работы make

Если утилита make получит сигнал фатальной ошибки при работе шелла, она может удалить целевой файл (target), который рецепт должен был обновить. Это делается, если время последней модификации целевого файла изменилось с момента его первой проверки утилитой make.

Причина такого поведения - гарантировать, что файл создастся с нуля при следующем запуске make. Для чего это нужно? Предположим, что вы нажали комбинацию клавиш Ctrl-c при работе компилятора, и он начал записывать объектный файл foo.o. Ctrl-c прибьет компилятор, в результате не полностью сохраненный файл получит обновленное время последней модификации, которое превысит время последней модификации файла исходного кода foo.c. Однако утилита make также примет сигнал Ctrl-c и удалит этот неполный файл. Если бы make это не делала, то при следующем запуске make ошибочно подумает, что foo.o актуален, и не его не нужно обновлять - в результате будет странное сообщение об ошибке линкера, когда он попытается выполнить линковку объектного файла, половина которого отсутствует.

Вы можете предотвратить удаление целевого файла путем создания специальной цели .PRECIOUS, зависящей от него. Тогда перед тем, как заново создавать файл цели, утилита make проверит, присутствует ли этот файл в prerequisites .PRECIOUS, и по этому критерию решает, должен ли быть удален целевой файл, если произойдет сигнал остановки. Некоторые причины, по которым вы можете это делать, заключается в том, что цель обновляется каким-то атомарным способом, или существует только для записи времени модификации (содержимое файла цели не имеет значения), или файл цели должен существовать всегда, чтобы не могли произойти ошибки другого рода.

Хотя утилита make делает все возможное для лучшей очистки, существуют ситуации, при которых очистка невозможна. Например, make может быть прибита не перехватываемым сигналом. Или одна из программ, которую запустила утилита make, может быть прибита, или возможно она потерпела крах, оставив обновленным, но при этом поврежденным целевой файл: make не поймет, что этот сбой требует, чтобы цель была очищена. Или make может сама столкнуться с собственным багом и потерпеть крах.

По этой причине лучше всего писать защитные рецепты, которые не оставят после себя испорченные цели, даже если потерпят неудачу. Чаще всего такие рецепты создают временные файлы вместо того, чтобы напрямую обновлять файл цели, впоследствии переименовывая временный файл имя файла конечной цели. Некоторые компиляторы уже поступают таким образом, в этом случае можно не беспокоиться о защитном рецепте.

5.7. Рекурсивное использование make

Под рекурсивным использованием make подразумевается, что make используется в makefile как одна из команд рецепта. Эта техника полезна, когда вы хотите иметь отдельные файлы makefile для различных подсистем, которые составляют систему большего размера. Например предположим, что у вас есть подкаталог subdir, в котором находится его собственный makefile, и вы хотите, чтобы makefile основного каталога запускал make на этом подкаталоге. Вы можете сделать это следующим образом:

subsystem:
        cd subdir && $(MAKE)

.. или, что эквивалентно, так (см. 9.8. Сводка по опциям make):

subsystem:
        $(MAKE) -C subdir

Вы можете писать рекурсивные команды make, прост копируя этот пример, однако есть множество вещей, которые следует знать о том, как и почему это работает, и о том, как рекурсивный вызов make (sub-make) связан с make, который его вызвал (top-level make). Вы можете также найти полезным декларировать цели, которые вовлекают рекурсивные команды как '.PHONY' (для дополнительного обсуждения этой полезности см. 4.6. Фальшивые цели (Phony Targets)).

Для вашего удобства, когда запускается GNU make (после того, как она обработaла любые опции -C), она установит переменную CURDIR в имя пути текущей рабочей директории. Это значение make больше не меняет: в частности обратите внимание, что если вы подключаете файлы из других директорий, значение CURDIR не меняется. Это значение имеет такой же приоритет, какой оно имело бы, если бы оно задавалось в makefile (по умолчанию, переменная окружения CURDIR не переназначит это значение). Обратите внимание, что установка этой переменной не влияет на работу утилиты make (например, не приводит к изменению её рабочего каталога).

5.7.1. Как работает переменная MAKE. Рекурсивные команды make всегда должны использовать переменную MAKE, не явное имя команды 'make', как показано в следующем примере:

subsystem:
        cd subdir && $(MAKE)

Значение этой переменной - имя файла, которым была запущена make. Если у этого имени было значение /bin/make, то рецепт выполнился с командой 'cd subdir && /bin/make'. Если вы используете специальную версию make для запуска top-level makefile, то та же самая специальная версия будет выполнена для рекурсивных вызовов make.

Как специальная фича, использование переменной MAKE в рецепте правила меняет эффекты опций '-t' ('--touch'), '-n' ('--just-print') или '-q' ('--question'). Использование переменной MAKE имеет тот же эффект, что и использование символа '+' в начале строки рецепта. См. 9.3. Вместо выполнения recipes. Эта специальная фича разрешена только если переменная MAKE напрямую появляется в рецепте: она не применяется, если переменная к переменной MAKE произошло обращение через расширение другой переменной. В последнем случае вы должны использовать токен '+', чтобы получить эти специальные эффекты.

Рассмотрим команду 'make -t' в приведенном выше примере (опция '-t' помечает цели обновленными без фактического запуска каких-либо рецептов; см. 9.3. Вместо выполнения recipes). Следуя обычному определению '-t', команда 'make -t' в примере создала бы файл с именем subsystem, и ничего больше не сделала бы. Что вы действительно хотите, чтобы команда сделала, так это запуск 'cd subdir && make -t'; однако это потребует выполнения рецепта, а опция '-t' говорит не выполнять рецепты.

Специальная фича заставляет делать то, что вы хотите: всякий раз, когда строка рецепта в правиле содержит переменную MAKE, опции '-t', '-n' и '-q' к этой строке не применяются. Строки рецепта, содержащие MAKE, выполняются нормально, независимо от присутствия опции, которая заставляет не запускаться большинство рецептов. Обычный механизм MAKEFLAGS передает флаги в sub-make (см. 5.7.3. Передача опций в sub-make), так что ваш запрос на touch файлов, или печати рецептов, распространяется на подсистему.

5.7.2. Передача переменных в sub-make. Значения переменных top-level make могут быть переданы в sub-make через окружение по явному запросу. Эти переменные определены в sub-make как умолчания, однако они не переназначают переменные, определенные в makefile, используемые sub-make, если вы не используете опцию '-e' (см. 9.8. Сводка по опциям make).

Для передачи вниз, или экспорта, переменной, make добавляет переменную и её значение в окружение для запуска каждой строки рецепта. В свою очередь sub-make использует окружение для инициализации своей таблицы значений переменной. См. 6.10. Переменные из окружения.

За исключением явного запроса, make экспортирует переменную только если она либо изначально определена в окружении, либо если она установлена строкой команды, и её имя содержит только буквы, цифры и символы нижнего подчеркивания.

Значение make-переменной SHELL не экспортируется. Вместо этого значение переменной SHELL из вовлеченного окружения передается в sub-make. Вы можете заставить make экспортировать свое значение для SHELL с помощью директивы export, как описано далее. См. 5.3.2. Выбор шелла.

Специальная переменная MAKEFLAGS экспортируется всегда (за исключением случая, когда вы сделали её отмену через unexport). MAKEFILES экспортируется, если установили её в какое-то значение.

Утилита make автоматически передает вниз значения переменных, которые определены в командной строке, путем помещения их в переменную MAKEFLAGS. См. 5.7.3. Передача опций в sub-make.

Переменные нормально не передаются вниз, если они были созданы по умолчанию с помощью make (см. 10.3. Переменные, используемые неявными правилами). Sub-make будет определять их для себя.

Если вы хотите экспортировать определенные переменные в sub-make, используйте директиву export, вот так:

export variable ...

Если вы хотите защитить переменную от экспорта, используйте директиву unexport:

unexport variable ...

В обоих этих формах аргументы export и unexport расширяются, и поэтому могут быть переменные или функции, которые расширяются в (список) имен переменных, которые будут экспортированы (в случае export) или не будут экспортированы (в случае unexport).

Для удобства мы можете определит переменную и одновременно сделать её экспорт следующим образом:

export variable = value

.. что дает результат, какой же как:

variable = value
export variable

И следующее:

export variable := value

.. дает такой же результат, что и:

variable := value
export variable

Подобным образом,

export variable += value

.. это то же самое, что и:

variable += value
export variable

См. 6.6. Добавление текста к переменным.

Вы можете заметить, что make-директивы export и unexport работают похоже на то, как они работают в шелл sh (за исключением unexport, такую команду sh не понимает).

Если вы хотите, чтобы все переменные были по умолчанию экспортированы, то вы можете использовать саму директиву export:

export

Это говорит make, что переменные, которые вы не упомянули явно в директиве export или unexport, должны быть экспортированы. Любая переменная, упомянутая в директиве unexport, все еще не будет экспортироваться.

Поведение, выявленное директивой export, само по себе было умолчанием в старых версиях GNU make. Если ваши файлы makefile зависят от этого поведения, и вы хотите сохранить совместимость со старыми версиями make, то вы можете добавить специальную цель .EXPORT_ALL_VARIABLES в свой makefile вместо использования директивы export. Это будет игнорироваться старыми версиями make, в то время как директива export будет приводить к ошибке синтаксиса.

Когда используется export сама по себе, или когда добавлена цель .EXPORT_ALL_VARIABLES для экспорта по умолчанию переменных, будут экспортироваться исключительно только те переменные, имена которых состоят только из символов букв, цифр и символов подчеркивания. Для экспорта других переменных вы должны специально упоминать их в директиве export.

Добавление значения переменной в окружение требует расширения переменной. Если расширение переменной дает побочные эффекты (такие как info или eval, или подобные функции) то эти побочные эффекты будут проявляться каждый раз при выполнении команды. Вы можете избежать этого, если обеспечите их имена такими, которые по умолчанию не экспортируются. Однако лучшим решением будет вообще не использовать этот механизм "экспорта по умолчанию", и вместо этого применять явный export соответствующих переменных по имени.

Вы можете использовать директиву unexport саму по себе (без указания имен переменных), чтобы сказать make не экспортировать переменные по умолчанию. Поскольку это поведение по умолчанию, вам нужно будет сделать это только в том случае, если директива export использовалась сама по себе ранее (возможно, в подключенном makefile). Вы не можете использовать export и unexport сами по себе чтобы переменные экспортировались для одних рецептов и не экспортировались для других. Последнее появление директивы export или unexport directive само по себе определяет поведение всего запуска make.

Как специальная фича, существует переменная MAKELEVEL, которая меняется при переходе вниз и вверх по уровням рекурсивного вызова. Это значение переменной является строкой, которая показывает глубину уровня вложенности в виде десятичного значения. Таким образом, значение '0' обозначает top-level make; '1' sub-make, '2' sub-sub-make, и так далее. Инкремент происходит, когда make устанавливает окружение для рецепта.

Основное использование MAKELEVEL предназначено для проверки в директиве условия (см. 7. Операции проверки условий в файлах Makefile); так что вы можете написать makefile, который ведет себя по-другому в рекурсиях, чем если бы он был запущен напрямую.

Вы можете использовать переменную MAKEFILES, чтобы заставить все команды sub-make использовать дополнительные файлы makefile. Значение MAKEFILES это список имен файлов, отделенных друг от друга пробелом. Эта переменная, если она определена в makefile внешнего уровня, передается вниз через окружение; затем она служит списком дополнительных файлов makefile для sub-make, чтобы они были прочитаны перед обычными makefile или указанными. См. 3.4. Переменная MAKEFILES.

5.7.3. Передача опций в sub-make. Опции, такие как '-s' и '-k', автоматически передаются в sub-make через переменную MAKEFLAGS. Эта переменная устанавливается автоматически утилитой make, чтобы MAKEFLAGS содержала буквы опций, которые приняла make. Таким образом, если вы выполнили 'make -ks', то MAKEFLAGS получит значение 'ks'.

Как следствие, каждая sub-make получает значение для MAKEFLAGS в своем окружении. В ответ она получает флаги их этого значения, и обрабатывает из как если бы они были предоставлены как аргументы. См. 9.8. Сводка по опциям make. Это означает, что в отличие от других переменных окружения, указанная в окружении MAKEFLAGS имеет приоритет перед MAKEFLAGS, указанной в makefile.

Значение MAKEFLAGS может быть пустой группой символов, представляющих однобуквенные параметры опций, не принимающих аргументов, за которыми следует пробел и любые опции, принимающие аргументы, или имеющие длинные имена опции. Если у опция есть в двух видах - однобуквенная и длинная, то однобуквенной всегда дается предпочтение. Если в командной строке не было однобуквенных опций, то значение MAKEFLAGS начинается с пробела.

Подобным образом переменные, определенные в командной строке, передаются в sub-make через MAKEFLAGS. Слова в значении MAKEFLAGS, которые содержат '=', make обрабатывает как определения переменных, как если бы это появлялось в командной строке. См. 9.5. Переназначение переменных.

Опции '-C', '-f', '-o' и '-W' не вставляются в MAKEFLAGS; эти опции вниз не передаются.

Опция '-j' это специальный случай (см. 5.4. Параллельное выполнение). Если вы установили какое-то числовое значение 'N', и ваше операционная система это поддерживает (что верно для любой UNIX-системы; для других обычно нет), то родительская make и все её sub-make будут коммуницировать, чтобы гарантировать, что только 'N' заданий запущены одновременно между всеми ими глобально. Обратите внимание, что любое задание, которое помечено как рекурсивное (см. 9.3. Вместо выполнения recipes) не учитывает общее количество заданий (иначе мы могли бы получить 'N' запущенных sub-make, и не осталось бы слотов для реальной работы!).

Если ваша операционная система не поддерживает описанную выше коммуникацию, то '-j' не добавляется в MAKEFLAGS, так что sub-make запускаются не в параллельном режиме. Если бы опция '-j' передавалась вниз в sub-make, вы бы получили намного больше заданий, работающих параллельно, чем запрашивали. Если вы предоставили '-j' без числового аргумента, что означало бы параллельный запуск как можно большего количества заданий, то это передается вниз, поскольку несколько бесконечностей это то же самое, что и одна бесконечность.

Если вы не хотите передавать другие флаги вниз, то должны поменять значение MAKEFLAGS, например так:

subsystem:
        cd subdir && $(MAKE) MAKEFLAGS=

Определения переменных командной строки реально появляются в переменной MAKEOVERRIDES, и MAKEFLAGS содержит ссылку на эту переменную. Если вы хотите нормально передать вниз опции, но не хотите передать вниз определения переменной из командной строки, то можете сбросить MAKEOVERRIDES в пустое значение, вот так:

MAKEOVERRIDES =

Необязательно будет полезным поступать таким образом. Однако некоторые системы имеют небольшой фиксированный лимит на размер окружения, и вставка большого количества информации в значение MAKEFLAGS может этот предел превысить. Если вы наблюдаете сообщение 'Arg list too long', то это может быть этой проблемой (для строгой совместимости с POSIX.2 изменение MAKEOVERRIDES не повлияет на MAKEFLAGS, если в makefile появилась специальная цель '.POSIX'. Скорее всего это не должно вас волновать).

Также для исторической совместимости существует переменная MFLAGS. У неё то же самое значение, что и у MAKEFLAGS, за исключением того, что она не содержит определений переменных из командной строки, и всегда начинается с дефиса, если она не пустая (MAKEFLAGS начинается с дефиса только когда начинается опции, когда она не однобуквенная, такая как '--warn-undefined-variables'). MFLAGS была традиционно используемой исключительно в рекурсивных командах make, примерно так:

subsystem:
        cd subdir && $(MAKE) $(MFLAGS)

.. однако сейчас MAKEFLAGS делает это использование избыточным. Если вы хотите, чтобы ваши файлы makefile были совместимы со старыми программами make, то используйте эту технику; она также будет нормально работать и с более современными версиями make.

Переменная MAKEFLAGS может быть также полезной, если вы хотите иметь определенные опции, такие как '-k' (см. 9.8. Сводка по опциям make), установленными при каждом запуске make. Для этого просто поместите значение для MAKEFLAGS в ваше окружение. Вы также можете установить MAKEFLAGS в makefile, чтобы указать дополнительные флаги, которые также должны давать эффект в этом makefile (обратите внимание, что вы не можете для той же цели использовать MFLAGS. Эта переменная устанавливается только для совместимости; make не интерпретирует её установленное вами значение).

Когда make интерпретирует значение MAKEFLAGS (установленное либо из окружения, либо из makefile), она сначала добавляет к ней дефис, если его нет. Затем make разбивает MAKEFLAGS на слова, разделенные пробелами, и парсит эти слова как если бы они были опциями, присутствующими в командной строке (за исключением '-C', '-f', '-h', '-o', '-W' и их длинных аналогов, которые игнорируются; и нет ошибки для недопустимой опции).

Если вы поместили переменную MAKEFLAGS в свое окружение, то должны гарантировать, что в ней не включены любые опции, которые будут резко влиять на действия make и подрывать назначение работы файлов makefile и самой утилиты make. Например, это касается опций '-t', '-n' и '-q' - если поместить в MAKEFLAGS одну из этих опций, то вероятно будут удивительные и раздражающие последствия вызовов make.

Если вы хотели бы запустить другие реализации make в дополнение к GNU make, и поэтому не хотите добавлять специфические для GNU make опции в переменную MAKEFLAGS, то можете добавить их вместо этого в переменную GNUMAKEFLAGS. Эта переменная парсится непосредственно перед MAKEFLAGS, таким же способом, что и MAKEFLAGS. Когда make конструирует MAKEFLAGS для передачи в рекурсивный вызов make, то она будет включать все флаги, даже те, которые взяты из GNUMAKEFLAGS. В результате после парсинга GNUMAKEFLAGS утилита GNU make установит эту переменную в пустую строку, чтобы избежать дублирования опций во время рекурсии.

Лучше использовать GNUMAKEFLAGS только с опциями, которые не хотят материально менять поведение ваших файлов makefile. Если ваши файлы makefile все равно требуют GNU make, то просто используйте MAKEFLAGS. Такие опции, как '--no-print-directory' или '--output-sync' могут быть подходящими для GNUMAKEFLAGS.

5.7.4. Опция '--print-directory'. Если у вас используется несколько уровней рекурсивных вызовов make, то опция '-w' или '--print-directory' может сделать вывод несколько проще для понимания, потому что будет показана каждая директория, в которой make начинает обработку, и когда эта обработка завершается. Например, если 'make -w' запущена в директории /u/gnu/make, то make напечатает строку:

make: Entering directory `/u/gnu/make'.

.. перед тем, как что-то делать, и строку:

make: Leaving directory `/u/gnu/make'.

.. когда завершит обработку.

Обычно вам не нужно указывать эту опцию, потому что 'make' сделает это для вас: '-w' активируется автоматически, когда вы используете опцию '-C' и вызовы sub-make. Утилита make не активирует автоматически '-w', если вы также используете '-s', которые говорят работать "тихо", или если вы используете '--no-print-directory' для явного запрета этой фичи.

5.8. Определение фиксированных (canned) рецептов

Когда одна и та же последовательность команд полезна для сборки различных файлов целей, вы можете определить её как фиксированную (canned sequence, и canned-рецепт) с помощью директивы define, и вызывать из рецептов для этих файлов целей. Canned-рецепт это фактически переменная, так что его имя не должно конфликтовать с именами других переменных.

Вот пример определения canned-рецепта:

define run-yacc =
yacc $(firstword $^)
mv y.tab.c $@
endef

Здесь run-yacc это имя определяемой переменной; endef помечает конец определения; строки между ними это команды. Директива define не расширяет ссылки на переменные и вызовы функций в canned-рецепте; символы '$', круглые скобки, имена переменных и так далее, все становятся частью переменной, которую вы определили. См. 6.8. Определение многострочных переменных для полного объяснения работы директивы define.

Первая команда в этом примере запускает Yacc на первой prerequisite того правила, которое использует canned-рецепт. Выходной файл из Yacc всегда получает имя y.tab.c. Вторая команда переименовывает выходной файл в имя файла цели.

Чтобы использовать canned-рецепт, подставьте его переменную в рецепт правила. Вы можете её подставить как и любую другую переменную (см. 6.1. Ссылки на переменные: общая информация). Из-за того, что переменные, определенные директивой define, являются рекурсивно расширяемыми переменными, все ссылки на переменные, которые вы написали внутри define, теперь расширятся. Например:

foo.c : foo.y
        $(run-yacc)

Здесь 'foo.y' будет поставлен для переменной '$^', когда она встретилась в значении run-yacc, и 'foo.c' для '$@'.

Это реалистичный пример, однако не нужен на практике, потому что у make есть неявное правило, чтобы вывести эти команды на основе соответствующих имен файлов (см. 10. Использование неявных правил).

В выполнении рецепта каждая строка из canned-рецепта обрабатывается как если бы строка сама появилась в правиле с подставленным спереди символом табуляции. В частности, утилита make запустит отдельный sub-shell для каждой строки. Вы можете использовать специальные символы префикса, которые влияют на строки команды ('@', '-' и '+') на каждой строке canned-рецепта. См. 5. Написание списков рецептов (recipe) в правилах. Например, с использованием этого canned-рецепта:

define frobnicate =
@echo "frobnicating target $@"
frob-step-1 $< -o $@-step-1
frob-step-2 $@-step-1 -o $@
endef

.. утилита make не будет выводить эхо первой строки, echo команды. Однако эхо будет выводиться для последующих двух строк рецепта.

С другой стороны, символы префикса в строке рецепта, ссылающейся на canned-рецепт, применятся на каждую строку в последовательности команд canned-рецепта. Так что правило:

frob.out: frob.in
        @$(frobnicate)

.. не будет печатать эхо ни для каких строк рецепта (см. 5.2. Эхо в рецепте для полного объяснения '@').

5.9. Использование пустых рецептов

Иногда полезно определять рецепты, которые ничего не делают. Это делается просто составлением рецепта, который состоит только из пробелов. Например:

target: ;

.. определит пустой рецепт для файла целевого объекта target. Вы также можете использовать строку, начинающуюся с символа префикса рецепта, чтобы определить пустой рецепт, но это будет сбивать с толку, потому что такая строка выглядит пустой.

Вам может быть интересно, почему стоит определять рецепт, который ничего не делает. Одна из причин полезности пустого рецепта - предотвращение получения целевым объектом неявных рецептов (из неявных правил или специальной цели .DEFAULT; см. 10. Использование неявных правил и 10.6. Определение правил по умолчанию для последней инстанции).

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

Вы можете быть склонны определять пустые рецепты для целей, которые не являются реальными файлами, существующих только для того, чтобы их prerequisites могли быть созданы заново. Однако это не лучший способ, потому что prerequisites могли бы быть созданы неправильно, если файл цели действительно существует. См. 4.6. Фальшивые цели (Phony Targets), что является более подходящим методом.

[6. Как использовать переменные в makefile]

Переменная это имя, определенное в makefile, чтобы представлять строку текста, которая называется значением переменной. Эти значения подставляются явным запросом в целевых объектах, предпосылках (prerequisites), рецептах (recipes) и других частях makefile (в некоторых других версиях make, переменные называются макросами). Такая подстановка называется расширением переменной.

Переменные и функции во всех частях makefile расширяются при чтении, за исключением для рецептов, правой стороны определений переменной с помощью '=' и тел определений переменной с помощью директивы define. Значение, в которое переменная расширяется, является значением её самого последнего определения на момент расширения. Другими словами, переменные динамически масштабируются.

Переменные могут представлять списки имен файлов, опций для передачи компиляторам, программ для запуска, директорий для поиска в них исходных файлов, директорий для записи выходных файлов, или чего угодно, что вы могли бы представить.

Имя переменной может быть последовательностью любых символов, кроме ':', '#', '=' и пробела. Однако следует с осторожностью использовать переменные, имена которых состоят не только из символов букв, цифр или символа подчеркивания, поскольку в некоторых шеллах они не могут быть переданы через окружение в рекурсивный вызов sub-make (см. 5.7.2. Передача переменных в sub-make). Имена переменных, начинающиеся с точки '.' и буквы верхнего регистра, могут иметь специальный смысл в будущих версиях make.

Имена переменных чувствительны к регистру. Таким образом, 'foo', 'FOO' и 'Foo' все относятся к разным переменным.

Использование букв верхнего регистра является традиционным для имен переменных, однако разработчики make рекомендуют использовать буквы в нижнем регистре для имен переменных, обслуживающих внутренний функционал в makefile, резервируя верхний регистр для параметров, которые управляют неявными правилами, или для параметров, которые пользователь должен (мог бы) переопределить опциями командной строки (см. 9.5. Переназначение переменных).

Некоторые переменные имеют имена, состоящие из одного или нескольких символов пунктуации. Это автоматические переменные, и у них специализированное использование. См. 10.5.3. Автоматические переменные make.

6.1. Ссылки на переменные: общая информация

Чтобы подставить значение переменной, напишите знак доллара, за которым идет имя переменной в круглых или фигурных скобках: '$(foo)' или '${foo}' это допустимая ссылка на переменную foo. Это специальное значение символа '$' является причиной, почему мы должны писать '$$', чтобы получить эффект одного символа доллара в имени файла или рецепта.

Ссылки на переменную можно использовать в любом контексте: обозначении целевых файлов, prerequisites, в рецептах, в большинстве директив и новых значениях переменных. Вот пример общего случая, где переменная хранит имена всех объектных файлов в программе:

objects = program.o foo.o utils.o
program : $(objects)
        cc -o program $(objects)

$(objects) : defs.h

Ссылки на переменные работают путем строгой подстановки текста. Таким образом, правило:

foo = c
prog.o : prog.$(foo)
        $(foo)$(foo) -$(foo) prog.$(foo)

.. должно использоваться для компиляции C-программы prog.c. Поскольку пробелы перед значением переменной игнорируются в присваиваниях переменной, то значение foo будет точно 'c' (не следует на самом деле писать файлы makefile таким способом!).

Символ за символом доллара, который сам не знак доллара, не открытая круглая скобка и не открытая фигурная скобка, обрабатывается как односимвольное имя переменной. Таким образом, на переменную x можно ссылаться через '$x'. Однако подобная практика может привести к путанице, (например '$foo' ссылается на переменную f, за которой идет строка oo), поэтому рекомендуется использовать круглые или фигурные скобки вокруг всех переменных, даже однобуквенных, за исключением ситуаций, когда отсутствие скобок дает значительное улучшение читаемости. Одна из таких ситуаций - автоматические переменные (см. 10.5.3. Автоматические переменные make).

6.2. Две разновидности (flavor) переменных

Существует различные способы, которыми переменная GNU make может получить свое значение; разработчики make называют эти разновидности flavor переменных. Flavor-ы различаются тем, как они обрабатывают значения, которые им назначены в makefile, и тем, как эти значения управляются при последующем использовании и расширении переменных.

6.2.1. Рекурсивно расширяемое присваивание переменной. Первый flavor это рекурсивно расширяемая переменная. Переменные такого сорта определяются строками, использующие знак '=' (см. 6.5. Установка переменных) или директиву define (см. 6.8. Определение многострочных переменных). Указанное значение устанавливается дословно; если оно содержит ссылки на другие переменные, то эти ссылки расширяются всякий раз, когда переменная подставляется (в процессе расширения какой-то другой строки). Когда такое происходит, это называется рекурсивным расширением.

Например,

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:;echo $(foo)

.. выведет echo 'Huh?', потому что '$(foo)' расширится до '$(bar)', которая расширится до '$(ugh)', которая наконец расширится до 'Huh?'.

Этот вид переменной является единственным видом, поддерживаемым большинством других версий make. У него есть свои достоинства и недостатки. Преимущество (как считают многие) следующее:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

Этот пример сделает то, что ожидается: когда 'CFLAGS' расширяется в рецепте, она получит значение '-Ifoo -Ibar -O'. Основной недостаток в том, что вы не можете добавить что-нибудь в конец переменной, как в следующем примере:

CFLAGS = $(CFLAGS) -O

.. потому что это приведет к бесконечному циклу расширения переменной (в действительности make обнаруживает бесконечный цикл и сообщит об ошибке).

Другой недостаток состоит в том, что любые функции (см. 8. Функции для преобразования текста) со ссылкой в определении, будут выполняться каждый раз при расширении переменной. Это замедляет работу make; и что еще хуже, шаблоны wildcard и функции шелла дают непредсказуемый результат, потому что нельзя просто управлять тем, когда или даже сколько раз они вызываются.

6.2.2. Просто расширяемое присваивание переменной. Чтобы избежать проблем и неудобств рекурсивно расширяемых переменных существует другой flavor: просто расширяемые переменные.

Просто расширяемые переменные определяются строками, где используются ':=' или '::=' (см. 6.5. Установка переменных). Обе эти формы эквивалентны в GNU make; однако стандарт POSIX описывает только лишь форму '::=' (поддержка для '::=' добавлена в стандарт POSIX для POSIX Issue 8).

Значение просто расширяемой переменной сканируется один раз, расширяя любые ссылки на другие переменные и функции, когда переменная определена. Как только расширение завершилось, значение переменной больше не расширяется снова: когда переменная используется, её значение копируется дословно уже как расширенное. Если значение содержит ссылки на переменную, то результат расширения будет содержать их значения на момент определения этой переменной. Таким образом,

x := foo
y := $(x) bar
x := later

.. эквивалентно следующему:

y := foo bar
x := later

Вот несколько более сложный пример, иллюстрирующий использование ':=' в сочетании с функцией shell (см. 8.14. Функция shell). Этот пример также показывает использование переменной MAKELEVEL, которая меняется при передаче её вниз с одного уровня на другой (см. 5.7.2. Передача переменных в sub-make для информации по переменной MAKELEVEL).

ifeq (0,${MAKELEVEL})
whoami    := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

Достоинство этого использования ':=' в том, что обычное 'погружение в директорию' рецепта тогда выглядит следующим образом:

${subdirs}:
        ${MAKE} -C $@ all

Просто расширяемые переменные обычно делают сложное программирование файла makefile более предсказуемым, потому что оно работает наподобие переменным в большинстве языков программирования. Просто расширяемая переменная позволяет переопределить переменную, используя её же собственное текущее значение (или её значение, обработанное каким-либо образом одной из функций расширения), и позволяет использовать функции расширения намного более эффективно (см. 8. Функции для преобразования текста).

Просто расширяемые переменные можно также использовать для введения контролируемого начального пробела в значениях переменной. Символы начального пробела отбрасываются из вашего ввода перед подстановкой ссылок на переменную и вызовов функций; это означает, что вы можете включить начальные пробелы в значение переменной, защищая их ссылками на переменные, примерно так:

nullstring :=
space := $(nullstring) # конец строки

Здесь значение переменной space окажется точно одним пробелом. Комментарий '# конец строки' добавлен только для ясности. Поскольку завершающие символы пробела не вырезаются из значений переменной, просто пробел в конце строки (без этого комментария) даст тот же эффект (но тогда это усложнит читаемость и понимание). Если вы поместили пробел в конец значения переменной, будет хорошей идеей добавить подобный комментарий, чтобы ваши намерения были ясны. Соответственно, если вы не хотите, чтобы любые пробелы добавлялись в конец вашего значения переменной, то вы должны помнить, что не следует помещать комментарий в конец строки, как в этом примере:

dir := /foo/bar    # каталог для всякой всячины

Здесь значение переменной dir получится '/foo/bar    ' (с четырьмя пробелами в конце), что скорее всего совсем не то, что вам было нужно (представьте, что получилось бы при '$(dir)/file' с таким определением!).

6.2.3. Немедленно расширяемое присваивание переменной. Другая форма присваивания позволяет немедленное расширение, однако в отличие от простого присваивания результирующая переменная рекурсивна: она будет повторно расширяться при каждом использовании. Чтобы избежать неожиданных результатов, после немедленного расширения значение будет автоматически экранировано: все экземпляры $ в значении после расширения будут преобразованы в $$. Этот тип присваивания использует оператор ':::='. Например,

var = first
OUT :::= $(var)
var = second

.. приведет к переменной OUT, содержащей текст 'first', в то время как:

var = one$$two
OUT :::= $(var)
var = three$$four

.. приведет к переменной OUT, содержащей текст 'one$$two'. Значение расширяется, когда переменная присваивается, так что результат расширения первого значения var, 'one$two'; затем значение подвергается повторному экранированию перед присваиванием, что дает конечный результат 'one$$two'.

Переменная OUT после этого считается рекурсивной переменной, так что она повторно расширяется при использовании.

Этот функционал выглядит эквивалентно операторам ':=' / '::=', однако есть некоторые отличия:

Во-первых, после присваивания переменная является нормальной рекурсивной; когда вы добавляете к ней что-то с помощью '+=', значение с правой стороны не расширяется немедленно. Если вы хотели бы использовать оператор '+=' с немедленным расширением правой стороны, то должны использовать вместо этого присваивание ':=' / '::='.

Во-вторых, эти переменные несколько менее эффективны, чем просто расширяемые переменные, поскольку должны повторно расширяться каждый раз при использовании, а не просто скопированы. Однако, поскольку все ссылки на переменные экранируются, это расширение просто не экранирует значение, оно не будет расширять любые переменные или запускать любые функции.

Вот другой пример:

var = one$$two
OUT :::= $(var)
OUT += $(var)
var = three$$four

После этого значение OUT будет текстом 'one$$two $(var)'. Когда эта перемененная используется, она будет расширена, и даст результат 'one$two three$four'.

Этот стиль присваивания эквивалентен традиционному BSD make оператору ':='; как вы можете видеть, это работает несколько по-другому, чем GNU make оператор ':='. Оператор :::= был добавлен в спецификацию POSIX для Issue 8 с целью обеспечения портируемости.

6.2.4. Условное присваивание переменной. Существует еще один оператор присваивания для переменных, '?='. Он называется условным присвоением, потому что дает эффект только если переменная еще не была определена. Оператор:

FOO ?= bar

.. полностью эквивалентен следующему (см. 8.11. Функция origin):

ifeq ($(origin FOO), undefined)
  FOO = bar
endif

Обратите внимание, что переменная, для которой задано пустое значение, все еще определена, так что '?=' не будет устанавливать эту переменную.

6.3. Продвинутые возможности для ссылок на переменные

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

6.3.1. Ссылка с подстановкой. Подстановочная ссылка заменяет значение переменной на указанные вами изменения. Это имеет вид '$(var:a=b)' (или '${var:a=b}'), и означает взять значение переменной var, заменить каждое a в конце слова этого значения на b, и заменить полученную строку.

Когда мы говорим "в конце строки", мы имеем в виду, что это либо после пробела, либо в конце значения, чтобы произошла замена; другие вхождения в значении остаются неизмененными. Например:

foo := a.o b.o l.a c.o
bar := $(foo:.o=.c)

.. установит 'bar' в 'a.c b.c l.a c.c'. См. 6.5. Установка переменных.

Подстановочная ссылка это сокращение для функции расширения patsubst (см. 8.2. Функции для подстановки и анализа строк): '$(var:a=b)' является эквивалентом '$(patsubst %a,%b,var)'. Разработчики make предоставили как подстановочные ссылки, так и patsubst для обеспечения совместимости с другими реализациями.

Другой тип подстановочный ссылки позволяет вам полностью воспользоваться возможностями функции patsubst. Он имеет ту же самую форму, что и описанная выше '$(var:a=b)', за исключением того, что должна содержать одиночный символ '%'. Этот случай эквивалентен '$(patsubst a,b,$(var))', см. 8.2. Функции для подстановки и анализа строк для описания функции patsubst. Например:

foo := a.o b.o l.a c.o
bar := $(foo:%.o=%.c)

.. установит 'bar' в значение 'a.c b.c l.a c.c'.

6.3.2. Вычисленные имена переменных. Это продвинутая концепция, очень полезная в более продвинутом программировании файла makefile. В простых ситуациях это не понадобится, однако может оказаться очень полезным.

К переменным можно обращаться изнутри имени переменной. Это называется вычисленным именем переменной или вложенной ссылкой на переменную. Например,

x = y
y = z
a := $($(x))

.. определит a как 'z': здесь '$(x)' внутри '$($(x))' расширяется в 'y', так что '$($(x))' расширяется в '$(y)', что в свою очередь расширяется в 'z'. Здесь имя переменной для ссылки не указывается явно; оно вычисляется путем расширения '$(x)'. Ссылка '$(x)' в этом примере вложена во внешнюю ссылку на переменную.

Предыдущий пример показывает два уровня вложенности, однако возможно любое количество вложений. Вот например, три уровня вложенности:

x = y
y = z
z = u
a := $($($(x)))

В этом примере внутреннее '$(x)' расширяется в 'y', так что '$($(x))' расширится в '$(y)', которое в свою очередь расширится в 'z'; теперь у нас получится '$(z)', что превращается в 'u'.

Ссылки на рекурсивно расширяемые переменные внутри имени переменной повторно расширяются подобным образом. Например:

x = $(y)
y = z
z = Hello
a := $($(x))

.. определит a как 'Hello': '$($(x))' становится '$($(y)), что становится '$(z)', что становится 'Hello'.

Вложенные ссылки на переменные могут также содержать измененные ссылки и вызовы функций (см. 8. Функции для преобразования текста), точно так же, как любая другая ссылка. Например, при использовании функции subst (см. 8.2. Функции для подстановки и анализа строк):

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

.. в конечном счете определит a как 'Hello'. Сомнительно, что кто-то когда-нибудь захочет написать настолько запутанную вложенную ссылку, как эта, но она работает: '$($($(z)))' расширится в '$($(y))', что превратится в '$($(subst 1,2,$(x)))'. Это получит значение 'variable1' из x, и поменяется подстановкой 'variable2', так что вся строка станет '$(variable2)', простой ссылкой на переменную, у которой значение 'Hello'.

Имя вычисляемой переменной не обязательно должно полностью состоять из ссылки на одну переменную. Оно может содержать как несколько ссылок на переменную, так и инвариантный текст. Например,

a_dirs := dira dirb
1_dirs := dir1 dir2
a_files := filea fileb 1_files := file1 file2

ifeq "$(use_a)" "yes" a1 := a
else a1 := 1
endif

ifeq "$(use_dirs)" "yes" df := dirs
else df := files
endif
dirs := $($(a1)_$(df))

.. даст dirs такое же значение, как a_dirs, 1_dirs, a_files или 1_files, в зависимости от установок use_a и use_dirs.

Вычисляемые имена переменных могут быть также полезны в подстановочных ссылках:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)

.. что определить sources либо как 'a.c b.c c.c', либо как '1.c 2.c 3.c', в зависимости от значения a1.

Единственное ограничение такого рода использования ссылок на вложенные переменные заключается в том, что они не могут указывать часть имени вызываемой функции. Это связано с тем, что проверка для распознанного имени функции выполняется до расширения вложенных ссылок. Например,

ifdef do_sort
func := sortelse
func := stripendif
bar := a d b g q c
foo := $($(func) $(bar))

.. попытается дать 'foo' значение 'sort a d b g q c' или 'strip a d b g q c' вместо того, чтобы дать 'a d b g q c' в качестве аргумента для функции sort или strip. Это ограничение будет снято в будущем, если будет доказано, что изменение окажется хорошей идеей.

Вычисляемые имена переменных также можно использовать в левой части присваивания переменной или в директиве define, как в следующем примере:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print = lpr $($(dir)_sources)
endef

Этот пример определит переменные 'dir', 'foo_sources' и 'foo_print'.

Обратите внимание, что вложенные ссылки на переменную значительно отличаются от рекурсивно расширяемых переменных (см. 6.2. Две разновидности (flavor) переменных), хотя они обе используются вместе при сложных способах программирования makefile.

6.4. Как переменные получают свои значения

Переменные могут получать значения несколькими различными способами:

• Вы можете указать изменяющее значение, когда запускаете make. См. 9.5. Переназначение переменных.
• Вы можете указать значение в makefile, либо с присваиванием (см. 6.5. Установка переменных), либо дословным определением (см. 6.8. Определение многострочных переменных).
• Вы можете указать значение с коротким временем жизни с помощью функции let (см. 8.5. Функция let) или с помощью функции foreach (см. 8.6. Функция foreach).
• Переменные в окружении становятся переменными make. См. 6.10. Переменные из окружения.
• Нескольким автоматическим переменным присваиваются новые значения для каждого правила. Каждая из них имеет одиночное обычное использование. См. 10.5.3. Автоматические переменные make.
• Некоторые переменные имеют постоянные начальные значения. См. 10.3. Переменные, используемые неявными правилами.

6.5. Установка переменных

Для установки переменной из makefile напишите строку, начинающуюся с имени переменной, за которым идет оператор присваивания '=', ':=', '::=' или ':::='. Все, что идет за оператором присваивания и любым начальным пробелом, становится значением переменной. Например,

objects = main.o foo.o bar.o utils.o

.. определит переменную с именем objects, содержащую значение 'main.o foo.o bar.o utils.o'. Пробелы вокруг имени переменной и непосредственно после '=' игнорируются.

Переменные, определенные с помощью '=', являются рекурсивно расширяемыми. Переменные, определенные с помощью ':=' или '::=' являются простыми расширяемыми; эти определения могут содержать ссылки на переменные, которые будут расширены перед созданием определения. Переменные, определенные с помощью ':::=' являются немедленно расширяемыми. Отличия операторов присваивания описано в секции 6.2. Две разновидности (flavor) переменных.

Имя переменной может содержать ссылки на функцию и переменную, которые расширяются в момент, когда строка считывается, чтобы найти реальное имя переменной для использования.

Не существует ограничения на длину значения переменной, кроме объема памяти компьютера. Вы можете разделить значение переменной на несколько физических строк для удобства чтения (см. 3.1.1. Разбиение длинных строк).

Большинство имен переменных считаются как имеющие значение пустой строки, если вы никогда не устанавливали их значение. Некоторые переменные имеют изначальные встроенные не пустые значения, однако вы можете установить их обычными способами (см. 10.3. Переменные, используемые неявными правилами). Некоторые специальные переменные устанавливаются автоматически в новое значение для каждого правила; эти переменные называются автоматическими (см. 10.5.3. Автоматические переменные make).

Если вы хотите установить переменную, если она еще не была установлена, то используйте сокращенный оператор '?=' вместо '='. Например, следующий вариант установки переменной 'FOO':

FOO ?= bar

.. полностью идентичен следующей установке:

ifeq ($(origin FOO), undefined)
FOO = bar
endif

Оператор присваивания шелла '!=' может использоваться для выполнения скрипта шелла и установки переменной для его вывода. Этот оператор сначала вычисляет правую сторону, затем передает результат шеллу для выполнения. Если результат выполнения заканчивается на новую строку, то один символ новой строки удаляется; все другие символы новой строки заменяются на пробелы. Затем результирующая строка помещается в именованную рекурсивно расширяемую переменную. Например:

hash != printf '\043'
file_list != find . -name '*.c'

Если результат выполнения может привести в к $, и вы не собираетесь интерпретировать это как ссылку на переменную или функцию make, то вы должны заменить каждый $ на $$ как часть выполнения. Альтернативно вы можете установить просто расширяемую переменную в результат запуска программы, используя вызов функции shell, см. 8.14. Функция shell. Например:

hash := $(shell printf '\043')
var := $(shell find . -name "*.c")

Как и в случае с функцией shell, статус выхода только что вызванного скрипта шелла сохраняется в переменной .SHELLSTATUS.

6.6. Добавление текста к переменным

Часто полезно добавлять дополнительный текст к значению переменной, которая уже была определена. Вы можете это сделать строкой, содержащей '+=', примерно так:

objects += another.o

Эта строка берет значение переменной objects и добавляет к нему текст 'another.o' (с подставленным спереди одиночным пробелом, если у переменной уже присвоено значение). Таким образом:

objects = main.o foo.o bar.o utils.o
objects += another.o

.. установит переменную objects в значение 'main.o foo.o bar.o utils.o another.o'.

Использование '+=' подобно следующему:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

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

Когда рассматриваемая переменная не была определена ранее, '+=' действует только как и обычный '=': он определит рекурсивно расширяемую переменную. Однако когда было предыдущее определение, точное поведение '+=' зависит от того, каким flavor переменная была определена изначально, см. 6.2. Две разновидности (flavor) переменных.

Когда вы добавляете значение к переменной с помощью '+=', make действует по существу так, как если бы вы включили бы дополнительный текст в изначальное определение переменной. Если вы определили ранее эту переменную с помощью ':=' или '::=', сделав этим её просто расширяемой переменной, то '+=' сделает добавление к просто расширенному определению, и расширит новый текст перед добавлением его к старому значению, точно так же, как это делает ':=' (см. 6.5. Установка переменных для полного объяснения ':=' или '::='). Фактически,

variable := value
variable += more

.. полностью эквивалентно следующему:

variable := value
variable := $(variable) more

С другой стороны, когда вы используете '+=' с переменной, которую вы сначала определили как рекурсивно расширяемую с помощью обычного '=' или ':::=', make добавит нерасширенный текст к существующему значению, каким бы оно ни было. Это означает, что

variable = value
variable += more

.. является грубым эквивалентом следующего:

temp = value
variable = $(temp) more

.. за исключением того, что он никогда не определяет переменную с именем temp. Это становится важным, когда старое значение содержит ссылки на переменную. Возьмем общий пример:

CFLAGS = $(includes) -O
...
CFLAGS += -pg # enable profiling

Первая строка определяет переменную CFLAGS с помощью ссылки на другую переменную includes (CFLAGS используется правилами компиляции для кода C; см. 10.2. Каталог встроенных правил). Использование '=' для определения делает переменную CFLAGS рекурсивно расширяемой, т. е. '$(includes) -O' не расширяется, когда make обрабатывает определение CFLAGS. Таким образом, пока что переменная includes не должна быть определена, чтобы её значение оказала действие. Она должна быть определена только перед любой ссылкой на CFLAGS. Если мы попытаемся добавить значение CFLAGS без использования '+=', то можем сделать что-то подобное:

CFLAGS := $(CFLAGS) -pg # enable profiling

Это очень близко, но не совсем то, что мы хотели бы получить. Использование ':=' переопределяет CFLAGS как просто расширяемую переменную; это означает, что make расширит текст '$(CFLAGS) -pg' перед установкой переменной. Если переменная includes еще не была определена, то мы получим ' -O -pg', и последующее определение переменной includes не даст эффекта. И наоборот, используя '+=', мы установим CFLAGS нерасширенное значение '$(includes) -O -pg'. Тем самым мы сохраним ссылку на переменную includes, так что если она получит определение позже, то ссылка наподобие '$(CFLAGS)' все еще будет использовать её значение.

6.7. Директива override

Если переменная была установлена аргументом командной строки make (см. 9.5. Переназначение переменных), то её обычные последующие присваивания игнорируются. Если вы хотите установить переменную в makefile, даже если она была установлена аргументом командной строки, то можете использовать директиву override, строка которой выглядит примерно так:

override variable = value

.. или так:

override variable := value

Чтобы добавить еще текст к переменной, определенной в командной строке, используйте (см. 6.6. Добавление текста к переменным):

override variable += more text

Присваивания переменной, помеченное флагом override, имеет более высокий приоритет, чем все другие присваивания, кроме другого override. Последующие присваивания или добавления к этой переменной, помеченной, не помеченные override, будут игнорироваться.

Директива override не была введена для эскалации в войнах между файлами makefile и аргументами командной строки. Она была добавлена для того, чтобы вы могли изменять значения переменной и добавлять к ним что-нибудь, когда пользователь присвоил что-нибудь этой переменной через аргументы командной строки.

Для примера предположим, что вы всегда хотите применять опцию '-g', когда запускаете компилятор C, однако хотели бы позволить пользователю указать другие опции командной строки, как обычно. Для этого вам нужно использовать директиву override:

override CFLAGS += -g

Вы можете также использовать директивы override вместе с директивами define. Это делается ожидаемо:

override define foo =
barendef

6.8. Определение многострочных переменных

Еще один способ установки значения переменной - использовать директиву define. Эта директива имеет необычный синтаксис, который позволяет включать символы новой строки (newline) в значение переменной, что удобно как для определения фиксированных (canned) последовательностей команд (см. 5.8. Определение фиксированных (canned) рецептов), так и для определения секций синтаксиса makefile для использования совместно с eval (см. 8.10. Функция eval).

За директивой define на той же строки идет имя определяемой переменной и (опционально) оператор присваивания, и ничего больше. Значение для предоставления переменной появляется на последующих строках. Конец значения помечается отдельной строкой, содержащей только слово endef.

Помимо этого отличия в синтаксисе, define работает так же, как и любое другое определение переменной. Имя переменной может содержать ссылки на функции и переменные, которые расширяются, когда директива считывается, чтобы найти фактическое имя переменной.

Последний символ новой строки (newline) перед endef не включается в значение; если вы хотите, чтобы значение содержало завершающий символ newline, то вы должны добавить в конец пустую строку. Ниже показан пример определения переменной, где содержится только один символ новой строки. Для его определения с помощью define нужно в его тело поместить две пустые строки, а не одну:

define newline


endef

Вы можете, если хотите, опустить оператор присваивания. Если он опущен, то make подразумевает его как '=', и создает рекурсивно расширяемую переменную (см. 6.2. Две разновидности (flavor) переменных). Когда используется оператор '+=', значение добавляется к предыдущему значению, как с любым другим оператором присоединения: с одиночным пробелом, отделяющим старое и новое значения.

Вы можете вкладывать директивы define друг в друга: make будет отслеживать вложенность директив и сообщать об ошибке, если они не все были корректно закрыты endef. Обратите внимание, что строки, начинающиеся с символа префикса рецепта (обычно символ табуляции) считаются частью рецепта, так что любые строки define или endef, появляющиеся на такой строке, не будут считаться директивами make. Следующий пример при использовании в рецепте:

define two-lines
echo foo
echo $(bar)
endef

.. будет функционально эквивалентен:

two-lines = echo foo; echo $(bar)

.. поскольку две команды, отделенные друг от друга символом точки с запятой, ведут себя как две отдельные команды шелла. Однако имейте в виду, что использование двух отдельных строк приведет к тому, что make будет вызвать шелл дважды, запуская независимый sub-шелл для каждой строки. См. 5.3. Выполнение рецепта.

Если вы хотите, чтобы определения переменной, сделанные через define, получили приоритет перед определением переменных в командной строке, то можете использовать директиву override вместе с директивой define:

override define two-lines =
foo
$(bar)
endef

См. 6.7. Директива override.

6.9. Отмена определений переменной

Если вы хотите очистить переменную, то обычно достаточно установить её в пустое значение. Расширение такой переменной даст один и тот же результат (пустую строку) независимо от того, была ли переменная установлена или нет. Однако, если вы используете функцию flavor (см. 8.12 The flavor Function) и функцию origin (см. 8.11 The origin Function), то есть отличие между переменной, которая никогда не устанавливалась, и переменной с пустым значением. В таких ситуациях вы можете захотеть использовать директиву undefine, чтобы переменная оказалась такой, как если бы они никогда не была установлена. Например:

foo := foo
bar = bar

undefine foo
undefine bar

$(info $(origin foo))
$(info $(flavor bar))

Этот пример напечатает "undefined" для обоих переменных.

Если вы хотите отменить определение командной строки, то можете использовать директиву override вместе с undefine, подобно тому, как это делается для определений переменной:

override undefine CFLAGS

6.10. Переменные из окружения

Переменные make могут поступать из окружения, а котором работает make. Каждая переменная окружения, которую make видит при своем запуске преобразуется в переменную make с тем же именем и значением. Однако явное присваивание переменной в makefile, или через аргументы командной строки, переназначает значение из окружения (если указана опция '-e', то значения из окружения переназначают назначения в makefile. См. 9.8. Сводка по опциям make. Однако это не будет рекомендуемой практикой).

Таким образом, установкой переменной CFLAGS в вашем окружении вы можете заставить все компиляции C в большинстве файлов makefile использовать предпочитаемые вами опции командной строки компилятора. Это безопасно для переменных со стандартными или обычными значениями, потому что вы знаете, что ни один makefile не будет использовать CFLAGS для других вещей (имейте в виду, что это не будет абсолютно надежным; некоторые файлы makefile явно установят CFLAGS, и поэтому они не зависят от её значения в окружении).

Когда make запустит рецепт, некоторые переменные, определенные в makefile, помещаются в окружение каждой команды, которую вовлекает make. По умолчанию любые переменные, которые поступают из окружения make или устанавливаются из командной строки, помещаются в окружение этих команд. Вы можете использовать директиву export, чтобы передать другие переменные variables. Для подробностей см. 5.7.2. Передача переменных в sub-make.

Другое использование переменных из окружения не рекомендуется. Нецелесообразно, чтобы файлы makefile зависели в своем функционировании от их переменных окружения, установленных вне их контроля, поскольку это может привести к разным результатам у разных пользователей одного и того же makefile. А это противоречит основному применению большинства файлов makefile.

Подобные проблемы были бы особенно вероятны с переменной SHELL, которая обычно присутствует в окружении, чтобы указать пользовательский выбор интерактивного шелла. Очень нежелательно, чтобы этот выбор влиял на make; утилита make обрабатывает переменную окружения SHELL специальным образом; см. 5.3.2. Выбор шелла.

6.11. Значения переменных, специфичных для target

Значения переменных в make обычно глобальны; т. е. они одинаковые независимо от того, где вычисляются (конечно, если они не сбрасываются). Исключениями являются переменные, определенные функцией let (см. 8.5. Функция let) или функцией foreach (см. 8.6. Функция foreach), и автоматические переменные (см. 10.5.3. Автоматические переменные make).

Другое исключение - значения переменных, специфических для цели, или целевых переменных (target-specific variable values). Эта фича позволяет вам определить различные значения для одной и той же переменной, основываясь на цели, которую make собирает в настоящий момент. Как и с автоматическими переменными, эти значения доступны только в контексте рецепта цели (и в других специфичных для цели присваиваниях).

Установка специфической для цели переменной () выглядит следующим образом:

target ... : variable-assignment

Присваивания специфичной для цели переменной могут снабжаться префиксом с любым или всеми специальными ключевыми словами export, unexport, override или private; они применяют свое нормальное поведение только к этому экземпляру переменной.

Несколько целевых значений создают значение переменной, специфичной для цели, индивидуально для каждого члена списка цели.

Здесь variable-assignment может быть любой допустимой формой присваивания: рекурсивная ('='), простая (':=' или '::='), немедленная (':::='), добавляемая ('+=') или условная ('?='). Все переменные, которые появляются внутри variable-assignment, вычисляются в контексте целевого объекта: таким образом, будет давать эффект любое ранее определенное значение целевой переменной. Обратите внимание, что эта переменная фактически отличается от любого "глобального" значения: две переменные не должны иметь одинаковый flavor (рекурсивная переменная против простой переменной).

Специфические для цели переменные имеют такой же приоритет, что и любая другая переменная makefile. Переменные, предоставленные через командную строку (и в окружении, если действует опция '-e') будут иметь приоритет. Указание директивы override позволит отдать предпочтение значению целевой переменной.

Существует еще одна специальная фича целевых переменных: когда вы определили целевую переменную, её значение также действует для всех prerequisites этой цели, и все их prerequisites, и так далее (если эти prerequisites не переопределяют эту переменную их собственным значением переменной, специфичной для цели). Так, например, следующие утверждение:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o

.. установит CFLAGS значениеv '-g' в рецепте для prog, однако это также установит CFLAGS значением '-g' в рецептах, которые создают prog.o, foo.o и bar.o, и любых рецептах, которые создают их prerequisites.

Имейте в виду, что эта prerequisite будет собираться не чаще одного раза на вызов make. Если один и тот же файл является prerequisite нескольких целей, и каждая из этих целей имеет другое значение для целевой переменной, то первый построенный целевой объект приведет к созданию этого prerequisite, и оно будет наследовать значение целевой переменной от первого целевого объекта. Это приведет к игнорированию значений целевой переменной любых других целей.

6.12. Значения переменных, специфичных для шаблона

В дополнение к целевым переменным (см. 6.11. Значения переменных, специфичных для target), GNU make поддерживает значения переменных шаблона (pattern-specific variable values). В этой форме переменная определяется для любого целевого объекта, который совпадет с указанным шаблоном (pattern).

Значение переменной, специфичной для шаблона, устанавливается следующим образом:

pattern ... : variable-assignment

.. где pattern указывается в виде %-шаблона. Как и в случае значений целевых переменных, несколько шаблонных значений шаблона создают специфическое для шаблона значение переменной индивидуально для каждого шаблона. Здесь variable-assignment может быть любой допустимой формой присваивания. Любая установка переменной через командную строку будет иметь приоритет, если не применена директива override.

Например:

%.o : CFLAGS = -O

.. назначит CFLAGS значением '-O' для всех целей, совпавших с шаблоном %.o.

Если целевой объект совпадет с более чем одним шаблоном, то первым интерпретируются шаблонные переменные, у которых самая длинная основа шаблона (stem). Это приводит к тому, что более специфичные переменные имеют приоритет над более общими, например:

%.o: %.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
lib/%.o: CFLAGS := -fPIC -g %.o: CFLAGS := -g
all: foo.o lib/bar.o

В этом примере первое определение переменной CFLAGS будет использоваться для обновления lib/bar.o, хотя даже второе определение также применяется к этому целевому объекту. Шаблонные переменные, у которых одинаковый stem шаблона, будут обрабатываться в том порядке, в каком они определены в makefile.

Шаблонные переменные ищутся позже любых целевых переменных, явно определенных для этого целевого объекта, и перед целевыми переменными, определенными для родительского целевого объекта.

6.13. Подавление наследования переменных

Как описывалось в предыдущих секциях, переменные make наследуются предпосылками (prerequisites). Эта возможность позволяет вам модифицировать поведение prerequisite на основе того, какой целевой объект привел к пересборке prerequisite. Например, вы можете установить целевую переменную на целевом объекте debug, тогда запуск 'make debug' приведет к тому, что эта переменная наследуется всеми prerequisites для debug, хотя простой запуск 'make all' (для примера) не будет иметь этого присваивания.

Однако иногда вы можете не захотеть, чтобы переменная наследовалась. Для этих ситуаций make предоставляет модификатор private. Хотя этот модификатор может использоваться с любым присваиванием переменной, он более удобен для переменных, специфичных для целевых объектов и шаблонов целевых объектов. Любая переменная, помеченная как private, будет видна локальному целевому объекту, но не будет наследоваться prerequisites этого целевого объекта. Глобальная переменная, помеченная private, будет видна в глобальной области, но не будет наследоваться любым целевым объектом, и таким образом, не будет видна в любом рецепте.

В качестве примера рассмотрим такой makefile:

EXTRA_CFLAGS =
prog: private EXTRA_CFLAGS = -L/usr/local/lib prog: a.o b.o

Из-за модификатора private a.o и b.o не наследуют присваивание значения переменной EXTRA_CFLAGS из цели prog.

6.14. Другие специальные переменные

GNU make поддерживает некоторые переменные со специальными свойствами.

MAKEFILE_LIST

Содержит имя каждого файла makefile, который парсится make, в том порядке, в котором приходил парсинг. Имя добавляется непосредственно перед тем, как make начинает парсинг этого makefile. Таким образом, если первая сущность makefile делает проверку последнего слова в этой переменной, то это будет имя текущего makefile. Однако как только текущий makefile использовал include, последнее слово этой переменной будет именем только что подключенного makefile.

Если у файла makefile имя Makefile, и у него следующее содержание:

name1 := $(lastword $(MAKEFILE_LIST))
include inc.mk
name2 := $(lastword $(MAKEFILE_LIST))
all: @echo name1 = $(name1) @echo name2 = $(name2)

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

name1 = Makefile
name2 = inc.mk
.DEFAULT_GOAL

Установит используемую цель по умолчанию (default goal), если в командной строке не были указаны цели (см. 9.2. Аргументы, указывающие goals). Переменная .DEFAULT_GOAL позволяет вам раскрыть текущую цель по умолчанию, перезапустить алгоритм выбора цели по умолчанию путем очистки значения переменной .DEFAULT_GOAL, или явно установить цель по умолчанию. Следующий пример иллюстрирует эти случаи:

# Запрос default goal.
ifeq ($(.DEFAULT_GOAL),)
  $(warning no default goal is set)
endif
.PHONY: foo foo: ; @echo $@
$(warning default goal is $(.DEFAULT_GOAL))
# Сброс default goal. .DEFAULT_GOAL :=
.PHONY: bar bar: ; @echo $@
$(warning default goal is $(.DEFAULT_GOAL))
# Установка самого себя. .DEFAULT_GOAL := foo

Этот makefile напечатает:

no default goal is set
default goal is foo
default goal is bar
foo

Обратите внимание, что недопустимо присвоение больше одного имени цели переменной .DEFAULT_GOAL, что приведет к ошибке.

MAKE_RESTARTS

Эта переменная установится только если этот экземпляр make был перезапущен (см. 3.5. Как заново создаются файлы Makefile): она будет содержать количество раз, сколько этот экземпляр make был перезапущен. Имейте в виду, что это не то же самое, что и рекурсия (которая учитывается переменной MAKELEVEL). Вы не должны устанавливать, модифицировать или экспортировать эту переменную.

MAKE_TERMOUT
MAKE_TERMERR

При запуске make проверяется, будут ли показывать stdout и stderr свои данные в терминале. Если так, то это установит соответственно значения переменных MAKE_TERMOUT и MAKE_TERMERR в имя устройства терминала (или в true, если устройство терминала не может быть определено). Если эти переменные установлены, то они будут помечены для экспорта. Эти переменные на будут изменены make, и они не будут изменены, если уже установлены.

Эти значения могут использоваться (в частности в комбинации с синхронизацией вывода, см. 5.4.2. Вывод при параллельном выполнении), чтобы определить, выполняет ли сама make запись в терминал; например они могут быть протестированы, чтобы решить, следует ли принудительно использовать команды рецепта для генерации раскрашенного вывода.

Если вы вовлекаете вызовы sub-make и перенаправляете их stdout или stderr, то в вашей зоне ответственности сбросить или применить unexport этих переменных, если ваши файлы makefile полагаются на них.

.RECIPEPREFIX

Первый символ значения этой переменной используется как символ, который make подразумевает как маркер строки рецепта. Если эта переменная пустая (что имеет место по умолчанию), то этим символом является стандартный символ табуляции. Например, вот пример допустимого makefile:

.RECIPEPREFIX = >
all:
> @echo Hello, world

Значение .RECIPEPREFIX может быть изменено несколько раз; будучи установленным, оно остается в силе для всех анализируемых правил, пока не будет изменено.

.VARIABLES

Расширяется в список имен всех глобальных переменных, определенных на данный момент. Это включает как переменные, у которых пустые значения, так и встроенные переменные (см. 10.3. Переменные, используемые неявными правилами), однако не включает любые переменные, которые определены только в специфическом для цели контексте. Обратите внимание, что любое значение, которое вы присвоите этой переменной, будет игнорироваться; она всегда будет возвращать свое специальное значение.

.FEATURES

Расширяется в список специальных фич, поддерживаемых этой версией make. Возможные значения включают, но не ограничиваются, следующим списком:

'archives' Поддержка ar-файлов (архив), использующая специальный синтаксис имен файлов. См. 11. Использование make для обновления файлов архива (библиотеки).

'check-symlink' Поддержка опции -L (--check-symlink-times). См. 9.8. Сводка по опциям make.

'else-if' Поддержка "else if" не вложенных проверок условий. См. 7.2. Синтаксис проверок условия.

'extra-prereqs' Поддержка специальной цели .EXTRA_PREREQS.

'grouped-target' Поддержка синтаксиса группировки целей для явных правил. См. 4.10. Несколько целей в правиле.

'guile' Имеет GNU Guile, доступный как встроенный язык расширения. См. 12.1. Интеграция GNU Guile.

'jobserver' Поддержка улучшенных параллельных сборок "job server". См. 5.4. Параллельное выполнение.

'jobserver-fifo' Поддержка улучшенных параллельных сборок "job server" с использованием именованных каналов (named pipes). См. 13. Интеграция GNU make.

'load' Поддержка динамически загружаемых объектов для создания пользовательских расширений. См. 12.2. Загрузка динамических объектов.

'notintermediate' Поддержка специальной цели .NOTINTERMEDIATE, см. 13. Интеграция GNU make.

'oneshell' Поддержка специальной цели .ONESHELL, см. 5.3.1. Использование одного шелла.

'order-only' Поддержка заказных prerequisites. См. 4.3. Типы prerequisites.

'output-sync' Поддержка опции командной строки --output-sync, см. 9.8. Сводка по опциям make.

'second-expansion' Поддержка вторичного расширения (secondary expansion) списков prerequisite.

'shell-export' Поддержка экспорта переменных make в функции shell.

'shortest-stem' Использует метод "самой короткой базы" (shortest stem) для выбора, какой из шаблонов нескольких применимых опций будет использоваться. См. 10.5.4. Как работает совпадение шаблонов.

'target-specific' Поддержка присваивания специфичных для целевого объекта (target-specific) переменных и специфичных для шаблона (pattern-specific) переменных. См. 6.11. Значения переменных, специфичных для target и 6.12. Значения переменных, специфичных для шаблона.

'undefine' Поддержка директивы undefine, см. 6.9. Отмена определений переменной.

.INCLUDE_DIRS

Расширяется в список директорий, в которых make ищет подключаемые файлы makefile (см. 3.3. Подключение других файлов Makefile). Обратите внимание, что модификация значения этой переменной не поменяет список просматриваемых директорий.

.EXTRA_PREREQS

Каждое слово в этой переменной задает новую prerequisite, которая добавляется к целям, для которых она установлена. Эти prerequisites отличаются от обычных prerequisites тем, что они не отображаются ни в одной из автоматических переменных (см. 10.5.3. Автоматические переменные make). Это позволяет определять prerequisites, которые не влияют на рецепт.

Рассмотрим правило для линковки программы:

myprog: myprog.o file1.o file2.o
       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)

Теперь предположим, что вы хотите улучшить этот makefile, чтобы обновления компилятора приводили к повторной линковке программы. Вы можете добавить компилятор как prerequisite, однако вы должны гарантировать, что он не будет передаваться в качестве аргумента команде линковки. Вам понадобится что-то наподобие:

myprog: myprog.o file1.o file2.o $(CC)
       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ \
           $(filter-out $(CC),$^) $(LDLIBS)

Теперь подумайте о наличии нескольких дополнительных prerequisites: все они должны быть отфильтрованы. Использование .EXTRA_PREREQS и target-specific переменных предоставит более простое решение:

myprog: myprog.o file1.o file2.o
       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
myprog: .EXTRA_PREREQS = $(CC)

Эта фича также полезна, если вы хотите добавить prerequisites в makefile, который вы не можете легко изменить. Вы можете создать новый файл, такой как extra.mk:

myprog: .EXTRA_PREREQS = $(CC)

.. затем примените его командой make -f extra.mk -f Makefile.

Установка .EXTRA_PREREQS глобально приведет к тому, что её prerequisites добавятся ко всем целям (которые сами не переопределили их target-specific значением). Обратите внимание, что утилита make достаточно умна, чтобы не добавлять prerequisite, перечисленную в .EXTRA_PREREQS, как prerequisite самой себе.

[Ссылки]

1. GNU make.
2. Руководство GNU make: главы 1-3.
3. GCC Options Controlling the Preprocessor site:gcc.gnu.org.
4. Как устроен Makefile и что это такое?
5Makefile: ошибки в списке команд команд цели (recipe errors).

 

Добавить комментарий


Защитный код
Обновить

Top of Page