Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание UNIX. И хотя UNIX давно уже нельзя назвать простой системой, да и минимализм в ней узреть не так то просто, ее можно считать наглядным примером количество- качественной трансформации множества простых и понятных вещей в одну весьма непростую и не прозрачную. В своем развитии make прошел примерно такой же путь: простота и ясность, с ростом масштабов, превратилась в жуткого монстра (вспомните свои ощущения, когда впервые открыли мэйкфайл).
Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом "пережитке прошлого" (по сути - ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
# Индентация осуществляется исключительно при помощи символов табуляции, # каждой команде должен предшествовать отступ <цели>: <реквизиты> <команда #1> ... <команда #n>
То есть, правило make это ответы на три вопроса:
{Из чего делаем? (реквизиты)} ---> [Как делаем? (команды)] ---> {Что делаем? (цели)}
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:
{исходные файлы} ---> [трансляция] ---> {объектные файлы}
{объектные файлы} ---> [линковка] ---> {исполнимые файлы}
/*
* main.c
*/
#include
Для его компиляции достаточно очень простого мэйкфайла:
Hello: main.c
gcc -o hello main.c
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели - «hello», реквизита - «main.c», и команды - «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:
$ make <цель>
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.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
$ make
$ make install
Командой make производят компиляцию программы, командой make install - установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:
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
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно - получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать . Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
$ make clean
$ make
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
SRC = main.c hello.c
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $(
Gcc -o hello $(SRC)
Ниже представлен мэйкфайл, использующий две переменные: TARGET - для определения имени целевой программы и PREFIX - для определения пути установки программы в систему.
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)
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.
Linux: Полное руководство Колисниченко Денис Николаевич
21.2. Сборочная утилита make
Если вы уже собирали прикладную программу из исходных кодов, то обратили внимание на стандартную последовательность команд: make; make install .
Без утилиты make не обходится создание ни одного серьезного проекта. Эта утилита управляет сборкой большого проекта, состоящего из десятков и сотен файлов. Программа make может работать не только с компилятором gcc, но и с любым компилятором для любого языка программирования, способным запускаться из командной строки.
Директивы утилиты make служат для определения зависимостей между файлами проекта и находятся в файле по имени Makefile, расположенном в каталоге сборки.
Разберемся, как пишутся make-файлы. Общий формат make-файла выглядит так:
цель1: список_необходимых_файлов
последовательность_команд
цельN: список_необходимых_файлов
последовательностъ_команд
Цель - это метка для некоторой последовательности команд (например, install) или результирующий файл, который нужно «построить» - скомпилировать или скомпоновать.
Цели должны отделяться друг от друга хотя бы одной пустой строкой. Список необходимых файлов - это перечень файлов или других целей, которые нужны для достижения данной цели; он может быть и пустым.
Последовательность команд - это команды, которые нужно выполнить для достижения цели. Последовательность команд должна отделяться от начала строки символом табуляции, иначе вы получите ошибку «missing separator» (нет разделителя).
Make-файл может содержать комментарии - они начинаются символом #.
В make-файлах вы можете использовать макроопределения:
PATH=/usr/include /usr/src/linux/include
MODFLAGS:= -O3 -Wall -DLINUX -I$(PATH)
$(CC) $(MODFLAGS) -c proga.c
Чтобы обратиться к макроопределению в команде или в другом макроопределении, нужно использовать конструкцию $(имя). Макроопределение может включать в себя другое, ранее определенное, макроопределение.
Формат запуска утилиты make:
make [-f файл] [ключи] [цель]
Ключ -f указывает файл инструкций, который нужно использовать вместо Makefile. Если этот ключ не указан, то make ищет в текущем каталоге файл Makefile и начинает собирать указанную цель. Если цель не указана, то выполняется первая встреченная в make-файле. Сборка выполняется рекурсивно: make сначала выполняет все цели, от которых зависит текущая цель. Если зависимость представляет собой файл, то make сравнивает его время последней модификации со временем целевого файла: если целевой файл старше или отсутствует, то будет выполнена указанная последовательность команд. Если целевой файл моложе, то текущая цель считается достигнутой.
Примечание
Если нужно избежать пересборки какого-то из файлов проекта, то можно искусственно «омолодить» его командой touch , которая присвоит ему в качестве времени последней модификации текущее время. Если нужно, наоборот, принудительно пересобрать цель, то следует «омолодить» один из файлов, от которых она зависит.
Работа программы make заканчивается, когда достигнута цель, указанная в командной строке. Обычно это цель all , собирающая все результирующие файлы проекта. Другими распространенными целями являются install (установить собранную программу) и clean (удалить ненужные файлы, созданные в процессе сборки).
В листинге 21.2 представлен make-файл, собирающий небольшой проект из двух программ client и server , каждая из которых компилируется из одного файла исходного кода.
Листинг 21.2. Примерный make-файл
all: client server
client: client.с
$(CC) client.с -о client
server: server.с
$(CC) server.с -о server
Обычно при вызове утилиты make не нужно задавать никаких ключей. Но иногда использование ключей бывает очень кстати (таблица 21.1).
Ключи команды make Таблица 21.1
Ключ | Назначение |
---|---|
-C каталог | Перейти в указанный каталог перед началом работы |
-d | Вывод отладочной информации |
-e | Приоритет переменным окружения. Если у нас установлена переменная окружения CC и в Makefile есть переменная с таким же именем, то будет использована переменная окружения |
-f файл | Использовать указанный файл вместо Makefile |
-i | Игнорировать ошибки компилятора |
-I каталог | В указанном каталоге будет производиться поиск файлов, включаемых в Makefile |
-j n | Запускать не более n команд одновременно |
-k | Продолжить работу после ошибки, если это возможно |
-n | Вывести команды, которые должны были выполниться, но не выполнять их |
-о файл | Пропустить данный файл, даже если в Makefile указано, что он должен быть создан заново |
-r | Не использовать встроенные правила |
-s | Не выводить команды перед их выполнением |
-w | Вывод текущего каталога до и после выполнения команды |
Установка Make-3.79.1 Приблизительное время компиляции: 0.22 SBU Необходимое дисковое пространство: 6 MBИнсталляция MakeДля инсталляции Make выполните:./configure –prefix=/usr && make && make install && chgrp root /usr/bin/make && chmod 755 /usr/bin/makeПояснения командПо умолчанию, /usr/bin/make принадлежит к той
Из книги Искусство программирования для Unix автора Реймонд Эрик СтивенMake Официальная ссылкаMake (3.79.1): ftp://ftp.gnu.org/gnu/make/Содержимое MakeПоследняя проверка: версия 3.79.1.ПрограммыmakeОписанияmakemake автоматически определяет, какие части объемной программы нуждаются в рекомпиляции, и выполняет все необходимые для этого команды.Зависимости MakeПоследняя
Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К. Из книги C++. Сборник рецептов автора Диггинс Кристофер Из книги автора Из книги автора15.4.4.2. Imake Утилита Imake была написана в попытке автоматизировать создание make-файлов для системы X Window. Она надстраивается на makedepend для решения как проблемы вывода зависимостей, так и проблемы переносимости.Imake-система эффективно заменяет традиционные make-файлы 1make-файлами,
Из книги автора15.8.1. Emacs и make Например, утилиту make можно запустить из Emacs с помощью команды ESC-x compile , Данная команда запускает make(1) в текущем каталоге, собирая вывод в буфер Emacs.Сама по себе данная операция не была бы очень полезной, но Emacs-режим make распознает формат сообщений об ошибках
Из книги автора15.4. Утилита make: автоматизация процедур Сами по себе исходные коды программ не делают приложения. Также важен способ их компоновки и упаковки для распространения. Операционная система Unix предоставляет инструментальное средство для частичной автоматизации данных
Из книги автора15.4.1. Базовая теория make При разработке программ на языках С или С++ важной частью для построения приложения является семейство команд компиляции и компоновки, необходимых для получения из файлов исходного кода работающих бинарных файлов. Ввод данных команд - длительная и
Из книги автора15.4.2. Утилита make в разработке не на C/C++ Программа make может оказаться полезной не только для программ на C/C++. Языки сценариев, подобные описанным в главе 14, могут не требовать традиционных этапов компиляции и компоновки, однако часто существуют другие виды зависимостей, с
Из книги автора15.4.3. Правила make Некоторые из наиболее интенсивно используемых правил в типичных make-файлах вообще не выражают зависимостей. Они позволяют связать небольшие процедуры, которые разработчик хочет механизировать, например, создание дистрибутивного пакета или удаление всех
Из книги автора15.8.1. Emacs и make Например, утилиту make можно запустить из Emacs с помощью команды ESC-x compile . Данная команда запускает make(1) в текущем каталоге, собирая вывод в буфер Emacs.Сама по себе данная операция не была бы очень полезной, но Emacs-режим make распознает формат сообщений об ошибках
Из книги автора Из книги автора1.14. Получение GNU make ПроблемаВы хотите получить и установить утилиту GNU make, используемую для сборки библиотек и исполняемых файлов из исходного кода.РешениеРешение зависит от вашей операционной системы.WindowsХотя в некоторых источниках можно получить готовые бинарные
Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание UNIX. И хотя UNIX давно уже нельзя назвать простой системой, да и минимализм в ней узреть не так то просто, ее можно считать наглядным примером количество- качественной трансформации множества простых и понятных вещей в одну весьма непростую и не прозрачную. В своем развитии make прошел примерно такой же путь: простота и ясность, с ростом масштабов, превратилась в жуткого монстра (вспомните свои ощущения, когда впервые открыли мэйкфайл).
Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом "пережитке прошлого" (по сути - ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
# Индентация осуществляется исключительно при помощи символов табуляции, # каждой команде должен предшествовать отступ <цели>: <реквизиты> <команда #1> ... <команда #n>
То есть, правило make это ответы на три вопроса:
{Из чего делаем? (реквизиты)} ---> [Как делаем? (команды)] ---> {Что делаем? (цели)}
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:
{исходные файлы} ---> [трансляция] ---> {объектные файлы}
{объектные файлы} ---> [линковка] ---> {исполнимые файлы}
/*
* main.c
*/
#include
Для его компиляции достаточно очень простого мэйкфайла:
Hello: main.c
gcc -o hello main.c
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели - «hello», реквизита - «main.c», и команды - «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:
$ make <цель>
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.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
$ make
$ make install
Командой make производят компиляцию программы, командой make install - установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:
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
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно - получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать . Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
$ make clean
$ make
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
SRC = main.c hello.c
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $(
Gcc -o hello $(SRC)
Ниже представлен мэйкфайл, использующий две переменные: TARGET - для определения имени целевой программы и PREFIX - для определения пути установки программы в систему.
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традиционно служит для построения исполняемых образов программ. Построение происходит по заданным правилам, которые могут определяться пользователем в специальном файле. Кроме непосредственно набора команд, с помощью которых производится построения того или иного файла, пользователь может задать зависимости между файлами, составляющими программный проект. При этом изменения или обновления какого-либо файла автоматически влечет перепостроение, других файлов, зависящих от обновленного. Также правила могут использовать переменные как те, которые были заданы в системном окружении или переданы через параметры при запуске утилитыmake, так и те, которые определяются пользователем непосредственно в файле описания правил. Кроме того, в синтаксисеmakeопределен набор функций для работы с переменными, которые позволяют довольно гибко настраивать правила под различные проекты.
Командная строка запуска утилиты makeвыглядит так:
Make[опции] [переменные] [цели_построения]
Опции – параметры, определяющие дополнительные условия при выполнении правил. Опции всегда начинаются со знака ‘-‘.
Например:
make -f aaa -s -v
Предписывает выводить команды, порождаемые make"ом, не выполняя их на самом деле |
|
Подавляет вывод командный строк перед их выполнением (если входящая в правило команда начинается с @, она не распечатывается) |
|
Отменяет действие опции –k. Это необходимо только при рекурсивном запуске make. |
|
Предписывает игнорировать ошибки (ошибка игнорируется также, если командная строка в файле описаний начинается со знака минус) |
|
Определяет директории для поиска включенных makefiles. В случае, если определяется несколько различных путей, поиск будет производиться в порядке их перечисления. |
|
При возникновении ошибки выполнение команд, связанных с текущей зависимостью, прекращается, однако обработка других зависимостей продолжается |
|
F имя_файла |
Задает имя файла описаний (пример: make -f mymakefile) |
Указывает make"у вывести все макроопределения, а также описания зависимостей и операций для создания целевых_файлов . Если использовать пустой make-файл, мы получим полную информацию о неявных предопределенных правилах, макросах и суффиксах. Имя файла- обозначает стандартный ввод. Если опция не указана, читается файл с именемmakefile илиMakefile . |
|
осуществляется переход в директорию dir до прочтения makefile. В случае многократного применения опции -C , каждая из них относится к предыдущей: -C / -C etc эквивалентно -C /etc. Это часто используется при рекурсивном вызове make. |
|
Дает переменным оболочки больший приоритет, чем переменным makefile. |
|
Отключение встроенных правил (по умолчанию). Очистка списка суффиксов по умолчанию. |
|
Печать версии make |
|
Печать имени текущего каталога |
Make обычно прекращает работу, если какая-либо команда возвращает ненулевой код завершения (чаще всего это признак ошибки).
Опции не обязательно указывать в командной строке, их можно помещать в переменную окружения MAKEFLAGS . Она обрабатывается как содержащая любую из допустимых опций (естественно, кроме -f). При запуске make в нее дописываются опции, указанные в командной строке.
Переменные – параметры, позволяющие задать новые значения уже созданных переменных или определить новые переменные, значения которых могут быть использованы при выполнении правил. Переменные, указанные в командной строке имеют более высокий приоритет, чем переменные, указанные в makefile.
Например:
makeABC=”aaa.c”CPU=”x86” - при запуске определяется две переменныеABCиCPU.
Цели построения – имена файлов или просто символические имена, которые при выполнении правил считаются конечным результатом выполнения утилиты make. Важно чтобы в файле описания правил существовало правило для построения указанной цели. Если такого правила не существует, то утилита выдаст ошибку.
Если при запуске утилиты не указывается ни одной цели, то по умолчанию будет выполнена попытка построить цель, указанную в первом найденном правиле.
Например:
Maketarget- выполнить построение целиtarget.