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

QMAKE_EXTRA_TARGETS

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

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


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 добавятся цели где-то в следующем виде:

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

 

readme.txt: readme_info

    @echo custom target > D:\source\test\readme.txt

 

readme_info: FORCE

    @echo generate readme.txt

 

FORCE:

 

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

Если не предпринять специальных действий, то 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


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.

Простой recursive

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

customtarget.CONFIG = recursive

 

Makefile

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

 

debug-customtarget:

    $(MAKE) -f $(MAKEFILE).Debug customtarget

release-customtarget:

    $(MAKE) -f $(MAKEFILE).Release customtarget

customtarget: debug-customtarget release-customtarget

    @echo custom target

 

Makefile.Debug и Makefile.Release

customtarget: @echo custom target


1

2

3

4

 

customtarget:

    @echo custom target

 

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

Простой recursive

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

customtarget.recurse = Debug

customtarget.recurse_target = special_target

 

Makefile

debug-customtarget: $(MAKE) -f $(MAKEFILE).Debug special_target customtarget: debug-customtarget @echo custom 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 генерирует правило, выглядящее где-то следующим образом:

DESTDIR_TARGET

# переменная 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 содержит список всех объектных файлов

 

$(DESTDIR_TARGET):  $(OBJECTS)

    $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS)  $(LIBS)

 

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

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

PRE_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!


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 получится что-то в таком духе:

Результат в 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)

 

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:

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_POST_LINK = @echo QMAKE_POST_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

 

$(DESTDIR_TARGET):  $(OBJECTS)

    @echo QMAKE_PRE_LINK

    $(LINKER) $(LFLAGS) -o $(DESTDIR_TARGET) $(OBJECTS)  $(LIBS)

    @echo QMAKE_POST_LINK

 

Полезные переменные в Makefile

qmake определяет набор переменных, которые могут быть полезны при написании своих команд. Вот, на мой взгляд, наиболее полезные из них:

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

Копирование результирующего файла с помощью QMAKE_POST_LINK

QMAKE_POST_LINK = $(COPY_FILE) "$(DESTDIR_TARGET)" 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.

Копирование результирующего файла с помощью DLLDESTDIR

DLLDESTDIR = D:/tmp


1

2

3

 

DLLDESTDIR = D:/tmp

 

QMAKE_CLEAN

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

QMAKE_CLEANK

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

 

# явное указание файлов

QMAKE_CLEAN += 1.txt 2.txt

 

# указание маской

# поиск по маске отработает во время выполнения make

QMAKE_CLEAN += *.txt