qmake позволяет добавить свои собственные цели (targets) в создаваемый Makefile.
Кастомные цели (правильнее было бы сказать: правила для целей) добавляются с помощью переменной QMAKE_EXTRA_TARGETS. Каждое значение этой переменной – имя составной переменной, описывающей цель.
QMAKE_EXTRA_TARGETS += customtarget1 customtarget2 # цель customtarget1.target = readme.txt # команда - создание файла в каталоге исходников # без $$PWD файл создасться в $$OUT_PWD customtarget1.commands = @echo custom target > $$system_path($$PWD/readme.txt) # зависимости. можно указывать как имена целей, # так и имена составных описательных переменных; они будут заменены # на имена соответствующих целей customtarget1.depends = customtarget2 # то же самое, что и readme_info customtarget2.target = readme_info customtarget2.commands = @echo generate readme.txt # цель оформляется как как "пустышка" customtarget2.CONFIG = phony
В Makefile добавятся цели где-то в следующем виде:
readme.txt: readme_info @echo custom target > D:\source\test\readme.txt readme_info: FORCE @echo generate readme.txt FORCE:
1 2 3 4 5 6 7 8 9 |
При описании цели можно использовать следующие “атрибуты”:
Если не предпринять специальных действий, то commands будет выведено в Makefile одной строкой, а значит, commands сможет вместить в себя только одну команду. Это ограничение можно обойти двумя способами. Во-первых, можно воспользоваться оператором && оболочки, который поддерживается и cmd.exe, и bash, и позволяет объединить несколько команд в одну. Во-вторых, можно вставить конец строки и необходимый для Makefile символ табуляции с помощью функции escape_expand:
QMAKE_EXTRA_TARGETS += customtarget customtarget.target = readme.txt customtarget.commands = @echo generate readme.txt $$escape_expand(\n\t)@echo custom target > $$PWD/readme.txt # или customtarget.commands = @echo generate readme.txt && @echo custom target > $$PWD/readme.txt
Теперь о recursive. Эта опция имеет значение для режима debug_and_release, который меня мало интересует, поэтому особо не разбирался с этой опцией. Вот то, что я выяснил.
При включенном debug_and_release создаются три Makefile с именами Makefile, Makefile.Debug и Makefile.Release. Кастомные цели будут в этих файлах просто продублированы, что может быть не то, что нужно. Если же указать CONFIG = recursive, то в Makefile вместо простой копии цели будет сгенерирован вызов make для Makefile.Debug и Makefile.Release.
QMAKE_EXTRA_TARGETS += customtarget customtarget.commands = @echo custom target customtarget.CONFIG = recursive
1 2 3 4 5 |
QMAKE_EXTRA_TARGETS += customtarget customtarget.commands = @echo custom target |
debug-customtarget: $(MAKE) -f $(MAKEFILE).Debug customtarget release-customtarget: $(MAKE) -f $(MAKEFILE).Release customtarget customtarget: debug-customtarget release-customtarget @echo custom target
1 2 3 4 5 6 7 8 |
$(MAKE) -f $(MAKEFILE).Debug customtarget $(MAKE) -f $(MAKEFILE).Release customtarget |
Makefile.Debug и Makefile.Release
customtarget: @echo custom target
1 2 3 4 |
С помощью recurse и recurse_target можно еще тоньше настроить результат. recurse_target позволяет переопределить цель, с которой вызывается make, а recurse позволяет ограничить рекурсию одним из конфигов (Debug или Release в данном случае):
QMAKE_EXTRA_TARGETS += customtarget customtarget.commands = @echo custom target customtarget.CONFIG = recursive customtarget.recurse = Debug customtarget.recurse_target = special_target
1 2 3 4 5 6 7 |
QMAKE_EXTRA_TARGETS += customtarget customtarget.commands = @echo custom target customtarget.CONFIG = recursive |
debug-customtarget: $(MAKE) -f $(MAKEFILE).Debug special_target customtarget: debug-customtarget @echo custom target
1 2 3 4 5 6 |
$(MAKE) -f $(MAKEFILE).Debug special_target |
Сами по себе кастомные цели в Makefile при построении проекта выполняться не будут, поскольку от них не зависят цели, созданные qmake. Для некоторых сценариев этого может быть достаточно. Например, можно создать цель doc, которая будет запускать doxygen по проекту и генерировать документацию; запустить цель можно будет с помощью команды наподобие make doc. Но меня, пользователя Qt Creator, запуск make вручную мало интересует. Так что рассмотрим некоторые способы, с помощью которых можно сделать так, чтобы добавленные цели запускались при построении проекта.
PRE_TARGETDEPS и POST_TARGETDEPS
Для линковки исполняемого файла qmake генерирует правило, выглядящее где-то следующим образом:
# переменная DESTDIR_TARGET содержит имя файла проекта (*.exe, *.dll, ...) # переменная OBJECTS содержит список всех объектных файлов $(DESTDIR_TARGET): $(OBJECTS) $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS)
1 2 3 4 5 6 7 |
# переменная DESTDIR_TARGET содержит имя файла проекта (*.exe, *.dll, ...) # переменная OBJECTS содержит список всех объектных файлов |
Как видно, результирующий файл проекта зависит только от объектных файлов и не зависит от статических библиотек, с которыми он линкуется. Если некая статическая библиотека может меняться (скажем, в нее вынесена часть проекта, и она регулярно пересобирается), то файл не будет пересобран при вызове make даже если библиотека была изменена. Нехорошо. “Вылечить” проблему можно, если добавить нужные статические библиотеки как зависимости. Для этого и предназначаются переменные PRE_TARGETDEPS и POST_TARGETDEPS. Содержат они списки целей, от которых будет зависеть линковка – и ничто не мешает указать там не только имена файлов статических библиотек, но и любые другие цели. В том числе и наши кастомные.
Разница между этими переменными в том, что цели из PRE_TARGETDEPS добавляются до $(OBJECTS), а из POST_TARGETDEPS – после, что влияет на порядок их обработки. Но, как я писал ранее, на этот порядок лучше не закладываться, и если он имеет значение, его лучше задавать явно. Пример:
QMAKE_EXTRA_TARGETS += customtarget customtarget.target = readme.txt customtarget.commands = @echo custom target > $$PWD/readme.txt customtarget.depends = $(OBJECTS) # явное задание порядка обработки целей PRE_TARGETDEPS += readme.txt # не customtarget! только имя цели, как в Makefile!
В Makefile получится что-то в таком духе:
$(DESTDIR_TARGET): readme.txt $(OBJECTS) $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) readme.txt: $(OBJECTS) @echo custom target > D:/source/test/readme.txt
1 2 3 4 5 6 7 |
$(DESTDIR_TARGET): readme.txt $(OBJECTS) $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) |
QMAKE_PRE_LINK и QMAKE_POST_LINK
Помимо своих зависимостей, в рассмотренное выше правило для $(DESTDIR_TARGET) можно добавить и свои команды. Делается это с помощью переменных QMAKE_PRE_LINK и QMAKE_POST_LINK:
QMAKE_PRE_LINK и QMAKE_POST_LINK
QMAKE_PRE_LINK = @echo QMAKE_PRE_LINK QMAKE_POST_LINK = @echo QMAKE_POST_LINK
1 2 3 4 |
QMAKE_PRE_LINK = @echo QMAKE_PRE_LINK |
QMAKE_PRE_LINK и QMAKE_POST_LINK
$(DESTDIR_TARGET): $(OBJECTS) @echo QMAKE_PRE_LINK $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) @echo QMAKE_POST_LINK
1 2 3 4 5 6 |
Полезные переменные в Makefile
qmake определяет набор переменных, которые могут быть полезны при написании своих команд. Вот, на мой взгляд, наиболее полезные из них:
Для примера, вот так можно копировать исполняемый файл после каждой линковки (в качестве примера – в каталог D:\tmp):
Копирование результирующего файла с помощью QMAKE_POST_LINK
QMAKE_POST_LINK = $(COPY_FILE) "$(DESTDIR_TARGET)" D:\tmp
1 2 3 |
Кстати, именно это действие можно настроить с помощью переменной DLLDESTDIR. Несмотря на имя, она работает для любых типов проектов, а не только динамических библиотек (хотя, конечно, для dll эта фича наиболее полезна). Переменная содержит путь к каталогу, в который будет скопирован результирующий файл. Реализована эта переменная в точности как предыдущий пример – добавлением строки вида $(COPY_FILE) "$(DESTDIR_TARGET)" D:\tmp в конец цели по сборке результирующего файла. Эта строка будет добавлена после команд в QMAKE_POST_LINK.
Копирование результирующего файла с помощью DLLDESTDIR
DLLDESTDIR = D:/tmp
1 2 3 |
QMAKE_CLEAN
Если добавленные в Makefile действия создают какие-то промежуточные файлы, то хорошим тоном будет добавить их в цель clean тем или иным образом. В сложных случаях можно написать для этого свою кастомную цель и привязать ее к цели clean, вручную аналогично вышеизложенному или с помощью переменной CLEAN_DEPS, содержащей список целей, которые будут добавлены как зависимости к цели clean. Но в тех случаях, когда нужно просто удалить известные заранее файлы, можно воспользоваться переменной QMAKE_CLEAN. Все файлы в этой переменной будут добавлены в расстрельный список прямо в действия правила clean. Пути к файлам задаются относительно OUT_PWD.
QMAKE_POST_LINK = echo garbage1> 1.txt$$escape_expand(\n\t) echo garbage2> 2.txt # явное указание файлов QMAKE_CLEAN += 1.txt 2.txt # указание маской # поиск по маске отработает во время выполнения make QMAKE_CLEAN += *.txt
1 2 3 4 5 6 7 8 9 10 |
QMAKE_POST_LINK = echo garbage1> 1.txt$$escape_expand(\n\t) echo garbage2> 2.txt |