КаталогИндекс раздела
НазадОглавлениеВперед


MAKE

СОДЕРЖАНИЕ

1. Введение

2. Основные возможности

3. Файлы описаний и подстановки
    3.1. Комментарии
    3.2. Строки продолжения
    3.3. Макроопределения
    3.4. Общий вид файла описаний
    3.5. Информация о зависимостях
    3.6. Исполняемые команды
    3.7. Усовершенствования $*, $@, $<
    3.8. Выходные преобразования макросов

4. Рекурсивные make-файлы
    4.1. Суффиксы и правила трансформации
    4.2. Подразумеваемые правила
    4.3. Архивные библиотеки

5. Имена SCCS-файлов (тильда)
    5.1. Пустой суффикс
    5.2. Включаемые файлы
    5.3. make-файлы в рамках SCCS
    5.4. Динамические параметры зависимостей

6. Запуск утилиты make
    6.1. Командная строка
    6.2. Переменные окружения

7. Советы и предостережения

8. Встроенные правила


 

1. ВВЕДЕНИЕ

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

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

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

Файлу описаний, задающему информацию о межфайловых зависимостях и последовательностях команд для порождения файлов, принято давать имя makefile, Makefile или s.[mM]akefile. Если следовать этому соглашению, для достижения цели чаще всего оказывается достаточно просто набрать

       make

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

Имеется одно применение утилиты make, с которым приходится сталкиваться всем системным программистам - это перегенерация ОС UNIX. В каталоге /usr/src/uts находится файл описаний с именем Makefile, управляющий процессом перегенерации. Изучение подобного реального примера должно способствовать лучшему пониманию данного описания.

 

2. ОСНОВНЫЕ ВОЗМОЖНОСТИ

Основное действие утилиты make - обновление целевого файла при условии, что все файлы, от которых зависит целевой, существуют и не являются устаревшими. Целевой файл порождается заново, если названные файлы модифицированы, а целевой - нет. Утилита make исследует граф зависимостей. Функционирование make'а основывается на анализе времени последней модификации файлов.

Утилита make действует, опираясь на три источника информации:

В качестве иллюстрации рассмотрим следующий простой пример. Программа prog получается из трех исходных файлов x.c, y.c и z.c путем их компиляции и редактированием связей совместно с библиотекой math. В соответствии с принятыми соглашениями результат работы C-компилятора будет помещен в файлы с именами x.o, y.o и z.o. Предположим также, что файлы x.c и y.c используют общие описания из включаемого файла defs.h, а z.c - не использует. Пусть x.c и y.c содержат строку

       #include "defs.h"

Следующая спецификация описывает взаимосвязи и операции:

       prog :  x.o  y.o  z.o
               cc  x.o  y.o  z.o  -lm  -o  prog
       x.o  y.o :  defs.h

Если эту информацию поместить в файл с именем makefile, команда

       make

будет выполнять операции, необходимые для перегенерации prog после любых изменений, сделанных в каком-либо из четырех исходных файлов x.c, y.c, z.c или defs.h. В приведенном выше примере в первой строке утверждается, что prog зависит от трех .o-файлов. Если эти файлы имеются в наличии, рассматривается вторая строка, которая описывает, как отредактировать связи между ними, чтобы создать prog. Третья строка гласит, что x.o и y.o зависят от defs.h. Обратившись к файловой системе, make обнаруживает, что имеются три .c-файла, соответствующие требуемым .o- файлам, и применяет встроенные правила порождения объектного файла из исходного C-файла (то есть выполняет команду cc -c).

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

       prog :  x.o  y.o  z.o
               cc  x.o  y.o  z.o  -lm  -o  prog
       x.o :  x.c  defs.h
               cc  -c  x.c
       y.o :  y.c  defs.h
               cc  -c  y.c
       z.o :  z.c
               cc  -c  z.c

Если ни один из исходных или объектных файлов не был изменен с момента последнего порождения prog и все файлы в наличии, утилита make известит об этом и прекратит работу. Если, однако, файл defs.h отредактировать, x.c и y.c (но не z.c) будут перекомпилированы; затем из новых файлов x.o и y.o и уже существую- щего файла z.o будет заново собрана программа prog. Если изменен лишь файл y.c, только он и перекомпилируется, после чего последует пересборка prog. Если в командной строке make не за- дано имя цели, создается первый упомянутый в описании целевой файл; в противном случае создаются специфицированные целевые файлы. Команда

       make x.o

будет перегенерировать x.o, если изменен x.c или defs.h.

Часто в файл описаний включают правила с мнемоническими именами и командами, которые в действительности не порождают файлов с соответствующими именами. Смысл в том, чтобы воспользоваться средствами make'а по генерации файлов и подстановке макросов (информацию о макросах см. в разделе ФАЙЛЫ ОПИСАНИЙ И ПОДСТАНОВКИ). Мнемонические имена играют роль точек входа, при обращении к которым выполняются определенные действия. Так, точка входа save может служить для копирования определенной совокупности файлов, а точка входа clean - для удаления ненужных промежуточных файлов.

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

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

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

       $(CFLAGS)
       $2
       $(xy)
       $Z
       $(Z)

Две последние строки эквивалентны.

$*, $@, $?, $< - это четыре специальных макроса, значения которых изменяются во время выполнения команды. Они описываются в разделе ФАЙЛЫ ОПИСАНИЙ И ПОДСТАНОВКИ. В следующем фрагменте показаны определения и использования некоторых макросов:

       OBJECTS = x.o y.o z.o
       LIBES = -lm
       prog: $(OBJECTS)
               cc  $(OBJECTS) $(LIBES)  -o prog
              . . .
Команда
       make LIBES="-ll -lm"

загружает три объектных файла вместе с библиотекой lex'а (-ll) и математической библиотекой (-lm), потому что в первую очередь используются макроопределения из командной строки, а не одноименные определения в файле описаний. (В командах ОС UNIX аргументы, содержащие пробелы, должны заключаться в кавычки).

В качестве примера использования make'а приведем файл описаний, который может применяться при сопровождении самой утилиты make. Исходный текст утилиты содержится в нескольких C-файлах, а также включает yacc-спецификацию грамматики.

       # Файл описаний для утилиты make
       
       FILES = Makefile defs.h main.h doname.c misc.c
               files.c dosys.c gram.y
       OBJECTS = main.o doname.o misc.o files.o
                 dosys.o gram.o
       LIBES = -lld
       LINTS = lint -p
       CFLAGS = -O
       LP = /usr/bin/lp
       
       make: $(OBJECTS)
              $(CC)  $(CFLAGS)  $(OBJECTS)  $(LIBES) -o make
       
       $(OBJECTS): defs.h
       
       cleanup:
               -rm *.o gram.c
               -du
       install:
               @size make /bin/make
               mv make /bin
       
       lint :  dosys.c doname.c files.c main.c misc.c gram.c
               $(LINT) dosys.c doname.c files.c main.c misc.c \
               gram.c
       
                  # Распечатать файлы, состояние которых не
                  # согласуется с "целевым файлом" print
       
       print:  $(FILES)
               pr $? | $(LP)
               touch print

Перед выполнением всякой команды утилита make распечатывает ее.

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

       cc -o -c main.c
       cc -o -c doname.c
       cc -o -c misc.c
       cc -o -c flies.c
       cc -o -c dosys.c
       yacc gram.y
       mv y.tab.c gram.c
       cc -o -c gram.c
       cc main.o doname.o misc.o files.o dosys.o gram.o -lld \
          -o make
       make: 24700 + 7100 + 18344 = 50144
       /bin/make: 25200 + 6900 + 18108 = 50208

Две последние строки являются результатом выполнения команды

       size make /bin/make

Печать самой командной строки подавляется знаком @ в файле описаний.

 

3. ФАЙЛЫ ОПИСАНИЙ И ПОДСТАНОВКИ

В данном разделе описываются основные компоненты файла описаний.

3.1. Комментарии

Признаком комментария, по соглашению, является символ #; все символы за ним до конца строки игнорируются. Пустые строки и строки, начинающиеся с #, игнорируются полностью.

3.2. Строки продолжения

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

3.3. Макроопределения

Макроопределение - это идентификатор, за которым следует знак равенства. Перед идентификатором не должно стоять двоеточия или табуляции. Имя (цепочка букв и/или цифр) слева от знака равенства (завершающие пробелы и табуляции отбрасываются) сопос- тавляется цепочке символов, следующей за знаком равенства (начальные пробелы и табуляции отбрасываются). Ниже приведены кор- ректные макроопределения:

       2 = xyz
       abc = -ll -ly -lm
       LIBES =

Последнее определение сопоставляет LIBES с пустой цепочкой. Макрос, нигде не определенный явным образом, имеет в качестве значения пустую цепочку. Напомним, однако, что некоторые макросы явно определяются во встроенных правилах утилиты make. (См. раздел ВСТРОЕННЫЕ ПРАВИЛА.)

3.4. Общий вид файла описаний

Общий вид точки входа в файле описаний таков:

       цель1  [цель2 ...]  :[:]  [зависимость1 ...]
       [; команды]  [# ...]    [\t команды ]  [# ...]

Указанные в скобках компоненты могут быть опущены, цели и зависимости являются цепочками из букв, цифр, символов . и /. При обработке строки интерпретируются метасимволы shell'а, такие как * и ?. Команды могут быть указаны после точки с запятой в строке зависимостей, или в строках, начинающихся с табуляции, которые следуют сразу за строкой зависимостей. Команда - это произвольная цепочка символов, не содержащая знак #, за исключением тех случаев, когда # заключен в кавычки.

3.5. Информация о зависимостях

В строке зависимостей может быть указан одинарный либо сдвоенный знак двоеточия. Целевое имя может встречаться более чем в одной строке зависимостей, однако все эти строки должны быть одного (либо с одинарным, либо со сдвоенным двоеточием) типа. В более распространенном случае (одинарное двоеточие) последовательность команд может быть сопоставлена не более чем одной строке зависимостей. Если целевой файл устарел по сравнению с какой-либо зависимостью в любой из этих строк и специфицирована последовательность команд (даже если после точки с запятой или табуляции идет пустая цепочка), данная последовательность команд выполняется; иначе может быть вызвано встроенное правило. В случае со сдвоенным двоеточием последовательность команд может быть сопоставлена более чем одной строке зависимостей. Если целевой файл устарел по сравнению с какой-либо зависимостью из одной из этих строк, выполняются соответствующие команды. Также может быть выполнено встроенное правило. Форма со сдвоенным двоеточием особенно полезна для обновления архивных файлов, где целевой файл - это сама архивная библиотека. (Соответствующий пример содержится в пункте Архивные библиотеки.)

3.6. Исполняемые команды

Если целевой файл должен быть создан, выполняется последовательность команд. Обычно каждая командная строка распечатывается и затем, после подстановки макросов, для ее выполнения запускается очередной экземпляр shell'а. Печать может быть подавлена в режиме "молчания" (опция -s утилиты make) или в том случае, если командная строка в файле описаний начинается со знака @. make обычно прекращает работу, если какая-либо команда сигнализирует об ошибке, возвращая ненулевой код завершения. Ошибки игнорируются, если в командной строке make'а указана опция -i, или если в файле описаний указано фиктивное целевое имя .IGNORE, или если командная строка в файле описаний начинается со знака минус. Если известно, что программа возвращает бессодержательное значение, полезно указывать минус перед строкой, ее запускающей. Поскольку каждая командная строка передается отдельному экземпляру shell'а, при использовании собственных команд shell'а [например, cd(1)] надо проявлять осторожность, потому что они имеют смысл только в пределах одного shell-процесса. Перед выполнением следующей строки результаты выполнения этих команд утрачиваются.

Перед вызовом любой команды устанавливаются некоторые встроенные макросы. Макрос $@ устанавливается равным полному имени текущего целевого файла. Он вычисляется только для явно указанных зависимостей. Макрос $? устанавливается равным цепочке имен файлов, которые оказались более свежими, чем целевой; он также вычисляется только при обработке явных правил make-файла. Если команда порождена неявным правилом, макрос $< равен имени файла, вызвавшего действие; макрос $* - префикс имени, общий для текущего файла и файла из строки зависимостей. Если файл должен быть получен, но нет явных команд или встроенных правил, используются команды, сопоставленные фиктивному целевому имени .DEFAULT. Если такого имени нет, make выдает сообщение и прекращает работу.

Кроме того, в файле описаний можно использовать следующие связанные с упомянутыми выше макросы: $(@D), $(@F), $(*D), $(*F), $(<D) и $(<F) (см. ниже).

3.7. Усовершенствования $*, $@, $<

Внутренние макросы $*, $@ и $< - полезные обозначения для текущих целевых файлов и устаревших файлов, связанных с целевыми. К этому списку следует дабавить следующие макросы: $(@D), $(@F), $(*D), $(*F), $(<D) и $(<F). Модификатор D обозначает маршрутную часть полного имени файла, соответствующего односимвольному макросу, а модификатор F - простое имя. Эти дополнительные макросы бывают полезны при построении иерархических make-файлов. Они позволяют получить доступ к имени каталога, чтобы воспользоваться командой cd(1). Например, можно написать

       cd $(<D); $(MAKE) $(<F)

3.8. Выходные преобразования макросов

Вхождения макросов в команды shell'а раскрываются в момент выполнения. В общем случае обращение к макросу выглядит так:

       $(макро:цепочка1=цепочка2)

Подобная конструкция преобразуется следующим образом. Сначала определяется значение $(макро). Затем каждое вхождение цепочки1 в данное значение заменяется на цепочку2; при этом вхождение цепочки1 в $(макро) понимается как сопоставление с регулярным выражением вида

       .*цепочка1[табуляция|пробел]

то есть значение $(макро) рассматривается как набор разделенных пробелами или табуляциями цепочек символов и в конце этих цепочек цепочка1 заменяется на цепочку2.

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

       $(LIB) : $(LIB)(a.o) $(LIB)(b.o) $(LIB)(c.o)
                $(CC) -c $(CFLAGS) $(?:.o=.c)
                $(AR) $(ARFLAGS) $(LIB) $?
                rm $?

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

 

4. РЕКУРСИВНЫЕ MAKE-ФАЙЛЫ

Еще одна особенность утилиты make касается учета окружения и возможности рекурсивных запусков. Если в командной строке shell'а где-либо указана последовательность $(MAKE), данная строка выполняется, даже если установлен флаг -n. Так как флаг -n распространяется по цепочке вызовов make'а (через переменную MAKEFLAGS), единственное выполняемое действие - это сама команда make. Данная особенность полезна в тех случаях, когда иерархия make-файлов описывает совокупность компонентов программных систем. Обращение make -n ... можно использовать для целей отладки; при этом распечатываются все те команды, которые должны быть выполнены, а также вывод вложенных вызовов утилиты make.

4.1. Суффиксы и правила трансформации

Утилита make использует таблицу встроенных правил, определяющих, как преобразовать файл с одним суффиксом в файл с другим суффиксом. Если в командной строке make'а указан флаг -r, встроенная таблица не используется.

В действительности список суффиксов совпадает со списком зависимостей для имени .SUFFIXES. make пытается найти файл с одним из суффиксов из списка. Если такой файл найден, make трансформирует его в файл с другим суффиксом. Имена правил трансформации - это просто конкатенации суффиксов файлов до и после трансформации. Так, правило трансформации .c-файла в .o-файл называется .c.o. Если данное правило существует, а в пользовательских файлах описаний не задано явной последовательности команд, используется последовательность команд, соответствующая правилу .c.o. Если команда порождается при помощи одного из таких правил, посредством макроса $* можно получить основу (все, кроме суффикса) имени файла; макрос $< обозначает полное имя файла из строки зависимостей, вызвавшего выполнение действия.

Порядок суффиксов в списке существенен, поскольку список просматривается слева направо. Используется первое сформированное имя - то, для которого имеется и файл, и соответствующее ему правило. Если надо добавить новые имена, пользователь может добавить запись в правило .SUFFIXES в файле описаний. Зависимости добавляются к стандартному списку. Строка .SUFFIXES, не содержащая зависимостей, делает текущий список пустым. Если порядок имен надо изменить, текущий список должен быть очищен.

4.2. Подразумеваемые правила

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

 .o объектный файл
 .c исходный C-файл
 .c~ исходный SCCS C-файл
 .f исходный f77-файл
 .f~ исходный SCCS f77-файл
 .s исходный файл на языке ассемблера
 .s~ исходный SCCS-файл на языке ассемблера
 .y исходная yacc-грамматика
 .y~ исходная SCCS yacc-грамматика
 .l исходная lex-спецификация
 .l~ исходный SCCS lex-спецификация
 .h включаемый файл
 .h~ включаемый SCCS-файл
 .sh командный файл
 .sh~ командный SCCS-файл

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

Если требуется файл x.o, а в описании или каталоге найден файл x.c, x.c будет откомпилирован. Если, кроме того, имеется файл x.l, он будет обработан lex'ом, после чего будет откомпилирован результат обработки. Однако, если файл x.c отсутствует, но есть x.l, make воспользуется правилом прямой трансформации и отбросит промежуточный C-файл.

Можно изменить имена некоторых используемых по умолчанию компиляторов, а также опции, с которыми компиляторы вызываются. Для этого предусмотрены макросы с определенными именами. Именам компиляторов соответствуют макросы AS, CC, F77, YACC, LEX. Команда

       make CC=newcc

будет приводить к использованию вместо стандартного C-компилятора команды newcc. Чтобы компиляторы запускались с дополнительными опциями, можно устанавливать макросы ASFLAGS, CFLAGS, F77FLAGS, YFLAGS и LFLAGS. Так, вызов

       make "CFLAGS=-g"

заставляет команду cc(1) включать в файл отладочную информацию.

4.3. Архивные библиотеки

Утилита make предоставляет интерфейс для работы с архивными библиотеками. В файле описаний пользователь может обратиться к элементу библиотеки следующим образом:

       библиотека(файл.o)
или
       библиотека(точка_входа)

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

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

       projlib::   projlib(pfile1.o)
               $(CC) -c -O pfile1.c
               $(AR) $(ARFLAGS) projlib pfile1.o
               rm pfile1.o
       projlib::   projlib(pfile2.o)
               $(CC) -c -O pfile2.c
               $(AR) $(ARFLAGS) projlib pfile2.o
               rm pfile2.o
        . . . и так далее для каждого элемента библиотеки

Это утомительно и чревато ошибками. Очевидно, последовательность команд для включения C-файла в библиотеку повторяется для каждого запуска; единственное отличие заключается в имени файла. (Данное утверждение истинно в большинстве случаев.)

Утилита make предоставляет пользователю правила для построения библиотек. Этим правилам соответствует суффикс .a. Так, правило .c.a - это правило компиляции исходного C-файла, включения его в библиотеку и удаления промежуточного .o-файла. Аналогично, правила .y.a, .s.a и .l.a заново обрабатывают, соответственно, файлы yacc'а, ассемблера и lex'а. Встроенными являются следущие правила для работы с архивами: .c.a, .c~.a, .f.a, .f~.a и .s~.a. (Назначение тильды, ~, будет ниже коротко описано.) В файле описаний пользователь может определить другие необходимые ему правила.

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

       projlib:        projlib(pfile1.o) projlib(pfile2.o)
                  @echo projlib up-to-date

На самом деле правило .c.a выглядит так:

       .c.a:
               $(CC) -c $(CFLAGS) $<
               $(AR) $(ARFLAGS) $@ $*.o
               rm -f $*.o

Здесь макрос $@ обозначает целевой файл (библиотеку projlib); макросы $< и $* устанавливаются равными, соответственно, имени изменившегося C-файла и имени файла без суффикса (pfile1.c и pfile1). Макрос $< (в приведенном выше правиле) можно заменить на $*.c.

Полезно в деталях рассмотреть, как make обрабатывает следующую конструкцию: projlib: projlib(pfile1.o) @echo projlib up-to-date

Предположим, что объектный файл, содержащийся в библиотеке, по сравнению с pfile1.c устарел. Кроме того, файл pfile1.o отсутствует.

Выполняются следующие действия:

                make projlib.

Следует напомнить, что для того, чтобы pfile1.o зависел от каких-либо файлов, требуется поместить в файл описаний запись, подобную

       projlib(pfile1.o):      $(INCDIR)/stdio.h pfile1.c

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

 

5. ИМЕНА SCCS-ФАЙЛОВ (ТИЛЬДА)

Синтаксис make'а не позволяет непосредственно ссылаться на префиксы имен файлов. Для большинства типов файлов в операционной системе UNIX это приемлемо, поскольку почти повсеместно для определения типа файлов используется суффикс. Исключение составляют SCCS-файлы. В этом случае s. является префиксом компонента имени файла в полном маршрутном имени.

Чтобы облегчить утилите make обращение с префиксом .s, в качестве признака SCCS-файлов используется символ ~ (тильда). Так, .c~.o обозначает правило, преобразующее исходный SCCS-файл на языке C в объектный файл. Именно, встроенное правило выглядит так:

       .c~.o:
               $(GET) $(GFLAGS) $<
               $(CC) -c $(CFLAGS) $*.c
               -rm -f $*.c

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

Встроенными являются следующие суффиксы SCCS:

       .c~
       .f~
       .y~
       .l~
       .s~
       .sh~
       .h~

Встроены также следующие правила трансформации SCCS-файлов:

       .c~:
       .f~:
       .sh~:
       .c~.a:
       .c~.c:
       .c~.o:
       .f~.a:
       .f~.f:
       .f~.o:
       .s~.a:
       .s~.s:
       .s~.o:
       .y~.c:
       .y~.o:
       .l~.l:
       .l~.o:
       .h~.h:

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

5.1. Пустой суффикс

Многие программы состоят из единственного исходного файла. Для обработки этих случаев утилита make предоставляет правило с пустым суффиксом. Так, чтобы поддерживать системную программу cat(1), требуется make-файл, содержащий правило следующего вида:

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

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

       make cat dd echo date

(указанные программы состоят из одного файла), и все четыре исходных C-файла передаются из командной строки shell'а на обра- ботку при помощи .c-правила. Встроены следующие правила с одним суффиксом:

       .c:
       .c~:
       .f:
       .f~:
       .sh:
       .sh~:

Пользователь может включить в make-файл дополнительные правила.

5.2. Включаемые файлы

Утилита make предоставляет возможность, аналогичную директиве #include препроцессора языка C. Если первые семь символов в какой-либо строке make-файла составляют цепочку include, а за ними следует пробел либо табуляция, остаток строки считается именем файла, который будет прочитан текущим запуском утилиты make. В именах включаемых файлов могут быть использованы макросы. Описатели читаемых включаемых файлов накапливаются, поэтому число вложенных включаемых файлов ограничено (не может быть больше 16).

5.3. make-файлы в рамках SCCS

Make-файлы могут управляться системой SCCS. Так, если набрана команда

       make

и имеются только файлы s.makefile или s.Makefile, make сначала выполнит команду get(1), а затем обработает и удалит файл.

5.4. Динамические параметры зависимостей

Параметр имеет смысл только в строке зависимостей make-файла. $$@ обозначает часть текущей строки слева от двоеточия ($@). Кроме того, имеется параметр $$(@F), позволяющий получить доступ к обозначающему файл компоненту $@. Так, в следующей записи:

       cat:    $$@.c

строка зависимостей в момент выполнения преобразуется в цепочку cat.c. Это полезно для построения большого числа выполняемых файлов, у каждого из которых только один исходный файл. Например, в каталоге, содержащем утилиты системы UNIX, может быть make-файл вида:

       CMDS = cat dd echo date cmp comm chown
       
       $(CMDS):        $$@.c
               $(CC) -O $? -o $@

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

Еще одна полезная форма параметра зависимостей - $$(@F). Этот параметр представляет обозначающую файл часть $$@. Он также вычисляется во время выполнения. Его польза становится очевидной, если попытаться поддерживать каталог /usr/include при помощи make-файла, который находится в каталоге /usr/src/head. Файл описаний /usr/src/head/makefile должен выглядеть так:

       INCDIR = /usr/include
       
       INCLUDES = \
               $(INCDIR)/stdio.h
               $(INCDIR)/pwd.h
               $(INCDIR)/dir.h
               $(INCDIR)/a.out.h
       
       $(INCLUDES): $$(@F)
               cp $? $@
               chmod 0444 $@

Использование этого make-файла должно приводить в полное соответствие содержимое каталога /usr/include при обновлении любого из упомянутых файлов, расположенных в каталоге /usr/src/head.

 

6. ЗАПУСК УТИЛИТЫ MAKE

Описание утилиты make содержится в статье make(1) Справочника пользователя.

6.1. Командная строка

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

       make  [опции] [макроопределения] [целевые_файлы]

Аргументы командной строки интерпретируются следующим образом. Прежде всего анализируются аргументы, являющиеся макроопределениями (то есть аргументы, содержащие внутри себя знаки равенства), и выполняются необходимые присваивания. Макросы, заданные в командной строке, имеют приоритет над макросами, заданными в файле описаний. Затем рассматриваются аргументы-опции. Допускаются следующие опции:

  -i
  Игнорировать коды ошибок, возвращаемых запускаемыми программами. Данный режим устанавливается и тогда, когда в файле описаний указано фиктивное целевое имя .IGNORE.
  -s
  Молчи, скрывайся и таи... Не выводить командные строки перед их выполнением. Данный режим устанавливается и тогда, когда в файле описаний указано фиктивное целевое имя .SILENT.
  -r
  Не использовать встроенные правила.
  -n
  Пробный режим. Распечатывать команды, но не выполнять их. Выводятся даже команды, начинающиеся со знака @.
  -t
  "Массаж" целевых_файлов: время их создания устанавливается равным текущему времени; команды, предназначенные для получения целевых_файлов, не выполняются.
  -q
  Запрос. Утилита make возвращает нулевой или ненулевой код завершения в зависимости от того, нужно ли обновлять целевые_файлы (0, если не нужно).
  -p
  Вывести все макроопределения, а также все описания целевых_файлов.
  -k
  При ошибке прекращать выполнение команд, связанных с текущей зависимостью, но продолжать обработку других зависимостей, не связанных с текущей.
  -e
  Использовать в первую очередь переменные окружения, а не одноименные макросы make-файлов.
  -f
  Следующий аргумент считается именем файла описаний. Имя файла - обозначает стандартный ввод. Если опция -f не указана, читается файл с именем makefile, Makefile или s.[mM]akefile из текущего каталога. В первую очередь используется содержимое файлов описаний, а не встроенные правила.

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

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

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

Всякий раз, когда выполняется утилита make, читаются и добавляются к макроопределениям переменные окружения. Чтобы пользоваться этим механизмом должным образом, важнее всего рассмотреть приоритетность различных определений. Ниже описывается взаимодействие make'а с окружением. Утилита make поддерживает макрос MAKEFLAGS. Данный макрос определяется как цепочка, состоящая из всех входных аргументов-флагов (без знаков -). Макрос экспортируется и тем самым становится доступным вложенным за- пускам make'а. Флаги командной строки и присваивания, содержащиеся в make-файле, обновляют значение MAKEFLAGS. Чтобы описать, как окружение взаимодействует с утилитой make, необходимо рассмотреть макрос MAKEFLAGS.

Выполняясь, make формирует макроопределения в следующем порядке:

Полезно привести список приоритетов присваиваний. Ниже перечисляются установки макропределений в порядке возрастания их старшинства:

Использование опции -e приводит к некоторому изменению порядка:

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

 

7. СОВЕТЫ И ПРЕДОСТЕРЕЖЕНИЯ

Чаще всего причиной возникновения трудностей является специфическая трактовка утилитой make понятия зависимостей. Если файл x.c содержит строку

       #include "defs.h"

считается, что от defs.h зависит объектный файл x.o, но не исходный файл x.c. Если defs.h изменяется, с x.c ничего не делается, в то время как x.o должен быть создан заново.

Чтобы понять, какие действия будет делать make, очень удобно использовать опцию -n. Обращение

       make -n

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

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

       make -ts

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

 

8. ВСТРОЕННЫЕ ПРАВИЛА

Ниже приводится стандартный набор встроенных правил, используемых утилитой make.


       #
       #       Суффиксы, распознаваемые make
       #
       .SUFFIXES: .o .c .c~ .y .y~ .l .l~ .s .s~ .h .h~ \
                  .sh .sh~ .f .f~

       #
       #       Предопределенные макросы
       #
       MAKE=make
       AR=ar
       ARFLAGS=-rv
       AS=as
       ARFLAGS=
       CC=cc
       CFLAGS=-O
       F77=f77
       F77FLAGS=
       GET=get
       GFLAGS=
       LEX=lex
       LFLAGS=
       LD=ld
       LDFLAGS=
       YACC=yacc
       YFLAGS=

       #
       #       Правила с одним суффиксом
       #
       .c:
               $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@

       .c~:
               $(GET) $(GFLAGS) $<
               $(CC) $(CFLAGS) $(LDFLAGS) $*.c -o $*
               -rm -f $*.c

       .f:
               $(F77) $(F77FLAGS) $(LDFLAGS) $< -o $@

       .f~:
               $(GET) $(GFLAGS) $<
               $(F77) $(F77FLAGS) $(LDFLAGS) $< -o $*
               -rm -f $*.f

       .sh:
               cp $< $@; chmod 0777 $@

       .sh~:
               $(GET) $(GFLAGS) $<
               cp $*.sh $*; chmod 0777 $@
               -rm -f $*.sh

       #
       #       Правила с двумя суффиксами
       #
       .c~.c .f~.f .s~.s .sh~.sh .y~.y .l~.l .h~.h:
               $(GET) $(GFLAGS) $<

       .c.a:
               $(CC) -c $(CFLAGS) $<
               $(AR) $(ARFLAGS) $@ $*.o
               rm -f $*.o

       .c~.a:
               $(GET) $(GFLAGS) $<
               $(CC) -c $(CFLAGS) $*.c
               $(AR) $(ARFLAGS) $@ $*.o
               rm -f $*.[co]

       .c.o:
               $(CC) -c $(CFLAGS) $<

       .c~.o:
               $(GET) $(GFLAGS) $<
               $(CC) -c $(CFLAGS) $*.c
               -rm -f $*.c

       .f.a:
               $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f
               $(AR) $(ARFLAGS) $@ $*.o
               -rm -f $*.o

       .f~.a:
               $(GET) $(GFLAGS) $<
               $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f
               $(AR) $(ARFLAGS) $@ $*.o
               -rm -f $*.[fo]

       .f.o:
               $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f

       .f~.o:
               $(GET) $(GFLAGS) $<
               $(F77) $(F77FLAGS) $(LDFLAGS) -c $*.f
               -rm -f $*.f

       .s~.a:
               $(GET) $(GFLAGS) $<
               $(AS) $(ASFLAGS) -o $*.o $*.s
               $(AR) $(ARFLAGS) $@ $*.o
               -rm -f $*.[so]

       .s.o:
               $(AS) $(ASFLAGS) -o $@ $<

       .s~.o:
               $(GET) $(GFLAGS) $<
               $(AS) $(ASFLAGS) -o $*.o $*.s
               -rm -f $*.s

       .l.c:
               $(LEX) $(LFLAGS) $<
               mv lex.yy.c $@

       .l~.c:
               $(GET) $(GFLAGS) $<
               $(LEX) $(LFLAGS) $*.l
               mv lex.yy.c $@
               rm -f $*.l

       .l.o:
               $(LEX) $(LFLAGS) $<
               $(CC) $(CFLAGS) -c lex.yy.c
               rm lex.yy.c
               mv lex.yy.o $@

       .l~.o:
               $(GET) $(GFLAGS) $<
               $(LEX) $(LFLAGS) $*.l
               $(CC) $(CFLAGS) -c lex.yy.c
               rm lex.yy.c $*.l
               mv lex.yy.o $*.o

       .y.c:
               $(YACC) $(YFLAGS) $<
               mv y.tab.c $@

       .y~.c:
               $(GET) $(GFLAGS) $<
               $(YACC) $(YFLAGS) $*.y
               mv y.tab.c $@
               -rm -f $*.y

       .y.o:
               $(YACC) $(YFLAGS) $<
               $(CC) $(CFLAGS) -c y.tab.c
               rm y.tab.c
               mv y.tab.o $@

       .y~.o:
               $(GET) $(GFLAGS) $<
               $(YACC) $(YFLAGS) $*.y
               $(CC) $(CFLAGS) -c y.tab.c
               rm -f y.tab.c $*.y
               mv y.tab.o $*.o


НазадОглавлениеВперед
КаталогИндекс раздела