Что такое Make?
Make - это утилита, доступная под Linux, Mac и Windows (в составе MinGW), основная цель которой, состоит в автоматизации преобразования файлов из одной формы в другую.
Она проверяет выходные и входные файлы и, исходя из результата, вызывает различные команды.
Все необходимые условия описываются в скрипт-файле, который называется Makefile.
Из чего состоит скрипт файл Makefile?
Каждый Makefile содержит в себе блоки, которые состоят из 3-х элементов:
- цель - бывают реальными (выходной файл) и фиктивными (не содержит реквизитов, результат команды не обязан создавать файл на выходе).
- реквизиты - список исходных файлов, если один из них изменен позже, чем цель (выходной файл), тогда будет вызвана команда.
- команды - список терминальных команд, которые будут вызваны, если условие соблюдается, состоит из пути к исполняемому файлу и аргументов к нему.
Если у нас есть исходный файл main.c и заголовочный файл к нему main.h, а выходным должен быть исполняемый файл main.exe. Компилятором будет служить gcc.exe. Тогда примером Makefile будет следующее:
main.exe: main.c main.h
gcc.exe -o main.exe main.cpp
Обратите внимание, что каждая команда обязана иметь отступы, индентация (стиль отступов) которых требует использовать для этого только символы табуляции (TAB).
Правила выполнения команд
- Каждая команда выводится в терминале, чтобы этого избежать, можно добавить @ в начале или вызвать make с аргументом -s (silent), тогда каждая команда не будет выводиться.
- Команда с каждой новой строки выполняется в новом shell, чтобы этого избежать нужно перечислять команды одной строкой через ; (точку с запятой).
- Так же можно указать shell, SHELL=/bin/sh по умолчанию, можно изменить на SHELL=/bin/bash.
Фиктивные цели
С реальными целями все ясно, это просто файлы, которые должны быть созданы и, если их нет или они устарели, тогда мы вызываем нужные команды.
Но зачем нужны фиктивные цели? Фиктивными целями может служить группа из реальных целей, например часто применяемая ALL, которая будет содержать в себе все цель по умолчанию.
Можно выделить список часто применяемых:
- all - цель по умолчанию, если вызвать make без аргументов, если явно не указать, тогда будет вызвана первая в Makefile.
- clean - очищаем каталог от результата компиляции.
- install - установка, перенос скомпилированных файлов в необходимый системный каталог.
- uninstall - соотвественно, удаление из системы.
Чтобы объявить такую цель, нужно использовать директиву .PHONY, на примере нашего списка объявим их следующим образом
.PHONY: all clean install uninstall
all: main.exe
clean:
rm -rf main.exe *.o
main.o: main.c
gcc -c -o main.o main.c
main.exe: main.o
gcc -o main.exe main.o
install:
install ./main.exe /ваш/путь
uninstall:
rm -rf /ваш/путь/main.exe
Инкрементная компиляция
Поскольку компиляция больших проектов может занимать долгие часы, а внося небольшую правку каждый раз тратить столько времени попросту не эффективно. Было принято решение разделить процесс компиляции на этапы.
- трансляция - процесс преобразования кода в объектный файл, который содержит бинарный код.
- линковка - объединение объектных файлов в результирующий исполняемый.
Именно этим и занимается утилита make, отслеживает зависимости и вызывает команды, цели которых устарели или попросту отсутствуют.
Переменные
В скрипт-файле Makefile допустимо использование переменных, именовать их ЗАГЛАВНЫМИ буквами, является правилом хорошего тона.
Переменные могут быть определены в разных местах
- default - переменная определена самим компилятором
- environment - переменная унаследована от окружения
- environment override - унаследована и переопределена через опцию -e
- file - определена в makefile
- override - переменная определена в makefile с директивой override
- automatic - автоматическая переменная
все предопределенные правила, команды и переменные утилиты make можно посмотреть вызвав make -p
Передача переменных в момент вызова make
make CFLAGS=-O2 и CFLAGS=-O2 make — будет по-разному интерпретироваться. Т.е. в первом случае CFLAGS будет принудительно перезаписан, вне зависимости от того что прописано в самом makefile, а во втором — только если разрешил разработчик.
Автоматические переменные
например, для проверки заголовочных файлов, можно использовать следующую конструкцию
main.o: main.cpp header_a.h header_b.h
g++ -c -o main.o $<
отсюда $< будет содержать первый реквизит исполняемого правила, а именно main.cpp
Перечень автоматических переменных
- $@ - имя файла цели.
- $% - имя элемента архива, если цель foo.a(bar.o), тогда $@ = foo.a, а $% = bar.o
- $< - имя первого пререквизита.
- $? - имена всех пререквизитов (разделены пробелами), которые новее, чем цель.
- $^ - имена всех пререквезитов (разделены пробелами), которые являются элементами архива.
- $+ - аналог $^, за исключением того, что не будут удалены дубли.
- $* - основа (stem), которая будет получена из шаблона.
Пример использования $*
.PHONY: all
all: mainA mainB
mainA mainB: main%: main.cpp
g++ $< -o main$*
На выходе получаем два исполняемых файла mainA и mainB.
Условия
Если есть переменные, должны быть и условия. Как и везде, можно выполнить или игнорировать часть команд в скрипт-файле, исходя из значений переменных.
Можно выделить 4 условия:
- ifeq - if equal
- ifneq - if not equal
- ifdef - if defined
- ifndef - if not defined
Пример применения аргументов
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
Функции
Так же make позволяет использовать функции, в основном их применяют для обработки текста:
- subst (substitution string) - заменяет все вхождения подстроки.
bar := ${subst not,totally, "I am not superman"}
all:
@echo $(bar)
на экране будет -> "I am totally superman"
- patsubst (pattern substitution string)- аналог subst, но так же будет применен шаблон
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
all:
echo $(one)
на экране будет -> "a.c b.c l.a c.c"
- foreach - обрабатывает слова из строки (разделенные пробелом).
foo := who are you
bar := $(foreach wrd,$(foo),$(wrd)!)
all:
@echo $(bar)
на экране -> "who! are! you!"
- if - проверяет переменную, если она пуста, тогда выдает третий аргумент, если нет - второй.
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
на экране будет -> "then! else!"
- call - вызов переменной, с передачей ей аргументов.
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
@echo $(call sweet_new_fn, go, tigers)
на экране будет -> "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable: "
- shell - вызывает shell, но в выводе заменяет все переносы строки на пробелы.
all:
@echo $(shell ls -la)
на экране будет весь список файлов в рабочей директории одной строкой.
Учимся вместе здесь https://t.me/osiechan