qmake позволяет добавить свои собственные цели (targets) в создаваемый Makefile.
QMAKE_EXTRA_TARGETS
Кастомные цели (правильнее было бы сказать: правила для целей) добавляются с помощью переменной QMAKE_EXTRA_TARGETS
. Каждое значение этой переменной — имя составной переменной, описывающей цель.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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 добавятся цели где-то в следующем виде:
1 2 3 4 5 6 7 8 9 |
readme.txt: readme_info @echo custom target > D:\source\test\readme.txt readme_info: FORCE @echo generate readme.txt FORCE: |
При описании цели можно использовать следующие «атрибуты»:
target
: цель, как она будет называться в Makefile. По умолчанию ее имя равно имени описательной переменной.depends
: список зависимостей. Могут использоваться как имена цели в Makefile, так и имена описательных переменных в qmake; в последнем случае оно будет заменено на имя соответствующей цели (атрибутtarget
).commands
: команда (или команды) оболочки (shell). Может отсутствовать. Если команда завершится с ошибкой, то make также завершится с ошибкой.CONFIG
: дополнительная настройка. Мне известны два значения:phony
иrecursive
. Первое значение заставит оформить цель как «пустышку» (phony target), а о втором я ниже расскажу.recurse
иrecurse_target
: используются сCONFIG = recursive
.
Если не предпринять специальных действий, то commands
будет выведено в Makefile одной строкой, а значит, commands сможет вместить в себя только одну команду. Это ограничение можно обойти двумя способами. Во-первых, можно воспользоваться оператором &&
оболочки, который поддерживается и cmd.exe, и bash, и позволяет объединить несколько команд в одну. Во-вторых, можно вставить конец строки и необходимый для Makefile символ табуляции с помощью функции escape_expand
:
1 2 3 4 5 6 7 |
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
.
1 2 3 4 5 |
QMAKE_EXTRA_TARGETS += customtarget customtarget.commands = @echo custom target customtarget.CONFIG = recursive |
1 2 3 4 5 6 7 8 |
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 |
customtarget: @echo custom target |
С помощью recurse
и recurse_target
можно еще тоньше настроить результат. recurse_target
позволяет переопределить цель, с которой вызывается make, а recurse
позволяет ограничить рекурсию одним из конфигов (Debug или Release в данном случае):
1 2 3 4 5 6 7 |
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 |
debug-customtarget: $(MAKE) -f $(MAKEFILE).Debug special_target customtarget: debug-customtarget @echo custom target |
Сами по себе кастомные цели в Makefile при построении проекта выполняться не будут, поскольку от них не зависят цели, созданные qmake. Для некоторых сценариев этого может быть достаточно. Например, можно создать цель doc, которая будет запускать doxygen по проекту и генерировать документацию; запустить цель можно будет с помощью команды наподобие make doc
. Но меня, пользователя Qt Creator, запуск make вручную мало интересует. Так что рассмотрим некоторые способы, с помощью которых можно сделать так, чтобы добавленные цели запускались при построении проекта.
PRE_TARGETDEPS и POST_TARGETDEPS
Для линковки исполняемого файла qmake генерирует правило, выглядящее где-то следующим образом:
1 2 3 4 5 6 7 |
# переменная DESTDIR_TARGET содержит имя файла проекта (*.exe, *.dll, ...) # переменная OBJECTS содержит список всех объектных файлов $(DESTDIR_TARGET): $(OBJECTS) $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) |
Как видно, результирующий файл проекта зависит только от объектных файлов и не зависит от статических библиотек, с которыми он линкуется. Если некая статическая библиотека может меняться (скажем, в нее вынесена часть проекта, и она регулярно пересобирается), то файл не будет пересобран при вызове make даже если библиотека была изменена. Нехорошо. «Вылечить» проблему можно, если добавить нужные статические библиотеки как зависимости. Для этого и предназначаются переменные PRE_TARGETDEPS
и POST_TARGETDEPS
. Содержат они списки целей, от которых будет зависеть линковка — и ничто не мешает указать там не только имена файлов статических библиотек, но и любые другие цели. В том числе и наши кастомные.
Разница между этими переменными в том, что цели из PRE_TARGETDEPS
добавляются до $(OBJECTS)
, а из POST_TARGETDEPS
— после, что влияет на порядок их обработки. Но, как я писал ранее, на этот порядок лучше не закладываться, и если он имеет значение, его лучше задавать явно. Пример:
1 2 3 4 5 6 7 8 9 |
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 получится что-то в таком духе:
1 2 3 4 5 6 7 |
$(DESTDIR_TARGET): readme.txt $(OBJECTS) $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) readme.txt: $(OBJECTS) @echo custom target > D:/source/test/readme.txt |
QMAKE_PRE_LINK и QMAKE_POST_LINK
Помимо своих зависимостей, в рассмотренное выше правило для $(DESTDIR_TARGET)
можно добавить и свои команды. Делается это с помощью переменных QMAKE_PRE_LINK
и QMAKE_POST_LINK
:
1 2 3 4 |
QMAKE_PRE_LINK = @echo QMAKE_PRE_LINK QMAKE_POST_LINK = @echo QMAKE_POST_LINK |
1 2 3 4 5 6 |
$(DESTDIR_TARGET): $(OBJECTS) @echo QMAKE_PRE_LINK $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS) $(LIBS) @echo QMAKE_POST_LINK |
Полезные переменные в Makefile
qmake определяет набор переменных, которые могут быть полезны при написании своих команд. Вот, на мой взгляд, наиболее полезные из них:
COPY_FILE
: команда для копирования файла (copy /y
на Windows).COPY_DIR
: команда для копирования каталога (xcopy /s /q /y /i
на Windows)DEL_FILE
: команда для удаления файла (del
на Windows).DEL_DIR
: команда для удаления каталога (rmdir
на Windows).MOVE
: команда для перемещения файлов (move
на Windows).MKDIR
: команда для создания каталога (mkdir
на Windows).MAKEFILE
: Имя Makefile файла. Обычно Makefile, Makefile.Release, Makefile.DebugDEFINES
: список определенных для проекта макросов.QMAKE
: полный путь к qmake, который создавал Makefile.TARGET
: имя результирующего исполнимого файла.DESTDIR_TARGET
: относительный путь к результирующему файлу.DESTDIR
: относительный путь к каталогу, в котором лежит результирующий файл.
Для примера, вот так можно копировать исполняемый файл после каждой линковки (в качестве примера — в каталог D:\tmp):
1 2 3 |
QMAKE_POST_LINK = $(COPY_FILE) "$(DESTDIR_TARGET)" D:\tmp |
Кстати, именно это действие можно настроить с помощью переменной DLLDESTDIR
. Несмотря на имя, она работает для любых типов проектов, а не только динамических библиотек (хотя, конечно, для dll эта фича наиболее полезна). Переменная содержит путь к каталогу, в который будет скопирован результирующий файл. Реализована эта переменная в точности как предыдущий пример — добавлением строки вида $(COPY_FILE) "$(DESTDIR_TARGET)" D:\tmp
в конец цели по сборке результирующего файла. Эта строка будет добавлена после команд в QMAKE_POST_LINK
.
1 2 3 |
DLLDESTDIR = D:/tmp |
QMAKE_CLEAN
Если добавленные в Makefile действия создают какие-то промежуточные файлы, то хорошим тоном будет добавить их в цель clean тем или иным образом. В сложных случаях можно написать для этого свою кастомную цель и привязать ее к цели clean
, вручную аналогично вышеизложенному или с помощью переменной CLEAN_DEPS
, содержащей список целей, которые будут добавлены как зависимости к цели clean
. Но в тех случаях, когда нужно просто удалить известные заранее файлы, можно воспользоваться переменной QMAKE_CLEAN
. Все файлы в этой переменной будут добавлены в расстрельный список прямо в действия правила clean
. Пути к файлам задаются относительно OUT_PWD
.
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 # явное указание файлов QMAKE_CLEAN += 1.txt 2.txt # указание маской # поиск по маске отработает во время выполнения make QMAKE_CLEAN += *.txt |
Приветсвую.
В qmake есть недокументированные переменные, которые, имхо, предпочтительней использовать переменных Makefile. Они прописаны в некоторых makespec/*.conf файлах. Вот например список некоторых переменных из Qt5, файла shell-win32.conf:
QMAKE_ZIP = zip -r -9
QMAKE_COPY = copy /y
QMAKE_COPY_DIR = xcopy /s /q /y /i
QMAKE_MOVE = move
QMAKE_DEL_FILE = del
QMAKE_DEL_DIR = rmdir
QMAKE_CHK_EXISTS = if not exist %1
QMAKE_CHK_DIR_EXISTS = if not exist # legacy
QMAKE_MKDIR = mkdir # legacy
QMAKE_MKDIR_CMD = if not exist %1 mkdir %1 & if not exist %1 exit 1
Конечно, можно их использовать. Но нужно помнить, что эти переменные раскрываются в qmake, не в make.
Это как раз и хорошо, ведь можно сразу вывести раскрывшийся результат на консоль через message или посмотреть Makefile. Да и использование переменных из Makefile в pro файле не выглядит логичным. Я, признаюсь, немного впал в ступор, когда увидел на форуме подобный синтаксис, не поддающийся правилам qmake.
P.S. Спасибо за статью.
Кроме того, использование переменных из Makefile делает невозможным использование генерацию файлов проектов для VS. Проверил на VS2008. Скорее всего не будет работать и на других версиях VS, а также XCode.
Чего не проверял, так не проверял. Я на Qt Creator плотно сижу…