make — утилита предназначенная для автоматизации преобразования файлов из одной формы в другую. Правила преобразования задаются в скрипте с именем Makefile, который должен находиться в корне рабочей директории проекта. Сам скрипт состоит из набора правил, которые в свою очередь описываются:
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
Код: Выделить всё
# Индентация осуществляется исключительно при помощи символов табуляции,
# каждой команде должен предшествовать отступ
<цели>: <реквизиты>
<команда #1>
...
<команда #n>
Код: Выделить всё
{Из чего делаем? (реквизиты)} ---> [Как делаем? (команды)] ---> {Что делаем? (цели)}
Код: Выделить всё
{исходные файлы} ---> [трансляция] ---> {объектные файлы}
{объектные файлы} ---> [линковка] ---> {исполнимые файлы}
Предположим, у нас имеется программа, состоящая всего из одного файла:
Код: Выделить всё
/*
* main.c
*/
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
Код: Выделить всё
hello: main.c
gcc -o hello main.c
Код: Выделить всё
$ make <цель>
Предположим, что у нас имеется программа, состоящая из 2 файлов:
Код: Выделить всё
main.c
/*
* main.c
*/
int main()
{
hello();
return 0;
}
Код: Выделить всё
/*
* hello.c
*/
#include <stdio.h>
void hello()
{
printf("Hello World!\n");
}
Код: Выделить всё
hello: main.c hello.c
gcc -o hello main.c hello.c
Инкрементная компиляция
Представим, что наша программа состоит из десятка- другого исходных файлов. Мы вносим изменения в один из них, и хотим ее пересобрать. Использование подхода описанного в предыдущем примере приведет к тому, что все без исключения исходные файлы будут снова скомпилированы, что негативно скажется на времени перекомпиляции. Решение — разделить компиляцию на два этапа: этап трансляции и этап линковки.
Теперь, после изменения одного из исходных файлов, достаточно произвести его трансляцию и линковку всех объектных файлов. При этом мы пропускаем этап трансляции не затронутых изменениями реквизитов, что сокращает время компиляции в целом. Такой подход называется инкрементной компиляцией. Для ее поддержки make сопоставляет время изменения целей и их реквизитов (используя данные файловой системы), благодаря чему самостоятельно решает какие правила следует выполнить, а какие можно просто проигнорировать:
Код: Выделить всё
main.o: main.c
gcc -c -o main.o main.c
hello.o: hello.c
gcc -c -o hello.o hello.c
hello: main.o hello.o
gcc -o hello main.o hello.o
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
Фиктивные цели
На самом деле, в качестве make целей могут выступать не только реальные файлы. Все, кому приходилось собирать программы из исходных кодов должны быть знакомы с двумя стандартными в мире UNIX командами:
Код: Выделить всё
$ make
$ make install
all — является стандартной целью по умолчанию. При вызове make ее можно явно не указывать.
clean — очистить каталог от всех файлов полученных в результате компиляции.
install — произвести инсталляцию
uninstall — и деинсталляцию соответственно.
Для того чтобы make не искал файлы с такими именами, их следует определить в Makefile, при помощи директивы .PHONY. Далее показан пример Makefile с целями all, clean, install и uninstall:
Код: Выделить всё
.PHONY: all clean install uninstall
all: hello
clean:
rm -rf hello *.o
main.o: main.c
gcc -c -o main.o main.c
hello.o: hello.c
gcc -c -o hello.o hello.c
hello: main.o hello.o
gcc -o hello main.o hello.o
install:
install ./hello /usr/local/bin
uninstall:
rm -rf /usr/local/bin/hello
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно — получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать. Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
Код: Выделить всё
$ make clean
$ make
Переменные
Все те, кто знакомы с правилом DRY (Don't repeat yourself), наверняка уже заметили неладное, а именно — наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:
Код: Выделить всё
<VAR_NAME> = <value string>
Код: Выделить всё
SRC = main.c hello.c
Код: Выделить всё
gcc -o hello $(SRC)
Код: Выделить всё
TARGET = hello
PREFIX = /usr/local/bin
.PHONY: all clean install uninstall
all: $(TARGET)
clean:
rm -rf $(TARGET) *.o
main.o: main.c
gcc -c -o main.o main.c
hello.o: hello.c
gcc -c -o hello.o hello.c
$(TARGET): main.o hello.o
gcc -o $(TARGET) main.o hello.o
install:
install $(TARGET) $(PREFIX)
uninstall:
rm -rf $(PREFIX)/$(TARGET)
Автоматические переменные
Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
$@ Имя цели обрабатываемого правила
$< Имя первой зависимости обрабатываемого правила
$^ Список всех зависимостей обрабатываемого правила
Если кто либо хочет произвести полную обфускацию своих скриптов — черпать вдохновение можете здесь:
Автоматические переменные
Заключение
В этой статье я попытался подробно объяснить основы написания и работы мэйкфайлов. Надеюсь, что она поможет вам приобрести понимание сути make и в кратчайшие сроки освоить этот провереный временем инструмент.
Взято тут