Помимо описанных ранее механизмов задать начальные значения переменных qmake предоставляет механизм для изменения переменных после обработки собственно .pro файла. Это механизм фич (features), и он является главным средством для расширения и настройки qmake под свои нужды.
Вкратце, работает он следующим образом:
- имена фич (только маленькие буквы в имени!), которые будут использованы, указываются в специальной переменной
CONFIG
; - файл фичи ищется в наборе специальных каталогов, имя файла есть имя фичи с расширением .prf;
- файл фичи неявно добавляется в конец .pro файла, подобно
include
Рассмотрим подробнее некоторые моменты.
Поиск фич
Цитируя документацию, при поиске фич просматриваются следующие каталоги, в порядке очередности:
- Из списка каталогов в переменной окружения
QMAKEFEATURES
(разделяются двоеточием) - Из списка каталогов в переменной в qmake
QMAKEFEATURES
(также разделяются двоеточием) - Каталог
features
в каталогеmkspecs
- И в кучке других мест в установочном каталоге Qt, которые мне влом перечислять.
Поскольку ковыряться в установочном каталоге Qt есть идея по многим причинам не очень хорошая, из всех этих способов подсунуть фичу qmake годится только переменная окружения или переменная qmake. Первый способ у меня не заработал почему-то, я особо не разбирался почему. Не люблю переменные окружения. А второй способ работает отлично. Я создал файл .qmake.cache
в корне дерева своих исходников и в нем прописал значение для переменной:
1 2 3 |
QMAKEFEATURES = D:/sources/sys/qmake/features |
Взаимоисключающие фичи
Иногда может быть удобно сделать пару взаимоисключающих фич. Самый заметный случай — это стандартные фичи debug и release. Выполнена может быть только одна из них. Можно потребовать от программиста, чтобы он следил за тем, чтобы эти фичи не были указаны одновременно, но авторы qmake решили этот вопрос по-другому, более гибким образом.
Взаимоисключающие фичи можно указывать в переменной CONFIG
одновременно и сколько угодно раз; эффект должна иметь последняя указанная. За тем, какая из фич имеет эффект, должны следить сами фичи, вызываются они в любом случае. Фича при вызове должна проверить, активна ли она, и действовать соответственно. Для проверки активности фичи предназначается встроенная функция CONFIG
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
CONFIG += apple garbage peach apple garbage2 apricot peach apple garbage3 # проверка активности фичи из списка взаимоисключающих фич # ищется первое встреченное с конца значение из списка (второй параметр) # и сверяется с проверяемой фичей (первый параметр) CONFIG(apple, apple|apricot|peach) { # apple активно message(apple is active) } # есть еще редко нужный синтаксис с одним параметром # его отличие от просто peach в условии заключается в том, # что проверяется только переменная CONFIG, а не mkspecs к примеру. CONFIG(peach) { # peach присутствует в CONFIG message(peach is present in CONFIG) } # другое имя функции CONFIG - isActiveConfig isActiveConfig(peach) { # peach присутствует в CONFIG message(peach is present in CONFIG) } |
Обратной стороной описанного подхода к реализации взаимоисключающих фич является неочевидность проверки взаимоисключающих условий. Интернет пестрит вопросами в духе «я проверяю на debug/release, и как-то ничего не работает, что делать».
1 2 3 4 5 6 7 |
debug { # вариант для debug } else { # вариант для release } |
Чтобы жизнь была хороша и сияло солнце, нужно делать так:
1 2 3 4 5 6 7 |
CONFIG(debug, debug|release) { # вариант для debug } else { # вариант для release } |
Порядок выполнения фич не определен
По замыслу разработчиков qmake, фичи должны быть независимы друг от друга, так что порядок их выполнения не должен иметь значения. Вот он и не определен.
Соответственно, нет удобного способа что-то сделать до того, как начнут выполняться фичи, или после того, как все фичи отработают. Если первое ограничение еще можно обойти с помощью include
в конце файла, то последнее никак нельзя.
Функция load
Иногда может быть нужно выполнить фичу не в конце файла, а в определенном месте. В самом начале файла, например, вместо include
. Для этого можно воспользоваться недокументированной функцией load
, которая ведет себя во многом подобно include
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# первым параметром указывается имя фичи load(feature_name) # Если фича не будет найдена, будет выдано предупреждение, # но выполнение qmake продолжиться. # Можно подавить это предупреждения с помощью второго параметра load(feature_name, true) # то же самое load(feature_name, 1) # или можно обработать ошибку !load(feature_name) { # принять меры, например - фатальная ошибка вместо предупреждения error(can't go on) } |
Вместо имени фичи можно указать путь к файлу фичи. Также прошу заметить такой момент: фича может быть выполнена только один раз. Все последующие вызовы load не будут иметь эффекта.
Фичи и Makefile
Все использованные фичи будут перечилены как зависимости в Makefile на вызов qmake. Соответственно, изменение файла фичи приведет к повтороной обработке .pro файла.
Хорошо, что разработчики продумали этот момент, а то нужно было бы при изменении своих самописных фич каждый раз руками перекомпилировать все свои проекты.
Фичи и Qt Creator
Фичи предназначены для «закулисной» работы, результаты их деятельности, по задумке создателей, должны сказываться на проекте неявным образом. Например, если фича реализует вызов IDL компилятора, то результат ее работы (промежуточные файлы, которые добавляются в переменную исходных файлов для дальнейшей обработки) не должен мозолить глаза программисту. По этой причине Qt Creator не отображает в интерфейсе проекта изменения, сделанные фичами.
В целом это правильное решение, но иногда оно мешает. Приходится выкручиваться через include в конце .pro файла.
Пример: MIN_QT_VERSION
Расширим qmake проверкой минимальной версии Qt: если версия меньше, чем указанная программистом в переменной MIN_QT_VERSION
, то qmake завершится с ошибкой.
Для реализации этой фичи создадим в одном из специальных мест файл minqtversion.prf
следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# не пугайтесь, я все это объясню в дальнейших постах !isEmpty(MGS_MIN_QT_VERSION) { __mgs_ver = $$split(MGS_MIN_QT_VERSION,'.') lessThan(QT_MAJOR_VERSION, $$member(__mgs_ver, 0)) { error(Minimum Qt version required: $$MGS_MIN_QT_VERSION while Qt version is: $$QT_VERSION) } equals(QT_MAJOR_VERSION, $$member(__mgs_ver, 0)) { lessThan(QT_MINOR_VERSION, $$member(__mgs_ver, 1)) { error(Minimum Qt version required: $$MGS_MIN_QT_VERSION while Qt version is: $$QT_VERSION) } equals(QT_MINOR_VERSION, $$member(__mgs_ver, 1)) { lessThan(QT_PATCH_VERSION, $$member(__mgs_ver, 2)) { error(Minimum Qt version required: $$MGS_MIN_QT_VERSION while Qt version is: $$QT_VERSION) } } } unset(__mgs_ver) } |
Пользоваться этой фичей предполагается следующим образом:
1 2 3 4 |
CONFIG += minqtversion MIN_QT_VERSION = 5.0.3 |
Могу добавить, что механизм с .qmake.cache не работает в Qt 4.8.6
Есть только один вариант как оповестить qmake где искать features,
это создать переменную окружения QMAKEFEATURES=D:/sources/sys/qmake/features
В Qt 5.x.x это работает. Я использовал вот такой код .pro
#———————————————————————————————————-
# Создать спецфайл, в котором указать qmake где искать features
MY_QMAKE_CACHE=$${OUT_PWD}/.qmake.cache
!exists($$MY_QMAKE_CACHE) : {
system(echo QMAKEFEATURES=$$PWD/features> $$MY_QMAKE_CACHE)
error(Please start again qmake)
}
unset(MY_QMAKE_CACHE)
#———————————————————————————————————-
Найдено решение, которое работает в Qt4 и Qt5
#———————————————————————————————————-
# создать директиву features для qmake
!exists($${OUT_PWD}/.qmake.cache) : {
system(echo QMAKEFEATURES=$$PWD/features>$${OUT_PWD}/.qmake.cache)
error(Please start again qmake)
}
!build_pass:message($$basename(_FILE_):$$_LINE_ _QMAKE_CACHE_=$$_QMAKE_CACHE_)
# Теперь можно использовать конструкцию вида:
# первый load для Qt5 А этот для Qt4
!load(_check_toolchane, 1): load(features/_check_toolchane.prf)
#———————————————————————————————————-