После выполнения make не появился исполняемый файл. Makefile для самых маленьких

11.04.2019

Утилита makeтрадиционно служит для построения исполняемых образов программ. Построение происходит по заданным правилам, которые могут определяться пользователем в специальном файле. Кроме непосредственно набора команд, с помощью которых производится построения того или иного файла, пользователь может задать зависимости между файлами, составляющими программный проект. При этом изменения или обновления какого-либо файла автоматически влечет перепостроение, других файлов, зависящих от обновленного. Также правила могут использовать переменные как те, которые были заданы в системном окружении или переданы через параметры при запуске утилиты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.

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

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

Make- основные сведения

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

1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).

В общем виде синтаксис makefile можно представить так:

# Индентация осуществляется исключительно при помощи символов табуляции, # каждой команде должен предшествовать отступ <цели>: <реквизиты> <команда #1> ... <команда #n>

То есть, правило make это ответы на три вопроса:

{Из чего делаем? (реквизиты)} ---> [Как делаем? (команды)] ---> {Что делаем? (цели)}
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:

{исходные файлы} ---> [трансляция] ---> {объектные файлы}
{объектные файлы} ---> [линковка] ---> {исполнимые файлы}

Простейший Makefile

Предположим, у нас имеется программа, состоящая всего из одного файла:

/* * main.c */ #include int main() { printf("Hello World!\n"); return 0; }
Для его компиляции достаточно очень простого мэйкфайла:

Hello: main.c gcc -o hello main.c
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели - «hello», реквизита - «main.c», и команды - «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:

$ make <цель>

Компиляция из множества исходников

Предположим, что у нас имеется программа, состоящая из 2 файлов:
main.c
/* * main.c */ int main() { hello(); return 0; }
и hello.c
/* * hello.c */ #include void hello() { printf("Hello World!\n"); }
Makefile, выполняющий компиляцию этой программы может выглядеть так:

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 целей могут выступать не только реальные файлы. Все, кому приходилось собирать программы из исходных кодов должны быть знакомы с двумя стандартными в мире UNIX командами:

$ make $ make install
Командой make производят компиляцию программы, командой make install - установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:

  • 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
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.

Обратите внимание на то, что в цели all не указаны команды; все что ей нужно - получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать . Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:

$ make clean $ make
Для выполнения целей install/uninstall вам потребуются использовать sudo.

Переменные

Все те, кто знакомы с правилом DRY (Don"t repeat yourself), наверняка уже заметили неладное, а именно - наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:

=
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:

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 предназначена для автоматизации сборки проектов. Если какие-либо файлы проекта могут быть сгенерированы из других файлов, утилита позволяет выполнить процесс построения наиболее оптимальным способом, по возможности минимизируя количество обрабатываемых файлов.

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

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

    GNU make - самый распространенный и функциональный вариант

    BSD make (pmake) - используется в проектах BSD, по функциональности примерно соответствует GNU make

    nmake (Microsoft make) - работает под Windows, малофункционален, только базовые принципы make.

Мы работаем с GNU make. На BSD системах (в частности, FreeBSD, он может быть доступен как gmake, на Linux - просто make).

Основные принципы

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

Цели могут соответствовать определенным файлам. Кроме того, цели могут не соответствовать ни одному файлу и использоваться для группировки других целей или определенной последовательности команд. Такие цели называются phony targets.

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

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

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

Запуск make

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

Соответственно, команда

$ make

будет использовать файл Makefile , находящийся в текущем каталоге.

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

Явное указание цели выполняется инструкцией DEFAULT_GOAL в Makefile:

. DEFAULT_GOAL: all

Например, команда

$ make clean

вызовет обработку цели clean файла Makefile , находящегося в текущем каталоге.

Можно указать сразу несколько целей.

Выполнение целей может быть настроено с использованием переменных (о которых ниже). При запуске make можно указать значения переменных:

$ make build PREFIX =/ usr/ local

Значение переменной PREFIX будет доступно в правилах Makefile и может быть использовано при сборке.

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

    F - позволяет явно указать файл правил

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

    B - отключает проверку времени зависимых целей и принудительно выполняет их полностью

Базовый синтаксис make

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

Target: dep1 dep2 ... command1 command2 ...

    target - цель

    dep1 , dep2 - цели, от которых зависит цель target

    command1 , command2 - команды, выполняемые для достижения цели target

Например:

Style. css: src/ less/ app. less lessc src/ less/ app. less > style. css

Этот фрагмент определяет, что файл style.css зависит от файла src/less/app.less и для его сборки необходимо выполнить команду lessc src/less/app.less > style.css . Перегенерация файла style.css будет выполняться только в случае,если файл src/less/app.less новее, чем файл style.css (до тех пор, пока при запуске make не будет указан ключ -B).

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

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

Каждая команда запускается в отдельном интерпретаторе shell, таким образом, команды не связаны друг с другом. Иначе говоря, одна строка команды - один shell. Это поведение может быть переопределено с помощью специальной цели.ONESHELL .

Если команду (или список зависимостей) необходимо записать в несколько строк, используют символ переноса \ .

PHONY targets

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

.PHONY : clean clean: rm *. o temp

Деклараций.PHONY может быть несколько, обычно определяют одну и прописывают туда все соответствующие цели.

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

В случае, если у phony target есть зависимость в виде другой phony target, то зависимость выполняется перед зависящей целью. Таким образом, мы получаем механизм, напоминающий подпрограммы. Например, мы можем определить цель all , собирающую все файлы проекта, и отдельные цели css , js и php , собирающие отдельной css -файлы, js -файлы и обрабатывающие php файлы.

Соответственно, в Makefile мы можем написать:

.PHONY : all css js php all: css js php css: www/ style. css ... тут команды js: www/ js/ app. js ... тут еще команды php: ... тут снова команды

В результате мы можем использовать make all для пересборки всех файлов и, скажем, make css для пересборки только CSS -файлов.

Переменные

В make-файле можно использовать переменные, хотя правильнее сказать, что можно использовать макросы.

Переменные определяются присваиванием в makefile или могут быть переданы извне.

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

СС= gcc IDIR=../ include CFLAGS=- I$ (IDIR ) DEPS= hellomake. o hellofunc. p NAME= hellomake $ (NAME ) : $ (DEPS ) $ (CC ) - o $ (NAME ) $ (DEPS )

Подстановка выполняется конструкцией $(VAR) в отличие от shell, где используется $VAR .

Если в shell команде используется shell-переменная, необходимо квотить знак $ , дублируя его, например:

Printhome: echo $$ HOME

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

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

MYSQL_CHARSET ?= UTF8

Соответственно, если мы вызовем

make create-database

будет использована кодировка UTF8 , а в случае

Make create- database MYSQL_CHARSET= CP1251

будет использована CP1251 .

Если переменная содержит несколько строк, можно использовать синтаксис define:

Define MYSQL_APP_CONF_TEMPLATE [ mysql] host=$ (MYSQL_SERVER ) port=$ (MYSQL_PORT ) user=$ (MYSQL_USER ) password= "$(MYSQL_PASSWORD)" endef

Автоматические переменные

Make поддерживает набор автоматических переменных, облегчающих написание правил. Например, переменная $@ соответствую текущей цели (то, что слева от:), а переменная $^ - списку зависимостей (то, что справа от:). Таким образом, например, можно написать:

Www/ js/ script. js: src/ js/ jquery. js src/ js/ plugin1. js src/ js/ plugin2. js cat $^ > $@

В результате www/js/script.js будет результатом объединения трех js-файлов.

Полный список таких переменных приведен в документации, для нас наиболее интересны:

    $@ - имя цели

    $< - имя первой зависимости

    $? - имена всех зависимостей, которые новее чем цель

    $^ - имена всех зависимостей цели

С полным списком можно ознакомиться в документации: Automatic Variables .

Условное выполнение

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

ifdef $ (APP ) setup: ... else setup: @ echo "Error, applications is not defined" endif

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

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

Полностью с возможностями условных выражений можно ознакомиться в документации: Conditional syntax .

Шаблонные правила

Шаблонные правила (pattern rules) позволяют указать правило преобразования одних файлов в другие на основании зависимостей между их именами. Например, мы можем указать правило для получения объектного файла из файла на языке C:

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

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

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

Включение других файлов make

Файл make может подключить другие файлы make оператором include:

include make1. mk make2. mk

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

Функции

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

$ (function arg1, arg2,... )

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

Несколько примеров из hive. Получаем текущее время (обратите внимание на использование:= :

HIVE_TIME := $ (shell date +% Y/% m/% d\ % H:% M:% S)

Включение файла container.mk только в случае, если он существует:

include $ (shell find $ (HIVE_ETC_DIR ) - name container. mk)

Формирование имени MySQL базы по имени проекта:

MYSQL_DB ?= $ (subst -, _,$ (HIVE_NAME ) _$ (APP ) )

Добавление префиксов и суффиксов к именам файлов

$ (addprefix src/ less/,$ (addsuffix . less, app ui catalog) )

Подробнее о функциях можно прочитать в документации Functions .

Собственные функции

Можно создавать собственные параметризованные функции путем определения переменных, содержащих специальные переменные $1 , $2 , ..., соответствующие переданным аргументам. Вызов пользовательской функции производится специальным макросом call:

$ (call variable, param, param,... )

Очень тупой пример:

Hive_red = "\0 33}