Makefile

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 23:02, 23 мая 2018.
MAKEFILE
10-make.jpg
Интернет-тип носителя text/x-makefile
Тип формата Автоматизация сборки
Стандарт POSIX

Makefile – файл, содержащий набор инструкций для программы make. Программа make с помощью этого файла позволяет автоматизировать процесс компиляции программы и выполнять при этом различные действия. При запуске make по умолчанию ищет файл Makefile в текущей папке и обрабатывает его (можно изменить это поведение, чтобы открывался другой файл с набором инструкций, если ввести команду make -f другое_имя_makefile).

Обзор

Чаще всего, Makefile показывает как компилировать и компоновать программу. Используя C / C ++ в качестве примера, когда исходный файл C / C ++ изменяется, он должен быть перекомпилирован. Если файл заголовка был изменен, каждый исходный файл C / C ++, содержащий заголовочный файл, должен быть перекомпилирован для обеспечения безопасности. Каждая компиляция создает объектный файл, соответствующий исходному файлу. Наконец, если какой-либо исходный файл был перекомпилирован, все объектные файлы, будь то новые или сохраненные из предыдущих компиляций, должны быть связаны друг с другом для создания новой исполняемой программы. [Источник 1] Эти инструкции с их зависимостями указаны в make- файле, Если ни один из файлов, которые являются предварительными условиями, не был изменен с момента компиляции программы, никаких действий не происходит. Для крупных программных проектов использование Make-файлов может существенно сократить время сборки, если изменилось только несколько исходных файлов.

Структура файла

Комментарии, как это принято в UNIX скриптах, начинаются с символа # и продолжаются до конца строки.
Обычно в файле содержатся метки, идентифицирующие "цели" (targets, см. далее). Метка начинается с начала строки и оканчивается двоеточием :. После двоеточия могут идти так называемые зависимости, dependencies (сразу непонятно, что это такое и для чего надо, но дальше по ходу дела станет яснее). Обычно это имена файлов, либо ссылки на цели.
Если в качестве dependencies указана последовательность целей, то они будут выполняться друг за другом.
Если в качестве dependencies указаны имена файлов (обычно объектных), то утилита make может проверить - нужно их компилировать, или нет (мне непонятно, как она проверяет, однако это работает). Например, если компилируется несколько исходных файлов в несколько объектных, то некоторые исходные файлы не требуется каждый раз перекомпилировать заново, если они не изменялись. Для больших проектов это важно, поскольку существенно экономит время сборки программы (в нашем случае - получение двоичной прошивки для AVR).
Для упрощения содержимого Makefile и для удобства используются переменные.

Пример задания переменной (здесь в переменную записана командная строка вызова программатора):

 JTAGICEII = c:/Program Files/AtmelAVR Tools/JTAGICEmkII/jtagiceii.exe -d $(DEVICE) -e -mi

После задания переменной на неё можно ссылаться так:

  flash: main.hex
    $(JTAGICEII) -pf -if main.hex
  • После задания в одной строке цели (цель: [зависимость1] .. [зависимостьN]) в последующих строках могут задаваться так называемые правила (rules). Каждое правило должно ОБЯЗАТЕЛЬНО начинаться с символа табуляции (таким способом make отслеживает правила и другие цели). Правило - это просто обычная команда (вызов компилятора, копирование, удаление и проч.), выполняемая шеллом.
  • Переменные $@, $< , $^ называются автоматическими (automatic variables).

$@ заменяется на текущую цель.
$< которая заменяется на первую зависимость из списка.
$^ которая заменяется на список всех зависимостей с их каталогами.

Итак, при работе с проектом может потребоваться автоматизировать следующие часто повторяющиеся действия:
1. Компиляция программы (вводимая команда будет выглядеть как make hex).
2. Запись двоичного файла (make flash).
3. Запись бит "перемычек" (make fuse, для микроконтроллеров AVR это обычно 2 байта).
4. Полная запись микроконтроллера (и памяти и перемычек), выполняются действия и 2, и 3.
5. Очистка проекта - удаление всех промежуточных файлов, образующихся при компиляции, обычно объектных (make clean).
6. Стирание микроконтроллера (очистка flash и сброс перемычек в исходное состояние).
7. Бэкап проекта (make backup), будет выполняться цель 5 (clean), а затем архивирование файлов.
8. Вывод подсказки по возможным вариантам работы с проектом (просто make, при этом выводится подсказка по make hex, make flash, make fuse, make clean).


Для каждого такого действия 1..8 в Makefile прописывается блок команд, этот блок идентифицируется целью (target). Имя цели по сути является меткой, по которой переходит управление при обработке команды, переданной программе make. Для действия 1 это будет запуск компилятора (цель hex), для 2 - вызов программатора (цель flash) и т. д.

Рассмотрим для примера ветку обработки цели hex (команда make hex) по шагам:

  • Пользователь вводит команду make hex.
  • Программа make открывает файл Makefile, ищет цель hex и начинает её обработку.
  • Для цели hex указана зависимость main.hex и ни одного правила (строка 131). Программа make ищет цель main.hex и начинает её обработку.
  • Для цели main.hex указана зависимость main.elf (строка 175) и несколько правил. Программа make ищет цель main.elf и начинает её обработку (правила цели main.hex будут отрабатываться после окончания обработки цели main.elf).
  • Для цели main.elf указаны зависимости usbdrv и $(OBJECTS) (строка 172), а также одно правило (вызов компилятора для получения файла main.elf). Программа make ищет цель usbdrv и начинает её обработку.
  • Для цели usbdrv не указано зависимостей (строка 169), только одно правило (копирование папки usbdrv в текущий каталог). Выполняется это правило, цель usbdrv завершена и происходит возврат к обработке цели main.elf.
  • Зависимости, входящие в переменную $(OBJECTS) (строка 172), не являются целями, это просто имена файлов, которые должны быть получены при компиляции. Поэтому сразу начинается выполняться правило, запускающее компилятор (строка 173). В результате те объектные файлы, которые должны быть скомпилированы, появляются в соответствующих каталогах, и появляется выходной файл main.elf (двоичный файл, который может использоваться в качестве входного для эмулятора или симулятора при отладке программы). Цель main.elf завершена, происходит возврат к обработке цели main.hex (строка 175).
  • Начинается обработка правил цели main.hex. Команда rm удаляет старые файлы прошивок flash и eeprom, avr-objcopy генерирует новую прошивку main.hex из файла main.elf, avr-size просто отображает информацию о размере секций в файле main.hex. Обработка цели main.hex закончена, происходит возврат к обработке цели hex.
  • Все зависимости цели hex обработаны, правил у цели hex нет. Работа make на этом завершается.

Проблемы и их решения

1. Makefile в среде Windows (Makefile работает при помощи пакета MSYS) завершается с ошибкой на команде xcopy, например (выполнение команды make backup):

 /usr/bin/sh: -c: line 3: syntax error: unexpected end of file
make: *** [backup] Error 258

Проблема решается добавлением в начало Makefile строки "SHELL=cmd.exe". Вот пример рабочего Makefile, в котором эта ошибка устранена:

 RAR = "c:/Program Files/WinRAR/WinRAR.exe"ARCHIVE = myproject.rarSHELL=cmd.exe
 help:
    @echo "This Makefile has no default rule. Use one of the following:"
    @echo "make backup .... backup project" 
 backup:
    $(RAR) a -r -dh -ep1 $(ARCHIVE) ../myproject
    mv $(ARCHIVE) c:/archive/ARMmyproject
    autoname /pattern:YYMMDDhhmmss c:/archive/ARM/myproject/$(ARCHIVE)
    xcopy /M /Y c:/archive/ARMmyproject*.* "\serverWORK"

2. Сетевые (UNC) пути необходимо заключать в двойные кавычки, иначе они будут неправильно переданы интерпретатору cmd.exe. Вот так:

 xcopy /M /Y c:/archive/ARMmyproject*.* "serverWORK"

3. Отлаживать makefile удобно с помощью команды echo, выводя в консоль значения переменных. Пример:

 @echo $(OBJECTS)

Операционная система

Система make родилась в мире UNIX и постепенно переползла и на Windows вместе с портами GNU-компиляторов (gcc). Если открыть пример готового Makefile, то он поначалу может показаться полной абракадаброй, поскольку содержимое файла подчиняется заранее заданному набору правил, которые необходимо предварительно изучить. Для простоты рассмотрим пример работы с проектом AVR и компилятором gcc.

Unix-подобный

Makefiles происходит от Unix-подобных систем и по-прежнему является основным механизмом создания программного обеспечения в таких средах.

Microsoft Windows

Windows поддерживает изменение make-файлов с помощью утилиты nmake . Стандартные Unix, такие как make-файлы, могут быть выполнены в Windows в среде Cygwin.

Содержание

Makefiles содержит пять видов вещей: явные правила , неявные правила , определения переменных , директивы и комментарии .

  • Явное правило говорит , когда и как переделать один или несколько файлов, называемых целями правила. В нем перечислены другие файлы, от которых зависят целевые объекты, называемые предпосылками цели, а также могут давать рецепт для создания или обновления целей.
  • Неявное правило говорит , когда и как переделать класс файлов на основе их имен. В нем описывается, как цель может зависеть от файла с именем, близким к целевому, и дает рецепт для создания или обновления такой цели.
  • Определение переменной - это строка, которая указывает значение текстовой строки для переменной, которая может быть заменена в тексте позже.
  • Директива является инструкцией для макияжа , чтобы сделать что - то особенное, читая Makefile , такие как чтение другого Makefile.
  • '#' в строке файла makefile начинает комментарий . Он и остальная часть строки игнорируются.

Правила

Простой make-файл состоит из «правил» со следующей из форм [Источник 2]:

target  : prerequisites 
        recipe
        
        
или
target: dependencies
    system command(s)

Зависимость или "prerequisites" (также называемая предварительным условием) - это файл, который используется как вход для создания цели. Необходимым условием является файл, который используется как вход для создания цели.

Цель(target) часто зависит от нескольких файлов.Однако правило, определяющее рецепт для цели, не обязательно имеет какие-либо предварительные условия. Например, правило, содержащее команду delete, связанную с целевым «чистым», не имеет предварительных условий.

Рецепт (recipe)- это действие , которое make выполняет. Рецепт может иметь более одной команды, либо в одной строке, либо в каждой строке. Обратите внимание: вам нужно поместить символ табуляции в начале каждой линии рецептов. Это неясность, на которой попадаются неосторожные. Если вы предпочитаете префикс своих рецептов символом, отличным от табуляции, вы можете установить RECIPEPREFIX переменную на альтернативный символ.

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

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

Условные части Make-файлов

Пример условного

В следующем примере условного выражения makeиспользуется один набор библиотек, если CCпеременная 'НКУ'и другой набор библиотек в противном случае. Он работает, контролируя, какая из двух линий рецепта будет использоваться для правила. В результате получается, что 'CC = GCC'как аргумент для makeизменений не только того, какой компилятор используется, но и какие библиотеки связаны.

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
        $(CC) -o foo $(objects) $(libs_for_gcc)
else
        $(CC) -o foo $(objects) $(normal_libs)
endif

В этом условном случае используются три директивы: один ifeq, один else и один endif.

ifeq Директива начинается условная, и определяет условие. Он содержит два аргумента, разделенных запятой и окруженных круглыми скобками. Подстановка переменных выполняется по обоим аргументам, а затем их сравнивают. Строки файла makefile, следующие за ними, ifeqвыполняются, если совпадают два аргумента; в противном случае они игнорируются.

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

endif Директива заканчивается условное. Каждое условие должно заканчиваться на endif. Далее следует текст безусловного make-файла.

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

Когда переменная CCимеет значение 'НКУ', приведенный выше пример имеет такой эффект:

foo: $(objects)
        $(CC) -o foo $(objects) $(libs_for_gcc)

Когда переменная CCимеет любое другое значение, эффект таков:

oo: $(objects)
        $(CC) -o foo $(objects) $(normal_libs)

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

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif

foo: $(objects)
        $(CC) -o foo $(objects) $(libs)

Синтаксис условий

Синтаксис простого условного с no else заключается в следующем:

conditional-directive
text-if-true
endif

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

Синтаксис сложного условного выражения следующий:

conditional-directive
text-if-true
else
text-if-false
endif

или:

conditional-directive-one
text-if-one-is-true
else conditional-directive-two
text-if-two-is-true
else
text-if-one-and-two-are-false
endif

При необходимости может быть столько « else условно-директивных » положений. Когда данное условие истинно, используется text-if-true, и никакое другое предложение не используется; если условие не истинно, используется text-if-false . Текста , если правда и текст-невыполненный- может быть любым количеством строк текста.

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

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"

Разверните все ссылки переменных в arg1 и arg2 и сравните их. Если они идентичны, текст-if-true эффективен; в противном случае текст-if-false , если таковой имеется, эффективен.

Часто вы хотите проверить, имеет ли переменная непустое значение. Когда значение получается из сложных разложений переменных и функций, разложения, которые вы считаете пустыми, могут фактически содержать пробельные символы и, следовательно, не считаются пустыми. Однако вы можете использовать эту stripфункцию (см. Текстовые функции ), чтобы избежать интерпретации пробела как непустого значения. Например:

ifeq ($ (strip $ (foo)),)
 text-if-empty
ENDIF

будет оценивать text-if-empty, даже если расширение $(foo)содержит пробельные символы.

ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2"

Разверните все ссылки переменных в arg1 и arg2 и сравните их. Если они разные, текст-if-true эффективен; в противном случае текст-if-false , если таковой имеется, эффективен.

ifdef variable-name ifdef Форма принимает имя переменной в качестве аргумента, а не ссылка на переменную. Если значение этой переменной имеет непустое значение, текст-if-true действует; в противном случае текст-if-false , если таковой имеется, эффективен. Переменные, которые никогда не были определены, имеют пустое значение. Текстовое имя переменной расширяется, поэтому это может быть переменная или функция, которая расширяется до имени переменной. Например:

bar = true
foo = bar
ifdef $(foo)
frobozz = yes
endif

Ссылка на переменную $(foo)расширяется, давая bar, что считается именем переменной. Переменная barне разворачивается, но ее значение проверяется, чтобы определить, не является ли она непустой.

Обратите внимание, что ifdefпроверяет только, имеет ли переменная значение. Он не расширяет переменную, чтобы увидеть, не является ли это значение непустым. Следовательно, тесты с использованием ifdefreturn true для всех определений, за исключением тех, которые похожи foo =. Чтобы проверить пустое значение, используйте ifeq ($(foo),). Например,

bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endifF

наборы 'frobozz'to'да', в то время как:

foo =
ifdef foo
frobozz = yes
else
frobozz = no
endiF

наборы 'frobozz'to'нет».

ifndef variable-name Если переменная variable-name имеет пустое значение, текст-if-true эффективен; в противном случае текст-if-false , если таковой имеется, эффективен. Правила расширения и тестирования имени переменной идентичны ifdefдирективе.

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

Остальные две директивы, которые играют роль в условном выражении, являются else и endif. Каждая из этих директив написана как одно слово без аргументов. Дополнительные пробелы разрешены и игнорируются в начале строки, а также пробелы или вкладки в конце. Комментарий, начинающийся с '#'может появиться в конце строки.

Условные обозначения влияют на makeиспользуемые строки make-файла . Если условие истинно, makeчитает строки текста-if-true как часть файла makefile; если условие ложно, полностью makeигнорирует эти строки. Из этого следует, что синтаксические единицы make-файла, такие как правила, могут безопасно разделяться в начале или в конце условного.

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

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


Условные обозначения,которые проверяют флаги

Вы можете написать условие, которое проверяет makeфлаги команд, такие как '-t', используя переменную MAKEFLAGSвместе с findstringфункцией (см. Функции для подстановки и анализа строк ). Это полезно, когда touchэтого недостаточно, чтобы обновить файл.

findstringФункция определяет , будет ли отображаться одна строка в качестве подстроки другого. Если вы хотите протестировать "-t'flag, use'T'как первая строка и значение MAKEFLAGSкак другое.

Например, вот как организовать использование 'ranlib -t'для окончательной маркировки архивного файла:

archive.a:ifneq (,$(findstring t,$(MAKEFLAGS)))
        +touch archive.a
        +ranlib -t archive.a
else
        ranlib archive.a
endif

"+" префикс отмечает эти строки рецептов как «рекурсивные», так что они будут выполнены, несмотря на использование'-t'.

Выполнение

Makefile выполняется с make командой, например make [options] [target1 target2 ...]. По умолчанию, когда make ищет make-файл, если имя файла makefile не было включено в качестве параметра, он пытается использовать следующие имена: makefile и Makefile.

Примеры

1. Вот простой make-файл, который описывает способ, которым исполняемый файл называется редактированием, зависит от четырех объектных файлов, которые, в свою очередь, зависят от четырех исходных файлов C и двух файлов заголовков.
edit: main.o kbd.o command.o display.o 
    cc -o edit main.o kbd.o command.o display.o
     
main.o: main.c defs.h
    cc -c main.c
kbd.o: kbd.c defs.h command.h
    cc -c kbd.c
command.o: command.c defs.h command.h
    cc -c command.c
display.o: display.c defs.h
    cc -c display.c

clean:
     rm edit main.o kbd.o command.o display.o
Чтобы использовать этот make-файл для создания исполняемого файла с именем edit , введите make. Чтобы использовать этот файл makefile для удаления исполняемого файла и всех объектных файлов из каталога, введите make clean.

2. Использование действий по умолчанию.

#default target - file edit 
edit : main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
         cc -o edit main.o kbd.o command.o display.o \ 
                    insert.o search.o files.o utils.o 

main.o : main.c defs.h
        cc -c main.c 
kbd.o : kbd.c defs.h command.h
        cc -c kbd.c
command.o : command.c defs.h command.h 
        cc -c command.c 
display.o : display.c defs.h buffer.h 
        cc -c display.c
insert.o : insert.c defs.h buffer.h 
        cc -c insert.c 
search.o : search.c defs.h buffer.h 
        cc -c search.c 
files.o : files.c defs.h buffer.h command.h 
        cc -c files.c 
utils.o : utils.c defs.h 
        cc -c utils.c
clean : 
       rm edit main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o

По умолчанию, make начинает с первого правила (не считая правил, имена целей у которых начинаются с '.'). Это называется главной целью по умолчанию. В нашем случае это правило edit. Если файл edit новее чем объектные файлы, от которых он зависит, то ничего не произойдет. В противном случае, прежде чем make сможет полностью обработать это правило, он должен рекурсивно обработать правила для файлов, от которых зависит edit. Каждый из этих файлов обрабатывается в соответствии со своим собственным правилом. Перекомпеляция должна быть проведена, если исходный файл или любой из заголовочных файлов, упомянутых среди зависимостей, обновлен позднее, чем объектный файл, или если объектный файл не существует. Правилу clean не соответствует никакого создаваемого файла и, соответственно, clean ни от чего не зависит и само не входит в список зависимостей. При запуске по умолчанию clean вызываться не будет. Для его выполнения необходимо явно указать цель при запуске make: make clean.

3. Для сокращения записи можно использовать переменные и действия по умолчанию (неявные правила)

objects = main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o 

edit : $(objects) 
        cc -o edit $(objects) 
main.o : defs.h 
kbd.o : defs.h command.h 
command.o : defs.h command.h 
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h 
.PHONY : clean 
clean :
        -rm edit $(objects)

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

file.c: file.o   cc -c file.c

4. Специальная цель .PHONY является встроенной в make и определяет свои зависимости как цели-имена, которым нет соответствия в виде файлов. Если данное правило пропустить, то создание в текущем каталоге файла с именем clean заблокирует выполнение make clean. Использование правил по умолчанию позволяет изменить стиль записей зависимостей:

objects = main.o kbd.o command.o display.o \ 
          insert.o search.o files.o utils.o 

edit : $(objects) 
       cc -o edit $(objects) 

$(objects) : defs.h 
kbd.o command.o files.o : command.h 
display.o insert.o search.o files.o : buffer.h

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

Источники

  1. Базовые спецификации Open Group Выпуск 7 (Целевые правила) // URL:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html#tag_20_76_13_04 (Дата обращения: 01.05.2018)
  2. Makefile // Википедия. [2017—2018]. Дата обновления: 17.04.2018. URL: https://ru.wikipedia.org/wiki/Makefile (дата обращения: 01.05.2018).

Ссылки