Консоль
Выдать сообщение на консоль (будет отображаться во вкладке Compiler Output в Qt Creator) можно с помощью функции message
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
message(text to output to stdout) # Project MESSAGE: text to output to stdout # по сути то же самое, что и message warning(text to output to stdout) # Project WARNING: text to output to stdout # если qmake работает в режиме debug_and_release, # то сообщение будет выведено трижды (по одному для # каждого из создаваемого Makefile). Чтобы избежать # этой глупости документация советует использовать следующую конструкцию: !build_pass:message(some message) # лично я вместо этого отключаю debug_and_release # что такое debug_and_release я расскажу в одном из дальнейших постов. # есть еще функция log, которая не добавляет ни префикса в духе "Project MESSAGE:", ни переноса строки. log(some text) |
Если нужно завершить qmake с ошибкой, используйте error
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
error(error message) # Project ERROR: error message # Для указания места ошибки можно использовать переменные _FILE_ и _LINE_ error(critical error in $$_FILE_ at $$_LINE_) # есть также аналогичного переменная _DATE_ с текущим временем, но # пользы от нее мало, т.к. она использует системную локализацию для даты. # Которая у меня - русская, а русские буквы в Qt Creator # у меня заменяются на вопросительные знаки. message($$_DATE_) # Project MESSAGE: ?? 16. ??? 10:55:36 2013 |
Как альтернативу error
в некоторых можно использовать функцию requires
. Эта функция получает один параметр — условие для проверки — и если оно не выполняется, вместо нормального Makefile генерируется заглушка, которая пишет что «модуль не найден».
1 2 3 4 |
# требование того, чтобы самописная фича была добавлена в CONFIG requires(my_custom_feature) |
Есть еще функция prompt
, которая ждет ввода пользователя и возвращает его.
1 2 3 |
A = $$prompt(Enter text:) |
Польза от этой функции была бы сомнительна, даже если бы она работала в Qt Creator. А она не работает, Qt Creator не перенаправляет ввод в qmake.
В тему о выводе из qmake и make. По умолчанию, вывод очень подробный, для каждого компилируемого файла выводится вся командная строка со всеми параметрами и т.п. Если хочется более сжатого вывода, по строчке на обрабатываемый файл, можно включить тихий режим:
1 2 3 |
CONFIG += silent |
Имена файлов
Функции basename
и dirname
служат для разбора имени файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Файл не должен реально существовать, это просто работа со строками. FILEPATH = D:/dir/file.ext # Функции работает только с переменной! Уж не знаю зачем так message($$basename(D:/dir/file.ext)) # пусто... message($$basename(FILEPATH)) # file.ext message($$dirname(FILEPATH)) # D:/dir # с виндовым разделителем функции тоже работают FILEPATH = D:\\dir\\file.ext message($$basename(FILEPATH )) # file.ext message($$dirname(FILEPATH )) # D:\dir |
Для работы с путями qmake предоставляет несколько полезных функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# абсолютный путь к файлу в PWD # переменная PWD содержит путь к каталогу, содержащему файл проекта message($$absolute_path(file.ext)) # D:/sources/test/single/file.ext # абсолютный путь к файлу в указанном каталоге. # Переменная OUT_PWD содердит путь к каталогу, в котором во время компиляции # создаются объектные и прочие файлы, включая исполнимый файл # Может не совпадать с PWD. message($$absolute_path(file.ext, $$OUT_PWD)) # D:/sources/test/build-single-Desktop_Qt_5_0_2_MinGW_32bit-Debug/file.ext # выделяет относительный путь из абсолютного пути к файлу в PWD message($$relative_path($$PWD/file.ext)) # file.ext # аналогично для любого другого каталога message($$relative_path($$OUT_PWD/file.ext, $$OUT_PWD)) # file.ext # очистка пути - убираются лишние слэши и переходы по дереву вверх/вниз message($$clean_path(d://dir/../dir/\\file.ext)) # d:/dir/file.ext |
Функция files
возвращает список всех найденных по маске файлов:
1 2 3 4 5 6 7 8 9 10 11 12 |
# Пусть в каталоге проекта лежит include.pri и include2.pri, a в подкаталоге 1 - a.pri FILELIST = $$files(*.pri) message($$FILELIST) # include.pri include2.pri # рекурсивный поиск в подкаталогах FILELIST = $$files(*.pri, true) message($$FILELIST) # include.pri include2.pri 1/a.pri # пути возвращаюся в той форме, в которой были переданы, # т.е. относительные пути не превращаются в абсолютные |
Работа с файлами
Проверить, существует ли файл или каталог, можно с помощью exists
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
exists(filename.ext) { # рядом с текущим файлом существует файл filename.ext } #можно использовать пути exists(D:/dir/filename.ext) { } # также можно использовать маски (wildcards/glob): exists(D:/dir/filename.*) { # существует хотя бы один файл или каталог, # удовлетворяющий маске } |
Создать каталог можно с помощью функции mkpath. Если каталог уже существует, ошибки не будет.
1 2 3 4 5 6 7 8 9 10 11 |
# Создает каталог mydir в PWD (т.е. в том же каталоге, где находится .pro файл) mkpath(mydir) # Создает каталог по абсолютному пути mkpath($$OUT_PWD/mydir) # создаются все несуществующие каталоги в указанном пути # т.е. не нужно отдельно создавать mydir, dir1, dir2 mkpath(mydir/dir1/dir2) |
Функция cat
читает текстовый файл и сохраняет его в переменную. Какая при этом используется кодировка — не разбирался детально. Точно не UTF-8, а раз так, то лучше ограничиваться ANSI.
1 2 3 4 5 |
a b c ;asdf;kls |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# режим по умолчанию игнорирует пробелы и переносы строк A = $$cat(D:/1.txt) # то же самое, что A = $$cat(D:/1.txt, true) message($$A) #a b c ;asdf;kls # если указать false, то переносы строк (\n) будут # оформлены отдельными значениями A = $$cat(D:/1.txt, false) # для ясности я заменяю \n на | message($$replace(A, $$escape_expand(\n), |)) # a b | c | ;asdf;kls | # режим lines каждую строку оформляет одним значением. # концы строк (\n) при этом отбрасываются, но пробелы не ужимаются A = $$cat(D:/1.txt, lines) message($$A) # a b c ;asdf;kls message($$first(A)) # a b # режим blob считывает файл как есть в одно значение, # без каких-либо преобразований A = $$cat(D:/1.txt, blob) message($$replace(A, $$escape_expand(\n), |)) # a b|c|;asdf;kls A = $$first(A) message($$replace(A, $$escape_expand(\n), |)) # a b|c|;asdf;kls |
Функция write_file
позволяет создать файл и записать в него значение переменной:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
VAR = 1 2 3 # Создает файл 1.txt и записывает в него значения # из переменной VAR, по одному значению на строку. # Если файл существует, то он будет перезаписан. write_file(D:/1.txt, VAR) VAR = 4 5 6 # Если файл 1.txt существует, то файл не будет перезаписан, # значения будут добавлены в конец файла write_file(D:/1.txt, VAR, append) # создает пустой файл write_file(D:/2.txt) # как обычно, относительные пути означают PWD write_file(3.txt) # то же самое, что $$PWD/3.txt |
Функция touch
устанавливает время модификации файла (первый параметр) равным времени модификации другого файла (второй параметр):
1 2 3 4 5 6 |
# время модификации file1.txt станет таким же, как у file2.txt # файлы должны существовать # относительные пути означают PWD touch(file1.txt, file2.txt) |
Shell
Самый мощный способ расширить qmake, по сути, чем угодно — это вызвать из него внешнюю программу. Для этого служит функция system
в разных своих проявлениях:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# можно запустить любую программу или команду оболочки system(echo text to file > filename.txt) # можно проверить на успешность (т.е. 0 в коде возврата) !system(my_script.cmd) { # ошибка при выполнении команды } # также можно получить stdout из запущенной программы path = $$system(PATH) # еще пример - логин текущего пользователя в Windows user = $$system(set username) user = $$split(user, =) user = $$last(user) message($$user) # хотя в данном случае можно просто message($(username)) # в качестве некоторой помощи в написании кроссплатформенных # конструкций существует переменная DIR_SEPARATOR message($$DIR_SEPARATOR) # \ на windows, / на unix # но лучше использовать функцию system_path message($$system_path(d:/dir/file.ext)) # под Windows должно быть d:\dir\file.ext # Еще есть функция для правильного оформления аргументов командных строк. # Под Windows она заключает значения с пробелами в двойные кавычки. message($$system_quote(arg)) # arg message($$system_quote(arg with spaces)) # "arg with spaces" # теоретически, system может запускаться по одним shell, # a make - под другим (в голову приходит Cygwin). # Тогда для system нужны будут одни правила для путей и аргументов, # а для команд в Makefile - другие. Так что в qmake есть аналоги # рассмотренных выше функций. message($$shell_path(d:/dir/file.ext)) message($$shell_quote(arg with spaces)) |
Обращаю ваше внимание: system выполняется во время выполнения qmake, а не make! Это различие очень важно. Qt Creator при построении проекта запускает make, а не qmake, как можно было бы подумать. Если .pro файл не поменялся (явно или неявно через свои зависимости вроде фич), то qmake при этом запущен не будет! Имейте это в виду. Если вы хотите, чтобы некоторое действие выполнялось при любом построении проекта, вам нужно менять Makefile. О том, как это можно сделать, я расскажу в дальнейших постах.
«Мета-qmake»
В qmake есть несколько функций (eval
, infile
, fromfile
), которые позволяют интерпретировать произвольный текст как часть .pro файла, причем этот текст может формироваться динамически. Я не особо игрался с ними, так как, по-моему, это уже слишком сложно и подобные задачи удобнее решаются другими средствами. Единственное применение, которое я нашел полезным — это использование функции eval для доступа к переменным по динамически генерируемым именам.
Функция eval вначале вычисляет параметр, а потом интерпретирует его как код в синтаксисе qmake и выполняет его.
1 2 3 4 5 6 7 8 9 10 11 12 |
A = B eval($$A = 10) # B = 10 # если eval обнаружит ошибку синтаксиса, # то будет выдано предупреждение, но # выполнение qmake продолжиться. # Можно обработать ошибку самому: !eval(A ++= 20) { error(syntax error) } |
Функции infile
и fromfile
позволяют обращаться к переменным, определенным в других файлах синтаксиса qmake. Первая функция позволяет проверить, равна ли некоторая переменная некоторому значению, а вторая — получить это значение. При этом, заметьте, указанный файл выполняется по правилам qmake.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include.pri A = 10 B = 20 $$A #project.pro # файл, имя переменной, проверяемое значение infile(include.pri, A, 10) { message(Yes!) } message($$fromfile(include.pri, B)) # 20 10 |
Список всех определенных переменных можно получить с помощью функции enumerate_vars
. Узнать, определена ли переменная с некоторым именем, можно с с помощью рассмотренной ранее функции defined
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
VARS = $$enumerate_vars() QT_INTERNAL_VARS = $$find(VARS, QT\\.) # убираю переменные, которые начинаются на QT., # а то их слишком много и они не особо интересны VARS -= $$QT_INTERNAL_VARS message($$VARS) # var во втором параметре указывает на то, № что ищется переменная, VARS в данном случае. # defined может и функции пользователя искать. defined(VARS, var): message(variable VARS is defined!) |