qmake: добавление своих команд в Makefile

qmake позволяет добавить свои собственные цели (targets) в создаваемый Makefile.

QMAKE_EXTRA_TARGETS

Кастомные цели (правильнее было бы сказать: правила для целей) добавляются с помощью переменной QMAKE_EXTRA_TARGETS. Каждое значение этой переменной — имя составной переменной, описывающей цель.

В Makefile добавятся цели где-то в следующем виде:

При описании цели можно использовать следующие «атрибуты»:

  • 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:

Теперь о recursive. Эта опция имеет значение для режима debug_and_release, который меня мало интересует, поэтому особо не разбирался с этой опцией. Вот то, что я выяснил.

При включенном debug_and_release создаются три Makefile с именами Makefile, Makefile.Debug и Makefile.Release. Кастомные цели будут в этих файлах просто продублированы, что может быть не то, что нужно. Если же указать CONFIG = recursive, то в Makefile вместо простой копии цели будет сгенерирован вызов make для Makefile.Debug и Makefile.Release.

С помощью recurse и recurse_target можно еще тоньше настроить результат. recurse_target позволяет переопределить цель, с которой вызывается make, а recurse позволяет ограничить рекурсию одним из конфигов (Debug или Release в данном случае):

Сами по себе кастомные цели в Makefile при построении проекта выполняться не будут, поскольку от них не зависят цели, созданные qmake. Для некоторых сценариев этого может быть достаточно. Например, можно создать цель doc, которая будет запускать doxygen по проекту и генерировать документацию; запустить цель можно будет с помощью команды наподобие make doc. Но меня, пользователя Qt Creator, запуск make вручную мало интересует. Так что рассмотрим некоторые способы, с помощью которых можно сделать так, чтобы добавленные цели запускались при построении проекта.

PRE_TARGETDEPS и POST_TARGETDEPS

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

Как видно, результирующий файл проекта зависит только от объектных файлов и не зависит от статических библиотек, с которыми он линкуется. Если некая статическая библиотека может меняться (скажем, в нее вынесена часть проекта, и она регулярно пересобирается), то файл не будет пересобран при вызове make даже если библиотека была изменена. Нехорошо. «Вылечить» проблему можно, если добавить нужные статические библиотеки как зависимости. Для этого и предназначаются переменные PRE_TARGETDEPS и POST_TARGETDEPS. Содержат они списки целей, от которых будет зависеть линковка — и ничто не мешает указать там не только имена файлов статических библиотек, но и любые другие цели. В том числе и наши кастомные.

Разница между этими переменными в том, что цели из PRE_TARGETDEPS добавляются до $(OBJECTS), а из POST_TARGETDEPS — после, что влияет на порядок их обработки. Но, как я писал ранее, на этот порядок лучше не закладываться, и если он имеет значение, его лучше задавать явно. Пример:

В Makefile получится что-то в таком духе:

QMAKE_PRE_LINK и QMAKE_POST_LINK

Помимо своих зависимостей, в рассмотренное выше правило для $(DESTDIR_TARGET) можно добавить и свои команды. Делается это с помощью переменных QMAKE_PRE_LINK и 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.Debug
  • DEFINES: список определенных для проекта макросов.
  • QMAKE: полный путь к qmake, который создавал Makefile.
  • TARGET: имя результирующего исполнимого файла.
  • DESTDIR_TARGET: относительный путь к результирующему файлу.
  • DESTDIR: относительный путь к каталогу, в котором лежит результирующий файл.

Для примера, вот так можно копировать исполняемый файл после каждой линковки (в качестве примера — в каталог D:\tmp):

Кстати, именно это действие можно настроить с помощью переменной DLLDESTDIR. Несмотря на имя, она работает для любых типов проектов, а не только динамических библиотек (хотя, конечно, для dll эта фича наиболее полезна). Переменная содержит путь к каталогу, в который будет скопирован результирующий файл. Реализована эта переменная в точности как предыдущий пример — добавлением строки вида $(COPY_FILE) "$(DESTDIR_TARGET)" D:\tmp в конец цели по сборке результирующего файла. Эта строка будет добавлена после команд в QMAKE_POST_LINK.

QMAKE_CLEAN

Если добавленные в Makefile действия создают какие-то промежуточные файлы, то хорошим тоном будет добавить их в цель clean тем или иным образом. В сложных случаях можно написать для этого свою кастомную цель и привязать ее к цели clean, вручную аналогично вышеизложенному или с помощью переменной CLEAN_DEPS, содержащей список целей, которые будут добавлены как зависимости к цели clean. Но в тех случаях, когда нужно просто удалить известные заранее файлы, можно воспользоваться переменной QMAKE_CLEAN. Все файлы в этой переменной будут добавлены в расстрельный список прямо в действия правила clean. Пути к файлам задаются относительно OUT_PWD.

Цикл: qmake

  5 comments for “qmake: добавление своих команд в Makefile

  1. twp
    25.07.2013 at 13:29

    Приветсвую.
    В 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

    • mgsxx
      25.07.2013 at 13:33

      Конечно, можно их использовать. Но нужно помнить, что эти переменные раскрываются в qmake, не в make.

      • twp
        25.07.2013 at 18:20

        Это как раз и хорошо, ведь можно сразу вывести раскрывшийся результат на консоль через message или посмотреть Makefile. Да и использование переменных из Makefile в pro файле не выглядит логичным. Я, признаюсь, немного впал в ступор, когда увидел на форуме подобный синтаксис, не поддающийся правилам qmake.
        P.S. Спасибо за статью.

      • twp
        26.07.2013 at 13:49

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

Добавить комментарий