-
Notifications
You must be signed in to change notification settings - Fork 1
doc_rus
Простая, быстрая, нерекурсивная, платформонезависимая, шаблонная, расширяемая система сборки, полагающая только на возможности Gnu Make.
В отличие от генераторов систем сборок, таких как CMake, Meson, clean-build представляет собой набор платформо-независимых макросов, при сборке раскрывающихся в непосредственный вызов утилит командной строки - компиляторов, линкеров и др., с возможностью сохранения генерируемых команд в shell-скирпт или batch-файл для сборки уже без участия clean-build, либо для отладки сборки.
clean-build не является заменой системам конфигурирования проектов, таким как Autotools или pkg-config.
Конфигурирование проекта, как то установка и настройка компиляторов, devel-версий внешних библиотек и т.д. должно быть выполнено до запуска сборки.
Задача clean-build - максимально быстро выполнить сборку проекта, аналогично минималистичной системе сборки Ninja.
Однако, в отличие от Ninja, имеющей очень ограниченный язык сценариев сборки и обычно используемой совместно с генератором сценариев сборки, clean-build самодостаточен - возможностей языка Gnu Make достаточно для написания довольно сложных сценариев сборки.
Использование шаблонов позволяет создавать сценарии clean-build краткими, понятными, не теряя при этом возможность изменять любой параметр вызываемой утилиты.
clean-build хорошо подходит для осуществления периодических сборок при кросс-платформеной разработке проекта.
- Принципы построения
- Основные характеристики
- Установка
- Запуск сборки
- Пример проекта
- Примеры правил сборки
-
Декларативное описание сценариев сборки
Для типовых сценариев сборки, в целевом Makefile достаточно указать один из предопределённых шаблонов сборки цели и параметры для его инициализации.
Также есть возможность создания произвольных шаблонов и правил сборки, используя функционал языка Gnu Make и примеры шаблонов clean-build.
-
Нерекурсивность выполнения сборки
Структура большого проекта обычно состоит из нескольких модулей, у каждого из которых есть своя под-директория проекта и свой сценарий сборки, расположенный в этой под-директории.
В рекурсивных системах сборки, порядок сборки модулей определяется порядком рекурсивного обхода их под-директориий.
В clean-build также осуществляется рекурсивный обход под-директориий проекта - для построения единого графа зависимостей целей между собой.
Затем сборка осуществляется в порядке, определяемом только зависимостями целей между собой.
Единый граф зависимостей позволяет максимально распараллелить сборку целей проекта, утилизируя все доступные ресурсы сборочной машины.
-
Использование только нативных утилит сборочной платформы
Система сборки минималистична, помимо стандартных утилит сборочной платформы, необходим только Gnu Make и, опционально, sed (например Gnu Sed).
Gnu Make и sed доступны в нативном виде для множества платформ, включая изначально поддерживаемые clean-build ОС Windows, Linux и Solaris.
- нерекурсивность выполнения сборки
- массивное распараллеливание построения целей, практически не ограниченное масштабирование по количеству процессоров сборочной платформы
- модульный, расширяемый дизайн
- платформо-независимые сценарии сборки - через одинаковый для всех ОС набор шаблонов (при их платформо-зависимой реализации)
- многоплатформенность, использование только нативных утилит сборочной платформы (для сборки на WINDOWS не нужен слой эмуляции UNIX)
- возможность указания зависимости выполнения сценариев сборки друг от друга - для задания порядка сборки подсистем проекта
- поддержка кросс-компиляции (в рамках одного типа ОС, но различной архитектуры центрального процессора сборочной и целевой машин)
- встроенная поддержка сборки целей из исходных текстов на языках: C, C++, Java, Scala, Wix
- поддержка предкомпилированных заголовочных файлов для языков C/C++
- поддержка автоматической генерации в процессе сборки описаний зависимостей исходных текстов от заголовочных файлов (для C/C++)
- поддержка групповой компиляции нескольких файлов за один вызов компилятора (для cl.exe на WINDOWS для C/C++)
- встроенные средства для отладки, проверки и трассировки сценариев сборки
- предопределённые шаблоны для сборки динамических и статических библиотек, исполняемых файлов, модулей ядра и архивов java
- поддержка генерации нескольких целей за один вызов заданной команды
- простое создание batch-файла или shell-скрипта для сборки без использования Gnu Make:
make V=1 > build.bat
- предсказуемость сборок, независимость от переменных окружения
- возможность переопределения любого макроса clean-build без редактирования сценариев сборки
- поддержка генерации конфигурационного файла с параметрами сборки - для повторяемых сборок
- красивый вывод информации о процессе сборки
- скорость: время проверки необходимости пересборки изменений проекта из 3000 файлов при отсутствии изменений - менее 1 секунды
Достаточно скопировать clean-build в локальную директорию, например в C:\tools
(для Windows) или ${HOME}
(для Unix-like ОС).
Сборка осуществляется вызовом интерпретатора Gnu Make, обычно в директории проекта, где находится корневой Makefile.
Система сборки сама сообщит о неопределённых параметрах сборки, полный список которых определяется проектом, например:
- путь к clean-build (обычно через определение переменной
MTOP
) -
TARGET
- тип сборки (напримерDEBUG
илиRELEASE
) -
OS
- тип операционной системы сборочной платфорvы (напримерWINXX
,LINUX
илиSOLARIS
) -
CPU
- архитектура процессора исполняемых файлов (напримерx86
илиx86_64
).
Параметры сборки, за некоторым исключением (возможно, переменной MTOP
), должны быть заданы либо в командной строке, либо в конфигурационном Makefile.
Замечание. Переменные, заданные в строке запуска make
, имеют наивысший приоритет и переопределяют переменные, оределённые в Makefile'ах.
Определение параметров сборки через переменные окружения, обычно, не используется (из-за сложности контроля за переменными окружения - все переменные окружения по умолчанию становятся переменными Makefile).
Перед началом сборки, все переменные окружения устанавливаются в предзаданные значения или очищаются, чтобы сборка не зависела от значений переменных окружения.
Рассмотрим пример проекта, идущего в составе clean-build, расположенного в директории examples/hello
.
Проект состоит из следующих файлов:
- Makefile
- project.mk
- hello.mk
- hello.c
- readme.txt
Makefile
Корневой Makefile проекта, обычно служит для подключения суб-модулей проекта.
Разберём его содержимое построчно.
-
Первой строкой подключается
project.mk
- конфигурационный Makefile проекта.include $(dir $(lastword $(MAKEFILE_LIST)))project.mk
здесь:
$(MAKEFILE_LIST)
- список всех подключённых Makefile'ов до выполнения этой строки.функция
lastword
возвращает из этого списка последнее слово - имя текущего Makefileфункция
dir
- возвращает директорию текущего Makefile, в которой находитсяproject.mk
Замечание:
- project.mk` должен быть подключён всегда первым, вначале любого Makefile проекта
- подключение
project.mk
включает необходимые определения для выполнения данного Makefile индивидуально, напрямую (через ключ-f
), а не через запуск сборки от корня проекта
-
строка
include $(MTOP)/parallel.mk
добавляет определения макросов clean-build, используемых для подключения в сборку суб-модулей проекта.
Здесь
$(MTOP)
- путь к clean-build, определённый вproject.mk
. -
следующей строкой проверяется, что цель сборки - не
distclean
.ifneq (distclean,$(MAKECMDGOALS))
Цель
distclean
предопределена в clean-build, служит для удаления всех результатов сборки.При выполнении этой цели просто удаляется директория
$(BUILD)
- директория результатов сборки, путь к которой задаётся вproject.mk
.Обработка суб-модулей проекта при выполении
distclean
не требуется. -
строка
TO_MAKE := hello.mk
определяет произвольную переменную
TO_MAKE
(имя выбрано произвольно), в которую записывается имя Makefile'а суб-модуля. -
если целевая платформа - Windows, то в сборку добавляется предопределённый в clean-build суб-модуль version.
ifeq (WINXX,$(OS)) TO_MAKE += $(MTOP)/exts/version endif
Подключение этого модуля нужно для генерации стандартного ресурсного файла с информацией о дате/версии/производителе и др. данных, включаемых в создаваемые исполняемые файлы и динамические библиотеки.
-
предпоследняя строка
$(call PROCESS_SUBMAKES,$(TO_MAKE))
- вызов макроса clean-build
PROCESS_SUBMAKES
, служащего для обработки суб-модулей проекта, в данном случае перечисленных в переменнойTO_MAKE
.
- вызов макроса clean-build
-
финальная строка
endif
завершает условное выражение
ifneq (distclean,$(MAKECMDGOALS))
.
project.mk
Конфигурационный Makefile проекта, определяет переменные проекта, как необходимые для clean-build, так и специфичные для данного проекта.
Разберём его содержимое.
-
Проверяем, определена ли переменная
TOP
- корневая директория проекта.ifneq (override,$(origin TOP))
Эта переменная заведена для удобства, например для указания путей к исходным файлам проекта.
Переменная
TOP
определяется автоматически, вproject.mk
, поэтому должна иметь атрибутoverride
- переопределённая переменная в файле.Если переменная
TOP
уже определена, значитproject.mk
уже был обработан и дальнейшие определения пропускаются. -
Иначе, определяем переменную
TOP
.override TOP := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
Зная, что
project.mk
находится в корневой директории проекта, просто присваиваемTOP
значение абсолютного пути к директории текущего Makefile.Замечание:
TOP
определяется с аттрибутомoverride
, чтобы указать, что это определение имеет высший приоритет над (случайным) определениемTOP
в командной строке. -
Опционально, задаём версию clean-build, требуемую для сборки данного проекта.
CLEAN_BUILD_REQUIRED_VERSION := 0.6.3
Эта переменная, если определена, проверяется clean-build.
Если проект требует неподдерживаемую версию clean-build, то сборка завершится с ошибкой.
-
Единственная обязательная переменная, требуемая clean-build - это путь к директории для создаваемых в процессе сборки файлов.
BUILD := $(TOP)/build
Замечание:
BUILD
можно переопределить, если задать значение этой переменной в командной строке (или в специальном Makefile переопределений, речь о котором идёт ниже). -
Задаём переменные, необходимые для генерации стандартного ресурсного файла, подключаемого в сборку исполняемого файла данного примера.
PRODUCT_VER := 1.0.0 PRODUCT_NAMES_H := product_names.h VENDOR_NAME := Michael M. Builov PRODUCT_NAME := Sample app VENDOR_COPYRIGHT := Copyright (C) 2015-2017 Michael M. Builov, https://github.com/mbuilov/clean-build
PRODUCT_VER
: версия продукта, clean-build определяет её как0.0.1
. Однако переменные, определённые в Makefile'ах до включения определений clean-build переопределяют значения clean-build. Так какproject.mk
включается первым в любом Makefile проекта, то итоговым значениемPRODUCT_VER
будет1.0.0
.PRODUCT_NAMES_H
, -
Определяем переменную, используемую в Makefile'ах для ссылки на clean-build.
MTOP := $(abspath $(TOP)/../..)
Замечание: поскольку директория данного тестового проекта расположена внутри clean-build, переменную
MTOP
можно определить автоматически, относительно$(TOP)
.
Для работы clean-build должна быть определена переменная TOP
- абсолютный путь к корневой директории проекта.
Переменная TOP
может быть определена автоматически, как показано выше на примере файла top.mk
, расположенного в корневой директории проекта.
Также, в корневой директории проекта обычно располагаются:
- корневой Makefile, содержащий список модулей проекта
- опциональная конфигурационная директория make.
Предполагается, что конфигурационная директория make содержит файл project.mk, включаемый в систему сборки непосредственно перед определением стандартных макросов clean-build.
Система сборки clean-build позволяет переопределить в project.mk практически все стандартные макросы и шаблоны сборки.
Ещё директория make может содержать настройки для подключения внешних модулей - через поддиректории use.
Пример структуры проекта (полный пример см. в clean-build/examples/hello):
/opt/hello/
/opt/hello/hello.mk
/opt/hello/Makefile
/opt/hello/make
/opt/hello/make/project.mk
/opt/hello/make/SOLARIS/use/socket.mk
/opt/hello/make/LINUX/use/pthread.mk
/opt/hello/make/WINXX/use/iphlpapi.mk
/opt/hello/hello.c
Замечания:
- путь к project.mk можно задать в переменной
PROJECT
(по умолчаниюPROJECT=$(TOP)/make/project.mk
) - путь к поддиректории use задаются через переменную
PROJECT_USE_DIR
(по умолчаниюPROJECT_USE_DIR=$(TOP)/make/$(OS)/use
)
- Сборка статической библиотеки
- Создание сборочной утилиты
- Генерация произвольных файлов
- Генерация нескольких файлов одной командой
- Объединение Makefile'ов в группу
- Собираем что-то уникальное, например выполняем тесты
include $(MTOP)/c.mk
LIB := my_lib
SRC := my_source1.c my_source2.c
INCLUDE := my_include1 my_include2
$(DEFINE_TARGETS)
Первой строкой указывается включение файла clean-build $(MTOP)/c.mk
- добавление шаблонов сборки целей на языках C, C++.
Далее указываются параметры шаблона:
-
LIB
- выбирает шаблон сборки статической библиотеки и задаёт её имя (расширение файла будет добавлено автоматически) -
SRC
- список файлов исходных текстов библиотеки -
INCLUDE
- список дополнительных директорий для поиска #include-файлов -
$(DEFINE_TARGETS)
- применение выбранных шаблонов сборки
Сборочная улитита может быть запущена во время сборки, например для генерации исходных текстов.
В случае кросс-компиляции, архитектура сборочной улититы всегда соответствует архитектуре сборочной машины и может отличаться от архитектуры собираемых исполняемых файлов.
TOOL_MODE := 1
include $(MTOP)/c.mk
EXE := my_tool
SRC := my_tool_source.c
$(DEFINE_TARGETS)
Выставление переменной TOOL_MODE
в непустое значение (которое будет очищено в $(DEFINE_TARGETS)
) настраивает шаблоны сборки на создание утилит сборки.
Тем самым, следующее включение $(MTOP)/c.mk
переопределит архитектуру и пути системы сборки для создаваемых файлов на архитектуру и пути для утилит сборки.
Параметры шаблона:
-
EXE
- выбирает шаблон сборки исполняемого файла и задаёт его имя (расширение файла будет добавлено автоматически) -
SRC
- список файлов исходных текстов утилиты -
$(DEFINE_TARGETS)
- применение выбранных шаблонов сборки
Для генерации произвольных файлов нужно определить правила их генерации и добавить генерируемые файлы в сборку - через вызов макроса clean-build ADD_GENERATED
.
include $(MTOP)/defs.mk
MY_GENERATED := $(GEN_DIR)/my_dir/my_file.txt
$(call ADD_GENERATED,$(MY_GENERATED))
$(MY_GENERATED): MY_OPTIONS := my_options
$(MY_GENERATED): $(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt
$(call ospath,$<) $(MY_OPTIONS) -o $(call ospath,$(word 2,$^))
$(DEFINE_TARGETS)
Разберём пример построчно.
-
Включение
$(MTOP)/defs.mk
добавляет описание стандартных функций и определений clean-build.
Вообще, включение любого шаблона clean-build (например упоминаемый ранее
$(MTOP)/c.mk
) также включает и$(MTOP)/defs.mk
. -
Строка
MY_GENERATED := $(GEN_DIR)/my_dir/my_file.txt
задаёт имя генерируемого файла.
Имя переменной
MY_GENERATED
выбрано произвольно, однако возможны коллизии с предопределёнными именами clean-build.Найти коллизии в именах переменных можно запустив сборку в режиме check makefiles - определением переменной
C
в отличное от нуля значение:make C=1
.GEN_DIR
- путь к директории для генерируемых в процессе сборки файлов.Система сбоки позволяет создавать файлы только в директориях
$(GEN_DIR)
(исходные тексты),$(LIB_DIR)
(библиотеки),$(BIN_DIR)
(исполняемые файлы) или$(OBJ_DIR)
(объектные файлы). -
Вызов
$(call ADD_GENERATED,$(MY_GENERATED))
добавляет генерируемые файлы (т.е.
$(GEN_DIR)/my_dir/my_file.txt
) в сборку:- директория
$(GEN_DIR)/my_dir
будет создана автоматически перед вызовом правила генерации файлов - при выполнении
make clean
сгенерённые файлы будут удалены (за исключением директорий)
- директория
-
строка
$(MY_GENERATED): MY_OPTIONS := my_options
определяет target-specific (связанную с целью) переменную
MY_OPTIONS
со значениемmy_options
.В системе сборки clean-build важно использовать по-возможности тоько target-specific переменные - из-за нерекурсивности системы сборки.
После того, как очередной Makefile разобран и определены правила сборки целей, может быть включён и разобран следующий Makefile, который может переопределить переменные из первого Makefile.
Во время выполнения правил первого Makefile глобальные переменные могут иметь уже другие значения, нежели во время определения правил.
Поэтому переменные нужно привязывать к цели сборки - имена целей сборки всегда уникальны, что контролируется самим Gnu Make.
-
следующие две строки определяют правило сборки файла
$(MY_GENERATED)
$(MY_GENERATED): $(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt $(call ospath,$<) $(MY_OPTIONS) -o $(call ospath,$(word 2,$^))
-
Первая строка задаёт зависимости
$(MY_GENERATED)
от утилиты сборкиmy_tool
и файлаmy_source.txt
.Раскрытие параметризованного макроса
GET_TOOL
через$(call GET_TOOL,my_tool)
даёт путь к утилите сборкиmy_tool
(пример созданияmy_tool
приведён выше).Указание
$(VPREFIX)my_source.txt
задаёт зависимость генерируемого файла отmy_source.txt
, расположенного в той же директории, что и данный Makefile.В переменной
$(VPREFIX)
содержится относительный путь от директории, в которой запущен make к директории, в которой расположен целевой Makefile.Замечание:
- В приведённых выше примерах сборки статической библиотеки и утилиты сборки переменная
$(VPREFIX)
не указывалась перед именами файлов исходных текстов.
$(VPREFIX)
добавляется автоматически шаблонами сборки к путям файлов исходных текстов, если пути не абсолютные (т.е. не указаны относительно корня проекта$(TOP)
).Здесь же задаётся произвольное правило сборки - все зависимости нужно указывать явно.
- В приведённых выше примерах сборки статической библиотеки и утилиты сборки переменная
-
Вторая строка (начинающаяся с символа табуляции) определяет метод генерации файла.
Здесь:
$(call ospath,$<) $(MY_OPTIONS) -o $(call ospath,$(word 2,$^))
$<
- автоматическая переменная - имя первой зависимости цели, в данном случае это путь, возвращаемый$(call GET_TOOL,my_tool)
.Вызовом
$(call ospath,$<)
путь к утилитеmy_tool
преобразуется в путь к фалу, характерный для операционной системы сборочной машины (например преобразуетсяC:/a/b
->C:\a\b
).$(MY_OPTIONS)
- target-specific переменная - раскрывается вmy_options
.$^
- автоматическая переменная - список всех зависимостей цели в порядке их перечисления.$(word 2,$^)
- имя второй зависимости цели, в данном случае это$(VPREFIX)my_source.txt
.Замечание:
-
VPREFIX
не была объявлена как target-specific переменная, поэтому нельзя использовать выражение$(VPREFIX)my_source.txt
в теле правила, т.к.VPREFIX
может быть изменена в следующих Makefile'ах, включенных после задания данного правила построения цели.
$(call ospath,$(word 2,$^))
- преобразование пути кmy_source.txt
в путь, характерный для операционной системы сборочной машины. -
-
-
$(DEFINE_TARGETS)
- применение выбранных шаблонов сборки.В данном случае, т.к. никаких предопределённых шаблонов сборки указано не было, то
$(DEFINE_TARGETS)
не делает ничего, кроме включения завершающего файла clean-build$(MTOP)/all.mk
, если текущий Makefile был обработан последним.Завершающий
$(MTOP)/all.mk
содержит определение цели по-умолчанию all, для выполнения которой нужно выполнить все заданные цели.Замечание:
- Вместе с определением правила генерации произвольного файла можно было выбрать и шаблон сборки, скажем библиотеки (включением
$(MTOP)/c.mk
вместо$(MTOP)/defs.mk
и заданием переменнойLIB
), тогда вызов$(DEFINE_TARGETS)
также приведёт к сборке библиотеки.
- Вместе с определением правила генерации произвольного файла можно было выбрать и шаблон сборки, скажем библиотеки (включением
Правило генерации нескольких файлов одной командой в Gnu Make можно задать с помошью определения шаблонного правила (pattern-rule).
Имена генерируемыех файлов, при этом, должны содержать некоторую общую часть - шаблон - например общий путь к родительской директории.
Рассмотрим пример генерации файлов $(GEN_DIR)/t1
, $(GEN_DIR)/t2
и $(GEN_DIR)/t3
, зависящих от файла prereq.txt
с помощью команды $(my_gen_cmd)
:
include $(MTOP)/defs.mk
define my_gen_cmd
$(if $(VERBOSE),,@)$(call ECHO,1) > $(call ospath,$(word 1,$1))
$(if $(VERBOSE),,@)$(call ECHO,2) > $(call ospath,$(word 2,$1))
$(if $(VERBOSE),,@)$(call ECHO,3) > $(call ospath,$(word 3,$1))
endef
MY_GENERATED := $(GEN_DIR)/t1 $(GEN_DIR)/t2 $(GEN_DIR)/t3
$(call ADD_GENERATED,$(MY_GENERATED))
$(MY_GENERATED): GENERATED := $(MY_GENERATED)
%/t1 %/t2 %/t3: $(VPREFIX)prereq.txt
$(call SUP,MGEN,$(GENERATED))$(call my_gen_cmd,$(GENERATED))
$(DEFINE_TARGETS)
В общем случае, при таком подходе есть несколько неудобных моментов:
- во-первых, нужно выделять шаблон из имён файлов (правда, если использовать общий путь в качестве шаблона, то это - не проблема)
- во-вторых, имена создаваемых файлов, для их использования в теле правила, нужно запоминать в target-specific-переменной, т.к. автоматическая переменная
$@
будет содержать имя только первого создаваемого файла (в примере -$(GEN_DIR)/t1
) - и в-третьих, создаётся шаблонное правило, которое будет проверяться для каждого файла, для которого явно не определено правило создания, что негативно повлияет на скорость сборки.
Для решения этих проблем и упрощения генерации нескольких файлов одной командой, в clean-build определён макрос MULTI_TARGET
.
include $(MTOP)/defs.mk
define my_gen_cmd
$(if $(VERBOSE),,@)$(call ECHO,1) > $(call ospath,$(word 1,$1))
$(if $(VERBOSE),,@)$(call ECHO,2) > $(call ospath,$(word 2,$1))
$(if $(VERBOSE),,@)$(call ECHO,3) > $(call ospath,$(word 3,$1))
endef
MY_GENERATED := $(GEN_DIR)/t1 $(GEN_DIR)/t2 $(GEN_DIR)/t3
$(call MULTI_TARGET,$(MY_GENERATED),prereq.txt,$$(call my_gen_cmd,$(MY_GENERATED)))
$(DEFINE_TARGETS)
Макрос MULTI_TARGET
, определённый в подключаемом $(MTOP)/defs.mk
, принимает 3 аргумента:
- список генерируемых файлов -
$(MY_GENERATED)
- список зависимостей генерируемых файлов -
prereq.txt
- правило генерации файлов -
$$(call my_gen_cmd,$(MY_GENERATED))
Перед вызовом макроса MULTI_TARGET
, вычисляется каждый из передаваемых ему аргументов.
Так как правило генерации файлов, передаваемое 3-им аргументом, будет раскрыто дважды - первый раз перед вызовом MULTI_TARGET
, второй - во время выполнения правила, то вызов $$(call my_gen_cmd,$(MY_GENERATED))
экранирован двойным $$
.
Однако список файлов, передаваемый в my_gen_cmd
раскрывается сразу - в момент вызова MULTI_TARGET
Значение переменной VPREFIX
необходимо использовать сразу, до включения следующего Makefile, в котором VPREFIX
может быть изменена.
Или можно сохранить $(VPREFIX)
в target-specific переменной, чтобы использовать это значение во время выполнения правила.
Переменная MY_OPTIONS
не объявлена как target-specific, в отличие от предыдущего примера, т.к. используется сразу, в момент определения правила сборки, а не в момент выполнения этого правила.
-
Объединение Makefile'ов в группу
Пример группового Makefile:
MDEPS := my_gen.mk TO_MAKE := tool1.mk lib1/my_lib.mk $(TOP)/dir1/dir2/my_exe_dir include $(MTOP)/parallel.mk
Группа Makefile'ов указывается в переменной
TO_MAKE
, значение которой интерпретируется в подключаемом$(MTOP)/parallel.mk
.Makefile'ы указываются как имена файлов с расширением .mk или директорий, путь к которым может быть либо относительным от текущего Makefile, либо абсолютным - от корня проекта в переменной
$(TOP)
. Если элемент списка$(TO_MAKE)
не заканчивается на .mk, то предполагается, что это директория, в которой ищется файл с именем Makefile.В переменной
MDEPS
можно указать список Makefile'ов, от которых зависят цели текущего Makefile (или группы Makefile'ов, как в данном примере). Эти зависимости будут наследоваться всеми Makefile'ами, подключаемыми групповым Makefile.Правила перечисления Makefile'ов в переменной
MDEPS
те же, что и для переменнойTO_MAKE
.Цели, указанные в Makefile'ах
tool1.mk
,lib1/my_lib.mk
и$(TOP)/dir1/dir2/my_exe_dir/Makefile
будут выполнены только после того, как будут выполнены цели Makefile'ов из$(MDEPS)
.Если Makefile из
$(MDEPS)
не был подключён к сборке в процессе определения целей, то он игнорируется. Таким образом, возможно выполнение (пере-)сборки модуля в его поддиректории, без необходимости пересобирать внешние зависимости, как в следующем примере:top_dir/Makefile:
TO_MAKE := gen module include $(MTOP)/parallel.mk
top_dir/module/Makefile:
MDEPS := ../gen .... $(DEFINE_TARGETS)
Вызов
make clean && make
в директории top_dir/module пересобирёт только цели из этой директории, не трогая цели в top_dir/gen. -
Собираем что-то уникальное, например выполняем тесты
Пример:
include $(MTOP)/defs.mk MDEPS := build_tests.mk TESTS := rb_test c_base64_test $(call ADD_GENERATED,$(addprefix $(GEN_DIR)/tests/,$(addsuffix .tsmt,$(TESTS)))) define TEST_RULE $(newline) $(GEN_DIR)/tests/$t.tsmt: $(BIN_DIR)/$t$(EXE_SUFFIX) $$(call SUP,TEST,$$<)$$< > $(GEN_DIR)/tests/$t.out $(if $(VERBOSE),,@)$$(call TOUCH,$$@) endef $(eval $(foreach t,$(TESTS),$(TEST_RULE))) $(DEFINE_TARGETS)
Здесь:
-
MDEPS := build_tests.mk
Задаёт что цели, указанные вbuild_tests.mk
(например сборка тестируемых исполняемых файлов) должны быть выполнены до целей текущего Makefile. -
TESTS := rb_test c_base64_test
Список тестируемых исполняемых файлов. -
$(call ADD_GENERATED,$(addprefix $(GEN_DIR)/tests/,$(addsuffix .tsmt,$(TESTS))))
В результате запуска тестов будут сгенерены файлыrb_test.tsmt
иc_base64_test.tsmt
в директории$(GEN_DIR)/tests
. -
define TEST_RULE
Начало определения шаблона генерации правила запуска теста. -
$(newline)
Тело определяемого шаблонаTEST_RULE
начинается со строки, следующей заdefine TEST_RULE
. Пусть первым символом шаблона будет перевод строки (переменнаяnewline
определенав $(MTOP)/defs.mk
). Это позволит начинать определение очередного правила с новой строки - при раскрытии шаблона в цикле foreach. -
шаблонное правило c параметром шаблона
$t
$(GEN_DIR)/tests/$t.tsmt: $(BIN_DIR)/$t$(EXE_SUFFIX) $$(call SUP,TEST,$$<)$$< > $(GEN_DIR)/tests/$t.out $(if $(VERBOSE),,@)$$(call TOUCH,$$@)
определяет, что результат теста
$t.tsmt
в директории$(GEN_DIR)/tests
будет зависеть от даты создания тестируемого исполняемого файла$t$(EXE_SUFFIX)
в директории$(BIN_DIR)
.Для выполнения теста сначала будет вызван исполняемый файл
$(BIN_DIR)/$t$(EXE_SUFFIX)
(указываемый через автоматическую перемсенную$$<
), а stdout этого вызова будет перенаправлен с помощью>
в файл$(GEN_DIR)/tests/$t.out
. Вызов$$(call SUP,TEST,$$<)
служит для информационной печати на экране выполняемой операции или команды (если make вызван с параметром V=1). Следующей строка -- вызов$$(call TOUCH,$$@)
-- вторая команда правила генерации файла$(GEN_DIR)/tests/$t.tsmt
(указываемого через автоматическую переменную$$@
). МакросTOUCH
определён в$(MTOP)/defs.mk
через подключение файла определений, специфичного для текущей сборочной платформы. Конструкция$(if $(VERBOSE),,@)
-- даёт пустое значение в verbose режиме (если make вызван с параметром V=1) и@
(директива make -- скрыть выполняемую команду) в обычном режиме.Соответственно, в обычном режиме, при выполнении правила шаблона make напечатает что-то вроде:
TEST /home/user/procject/bin/LINUX-x86_64-x86_64-DEBUG/rb_test
А в verbose-режиме:
/home/user/proj/bin/LINUX-x86_64-x86_64-DEBUG/rb_test > /home/user/proj/gen/LINUX-x86_64-x86_64-DEBUG/rb_test.out touch /home/user/proj/gen/LINUX-x86_64-x86_64-DEBUG/rb_test.tsmt
Замечания:
- команды правила должны начинаться с символа табуляции
- тест правил будет интерпретирован дважды, один раз при раскрытии шаблона, второй -- в момент выполнения правил. Поэтому раскрытие некоторых макросов в правилах отложено до второго раза - путём экранирования символа
$
двойным$$
.
-
endef
Конец шаблонаTEST_RULE
.Замечание: тело шаблона не включает перевод строки перед
endef
. -
$(eval $(foreach t,$(TESTS),$(TEST_RULE)))
Для каждого$t
из списка$(TESTS)
раскрывается шаблонTEST_RULE
, затем полученный список правил интерпретируется функциейeval
как если бы эти правила были заданы в тексте Makefile.Небольшое замечание: функция
foreach
разделяет результаты раскрытия шаблонаTEST_RULE
пробелами, поэтому в конце последней строки раскрытого шаблона (после$$(call TOUCH,$$@)
) будет добавлен пробел (добавление которого не играет никакой роли). А из-за того, что первым символом шаблона является$(newline)
, функцииeval
для интерпретации на вход поступят правила, начинающиеся с новой строки. -
$(DEFINE_TARGETS)
-- раскрывает макрос исполнения заданных шаблонов сборки. В данном случае, т.к. никаких предопределённых шаблонов сборки указано не было, то$(DEFINE_TARGETS)
не делает ничего, кроме включения завершающего$(MTOP)/all.mk
, если текущий Makefile был обработан последним.
-
е и разбор всех Makefile'ов проекта, затем построение единого дерева зависимостей целей, затем (параллельное) выполнение целей. Единое дерево зависимостей позволяет максимально распараллелить выполнение целей проекта, утилизируя все доступные ресурсы сборочной машины.
-
Использование только нативных утилит сборочной платформы
Система сборки минималистична, полагается только на возможности Gnu Make и, опционально, sed (например Gnu Sed). Данные утилиты доступны в нативном виде для всех поддерживаемых платформ: Windows, Linux, Solaris.
- нерекурсивное выполнение сборки
- массивное распараллеливание выполнения целей, практически не ограниченное масштабирование по количеству процессоров
- модульный, расширяемый дизайн
- платформо-независимые целевые сценарии сборки
- многоплатформенность, использование только нативных утилит сборочной платформы (для сборки на WINDOWS не нужен слой эмуляции UNIX)
- поддержка указания зависимости выполнения сценариев сборки друг от друга
- поддержка кросс-компиляции
- встроенная поддержка сборки целей из исходных текстов на языках: C, C++, Java, Scala, Wix
- поддержка предкомпилированных заголовочных файлов для языков C/C++
- поддержка генерации в процессе сборки описаний зависимостей исходных текстов от заголовочных файлов (для C/C++)
- поддержка групповой компиляции нескольких файлов за один вызов компилятора (на WINDOWS для C/C++)
- встроенные средства для отладки, проверки и трассировки сценариев сборки
- предопределённые шаблоны для сборки динамических и статических библиотек, исполняемых фалов, модулей ядра и архивов java
- поддержка произвольных правил генерации нескольких целей за один вызов заданной команды
- возможность генерации простого batch-файла или shell-скрипта для сборки с нуля без использования Gnu Make: make V=1 > build.bat
- красивый вывод информации о процессе сборки
Достаточно скопировать clean-build в директорию без пробелов в пути, например в C:\tools
(для Windows) или /usr/home/user
(для Unix-like ОС).
Сборка осуществляется обычным вызовом интерпретатора Gnu Make, примеры:
-
make distclean
-
make -j4
-
make -f dir/Makefile check
илиmake -C dir check
-
вызов из bash (например под cygwin) с заданием параметров сборки:
$ make MTOP=/cygdrive/c/User/clean-build OS=LINUX CPU=x86_64 COMPILER_TYPE=GCC6 NO_STACK_PROTECTOR=1 NO_SANITIZE=1 NO_COVERAGE=1 DLL_PREFIX= DLL_SUFFIX=.dll MEMSTACK_INCLUDE=/cygdrive/c/User/memstack MEMSTACK_LIBDIR='/cygdrive/c/User/memstack/lib/$(TARGET_TRIPLET)' CMN_HEADERS_INCLUDE=/cygdrive/c/User/cmn_headers GTPARSER_INCLUDE=/cygdrive/c/User/gtparser GTPARSER_LIBDIR='/cygdrive/c/User/gtparser/lib/$(TARGET_TRIPLET)'
-
вызов из командного итерпретатора cmd.exe с заданием параметров сборки:
C:\User\bitbridge>C:\tools\gnumake-4.2.1-x64.exe MTOP=c:\User\clean-build SED=c:\tools\sed-4.4-x64.exe OS=WINXX CPU=x86 OSVARIANT=WIN7 VS="d:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017" WDK="c:\Program Files (x86)\Windows Kits\10" WDK_TARGET="10.0.14393.0" MEMSTACK_INCLUDE=C:\User\memstack MEMSTACK_LIBDIR=C:\User\memstack\lib\$(TARGET_TRIPLET) MEMSTACK_DLLDIR=C:\User\memstack\bin\$(TARGET_TRIPLET) CMN_HEADERS_INCLUDE=C:\User\cmn_headers GTPARSER_INCLUDE=C:\User\gtparser GTPARSER_LIBDIR=C:\User\gtparser\lib\$(TARGET_TRIPLET)
- Сборка статической библиотеки
- Создание сборочной утилиты
- Генерация произвольных файлов
- Генерация нескольких файлов из одного исходного файла
- Объединение Makefileов в группу
- Собираем что-то уникальное, например выполняем тесты
include $(MTOP)/c.mk
LIB := my_lib
SRC := my_source1.c my_source2.c
INCLUDE := my_include1 my_include2
$(DEFINE_TARGETS)
Первой строкой указывается включение файла $(MTOP)/c.mk
-- добавление шаблонов сборки целей на языках C, C++.
Далее указываются параметры шаблона:
-
LIB
-- выбирает шаблон сборки статической библиотеки и задаёт её имя (расширение будет добавлено автоматически) -
SRC
-- список файлов исходных текстов библиотеки -
INCLUDE
-- список дополнительных директорий для поиска #include-файлов -
$(DEFINE_TARGETS)
-- раскрывает макрос исполнения выбранных шаблонов сборки
Сборочная улитита может быть запущена во время сборки, например для генерации исходных текстов.
В случае кросс-компиляции, архитектура сборочной улититы соответствует архитектуре сборочной машины и может отличаться от архитектуры собираемых исполняемых файлов.
TOOL_MODE := 1
include $(MTOP)/c.mk
EXE := my_tool
SRC := my_tool_source.c
$(DEFINE_TARGETS)
Выставление переменной TOOL_MODE
в непустое значение (которое будет очищено в $(DEFINE_TARGETS)
) настраивает шаблоны сборки на создание утилит сборки.
Тем самым, следующее включение $(MTOP)/c.mk
переопределит пути системы сборки для создаваемых файлов на пути для утилит сборки.
Параметры шаблона:
-
EXE
-- выбирает шаблон сборки исполняемого файла и задаёт его имя (расширение будет добавлено автоматически) -
SRC
-- список файлов исходных текстов исполняемого файла -
$(DEFINE_TARGETS)
-- раскрывает макрос исполнения заданных шаблонов сборки.
Для генерации произвольных файлов нужно определить правила их генерации и добавить генерируемые файлы в сборку через вызов макроса ADD_GENERATED
.
include $(MTOP)/defs.mk
MY_GENERATED := $(GEN_DIR)/my_dir/my_file.txt
$(call ADD_GENERATED,$(MY_GENERATED))
$(MY_GENERATED): MY_OPTIONS := my_options
$(MY_GENERATED): $(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt
$(call ospath,$<) $(MY_OPTIONS) -o $(call ospath,$(word 2,$^))
$(DEFINE_TARGETS)
Разберём пример построчно.
-
Включение
$(MTOP)/defs.mk
добавляет описание стандартных функций и определений.
Вообще, включение любого набора шаблонов clean-build (например упоминаемый ранее
$(MTOP)/c.mk
) также включает и$(MTOP)/defs.mk
. -
Строка
MY_GENERATED := $(GEN_DIR)/my_dir/my_file.txt
задаёт имя генерируемого файла.
Имя переменной
MY_GENERATED
выбрано произвольно, однако возможны коллизии с предопределёнными именами clean-build.Найти коллизии в именах переменных поможет запуск
make C=1
(режим check makefiles -- проверка скриптов сборки одновременно со сборкой).GEN_DIR
-- путь к директории для генерируемых в процессе сборки файлов.Система сбоки позволяет генерить файлы только в директориях
$(GEN_DIR)
,$(LIB_DIR)
,$(BIN_DIR)
или$(OBJ_DIR)
. -
Вызов
$(call ADD_GENERATED,$(MY_GENERATED))
добавляет генерируемые файлы в сборку:
- директория
$(GET_DIR)/my_dir
будет создана автоматически перед вызовом правила генерации файлов - при выполнении
make clean
сгенерённые файлы будут удалены
- директория
-
строка
$(MY_GENERATED): MY_OPTIONS := my_options
определяет target-specific переменную
MY_OPTIONS
со значениемmy_options
.В системе сборки clean-build важно использовать по-возможности тоько target-specific переменные -- из-за нерекурсивности системы сборки.
После того, как очередной Makefile разобран и определены правила сборки целей, может быть включён и разобран следующий Makefile, который может переопределить переменные из первого Makefile.
Поэтому переменные нужно привязывать к цели сборки -- цели сборки всегда уникальны, что контролируется самим Gnu Make.
-
следующие две строки определяют правило сборки файла
$(MY_GENERATED)
$(MY_GENERATED): $(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt $(call ospath,$<) $(MY_OPTIONS) -o $(call ospath,$(word 2,$^))
- Первая строка задаёт зависимости
$(MY_GENERATED)
от утилиты сборки и файлаmy_source.txt
.
Раскрытие параметризованного макроса
GET_TOOL
через$(call GET_TOOL,my_tool)
даёт путь к утилите сборкиmy_tool
(пример созданияmy_tool
приведён выше).Указание
$(VPREFIX)my_source.txt
задаёт зависимость генерируемого файла отmy_source.txt
, расположенного в той же директории, что и Makefile.В переменной
$(VPREFIX)
содержится относительный путь от директории, в которой запущен make к директории, в которой расположен целевой Makefile.Замечание.
В приведённых выше примерах сборки статической библиотеки и утилиты переменная
$(VPREFIX)
не указывалась перед именами исходных файлов.$(VPREFIX)
добавляется автоматически шаблонами сборки, если путь не абсолютный (т.е. не указан относительно корня проекта$(TOP)
).Здесь же задаётся произвольное правило сборки -- все зависимости нужно указывать явно.
- Вторая строка (начинающаяся с символа табуляции) определят метод генерации файла.
$<
-- автоматическая переменная -- имя первой зависимости цели, в данном случае это путь, возвращаемый$(call GET_TOOL,my_tool)
.Вызовом
$(call ospath,$<)
путь к утилитеmy_tool
преобразуется в путь к фалу, характерный для операционной системы сборочной машины (например преобразуетсяC:/a/b
->C:\a\b
).$(MY_OPTIONS)
-- target-specific переменная -- раскрывается вmy_options
.$^
-- автоматическая переменная -- список всех зависимостей цели в порядке их перечисления.$(word 2,$^)
-- имя второй зависимости цели, в данном случае это$(VPREFIX)my_source.txt
.Замечание.
VPREFIX
не была объявлена как target-specific переменная, поэтому нельзя использовать выражение$(VPREFIX)my_source.txt
в теле правила --VPREFIX
может быть изменена в следующих Makefile'ах, включенных после задания данного правила построения цели.$(call ospath,$(word 2,$^))
-- преобразование пути кmy_source.txt
в путь, характерный для операционной системы сборочной машины. - Первая строка задаёт зависимости
-
$(DEFINE_TARGETS)
-- раскрывает макрос исполнения заданных шаблонов сборки.В данном случае, т.к. никаких предопределённых шаблонов сборки указано не было, то
$(DEFINE_TARGETS)
не делает ничего, кроме включения завершающего$(MTOP)/all.mk
, если текущий Makefile был обработан последним.Завершающий
$(MTOP)/all.mk
содержит определение цели по-умолчанию all, для выполнения которой нужно выполнить все определённые правила.Замечание.
Вместе с заданием правила генерации произвольного файла можно было задать шаблон сборки, скажем библиотеки (включением
$(MTOP)/c.mk
вместо$(MTOP)/defs.mk
), тогда вызов$(DEFINE_TARGETS)
также приведёт к сборке библиотеки. -
Генерация нескольких файлов из одного исходного файла
В Gnu Make нет специальных средств для решения проблемы с генерацией одним вызовом нескольких файлов -- правило построения цели в Gnu Make предполагает, что целевой файл всегда один. А иногда нужно, чтобы правило генерации нескольких целей выполнялось единожды при необходимости обновления любой из целей.
Для решения этой проблемы, в clean-build определён макрос
MULTI_TARGET
.include $(MTOP)/defs.mk MY_OPTIONS := my_options MY_GENERATED := $(addprefix $(GEN_DIR)/my_dir/,my_file1.txt my_file2.txt) $(call MULTI_TARGET,$(MY_GENERATED),$(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt,$$(call \ ospath,$$<) $(MY_OPTIONS) -o $(VPREFIX)my_source.txt) $(DEFINE_TARGETS)
Макрос
MULTI_TARGET
, определённый в$(MTOP)/defs.mk
, принимает 3 аргумента:- список генерируемых файлов --
$(MY_GENERATED)
- список зависимостей генерируемых файлов --
$(call GET_TOOL,my_tool) $(VPREFIX)my_source.txt
- правило генерации файлов --
$$(call ospath,$$<) $(MY_OPTIONS) -o $(VPREFIX)my_source.txt
Перед вызовом макроса
MULTI_TARGET
, вычисляется каждый из передаваемых ему аргументов. Так как правило генерации файлов, передаваемое 3-им аргументом, будет раскрыто дважды -- первый раз перед вызовомMULTI_TARGET
, второй -- во время выполнения правила, то обращение к$(ospath)
и переменной$<
экранировано двойным$$
.Значение переменной
VPREFIX
необходимо использовать сразу, до включения следующего Makefile, в которомVPREFIX
может быть изменена. Или можно сохранить$(VPREFIX)
в target-specific переменной, чтобы использовать это значение во время выполнения правила.Переменная
MY_OPTIONS
не объявлена как target-specific, в отличие от предыдущего примера, т.к. используется сразу, в момент определения правила сборки, а не в момент выполнения этого правила. - список генерируемых файлов --
-
Объединение Makefile'ов в группу
Пример группового Makefile:
MDEPS := my_gen.mk TO_MAKE := tool1.mk lib1/my_lib.mk $(TOP)/dir1/dir2/my_exe_dir include $(MTOP)/parallel.mk
Группа Makefile'ов указывается в переменной
TO_MAKE
, значение которой интерпретируется в подключаемом$(MTOP)/parallel.mk
.Makefile'ы указываются как имена файлов с расширением .mk или директорий, путь к которым может быть либо относительным от текущего Makefile, либо абсолютным - от корня проекта в переменной
$(TOP)
. Если элемент списка$(TO_MAKE)
не заканчивается на .mk, то предполагается, что это директория, в которой ищется файл с именем Makefile.В переменной
MDEPS
можно указать список Makefile'ов, от которых зависят цели текущего Makefile (или группы Makefile'ов, как в данном примере). Эти зависимости будут наследоваться всеми Makefile'ами, подключаемыми групповым Makefile.Правила перечисления Makefile'ов в переменной
MDEPS
те же, что и для переменнойTO_MAKE
.Цели, указанные в Makefile'ах
tool1.mk
,lib1/my_lib.mk
и$(TOP)/dir1/dir2/my_exe_dir/Makefile
будут выполнены только после того, как будут выполнены цели Makefile'ов из$(MDEPS)
.Если Makefile из
$(MDEPS)
не был подключён к сборке в процессе определения целей, то он игнорируется. Таким образом, возможно выполнение (пере-)сборки модуля в его поддиректории, без необходимости пересобирать внешние зависимости, как в следующем примере:top_dir/Makefile:
TO_MAKE := gen module include $(MTOP)/parallel.mk
top_dir/module/Makefile:
MDEPS := ../gen .... $(DEFINE_TARGETS)
Вызов
make clean && make
в директории top_dir/module пересобирёт только цели из этой директории, не трогая цели в top_dir/gen. -
Собираем что-то уникальное, например выполняем тесты
Пример:
include $(MTOP)/defs.mk MDEPS := build_tests.mk TESTS := rb_test c_base64_test $(call ADD_GENERATED,$(addprefix $(GEN_DIR)/tests/,$(addsuffix .tsmt,$(TESTS)))) define TEST_RULE $(newline) $(GEN_DIR)/tests/$t.tsmt: $(BIN_DIR)/$t$(EXE_SUFFIX) $$(call SUP,TEST,$$<)$$< > $(GEN_DIR)/tests/$t.out $(if $(VERBOSE),,@)$$(call TOUCH,$$@) endef $(eval $(foreach t,$(TESTS),$(TEST_RULE))) $(DEFINE_TARGETS)
Здесь:
-
MDEPS := build_tests.mk
Задаёт что цели, указанные вbuild_tests.mk
(например сборка тестируемых исполняемых файлов) должны быть выполнены до целей текущего Makefile. -
TESTS := rb_test c_base64_test
Список тестируемых исполняемых файлов. -
$(call ADD_GENERATED,$(addprefix $(GEN_DIR)/tests/,$(addsuffix .tsmt,$(TESTS))))
В результате запуска тестов будут сгенерены файлыrb_test.tsmt
иc_base64_test.tsmt
в директории$(GEN_DIR)/tests
. -
define TEST_RULE
Начало определения шаблона генерации правила запуска теста. -
$(newline)
Тело определяемого шаблонаTEST_RULE
начинается со строки, следующей заdefine TEST_RULE
. Пусть первым символом шаблона будет перевод строки (переменнаяnewline
определенав $(MTOP)/defs.mk
). Это позволит начинать определение очередного правила с новой строки - при раскрытии шаблона в цикле foreach. -
шаблонное правило c параметром шаблона
$t
$(GEN_DIR)/tests/$t.tsmt: $(BIN_DIR)/$t$(EXE_SUFFIX) $$(call SUP,TEST,$$<)$$< > $(GEN_DIR)/tests/$t.out $(if $(VERBOSE),,@)$$(call TOUCH,$$@)
определяет, что результат теста
$t.tsmt
в директории$(GEN_DIR)/tests
будет зависеть от даты создания тестируемого исполняемого файла$t$(EXE_SUFFIX)
в директории$(BIN_DIR)
.Для выполнения теста сначала будет вызван исполняемый файл
$(BIN_DIR)/$t$(EXE_SUFFIX)
(указываемый через автоматическую перемсенную$$<
), а stdout этого вызова будет перенаправлен с помощью>
в файл$(GEN_DIR)/tests/$t.out
. Вызов$$(call SUP,TEST,$$<)
служит для информационной печати на экране выполняемой операции или команды (если make вызван с параметром V=1). Следующей строка -- вызов$$(call TOUCH,$$@)
-- вторая команда правила генерации файла$(GEN_DIR)/tests/$t.tsmt
(указываемого через автоматическую переменную$$@
). МакросTOUCH
определён в$(MTOP)/defs.mk
через подключение файла определений, специфичного для текущей сборочной платформы. Конструкция$(if $(VERBOSE),,@)
-- даёт пустое значение в verbose режиме (если make вызван с параметром V=1) и@
(директива make -- скрыть выполняемую команду) в обычном режиме.Соответственно, в обычном режиме, при выполнении правила шаблона make напечатает что-то вроде:
TEST /home/user/procject/bin/LINUX-x86_64-x86_64-DEBUG/rb_test
А в verbose-режиме:
/home/user/proj/bin/LINUX-x86_64-x86_64-DEBUG/rb_test > /home/user/proj/gen/LINUX-x86_64-x86_64-DEBUG/rb_test.out touch /home/user/proj/gen/LINUX-x86_64-x86_64-DEBUG/rb_test.tsmt
Замечания:
- команды правила должны начинаться с символа табуляции
- тест правил будет интерпретирован дважды, один раз при раскрытии шаблона, второй -- в момент выполнения правил. Поэтому раскрытие некоторых макросов в правилах отложено до второго раза - путём экранирования символа
$
двойным$$
.
-
endef
Конец шаблонаTEST_RULE
.Замечание: тело шаблона не включает перевод строки перед
endef
. -
$(eval $(foreach t,$(TESTS),$(TEST_RULE)))
Для каждого$t
из списка$(TESTS)
раскрывается шаблонTEST_RULE
, затем полученный список правил интерпретируется функциейeval
как если бы эти правила были заданы в тексте Makefile.Небольшое замечание: функция
foreach
разделяет результаты раскрытия шаблонаTEST_RULE
пробелами, поэтому в конце последней строки раскрытого шаблона (после$$(call TOUCH,$$@)
) будет добавлен пробел (добавление которого не играет никакой роли). А из-за того, что первым символом шаблона является$(newline)
, функцииeval
для интерпретации на вход поступят правила, начинающиеся с новой строки. -
$(DEFINE_TARGETS)
-- раскрывает макрос исполнения заданных шаблонов сборки. В данном случае, т.к. никаких предопределённых шаблонов сборки указано не было, то$(DEFINE_TARGETS)
не делает ничего, кроме включения завершающего$(MTOP)/all.mk
, если текущий Makefile был обработан последним.
-