В qmake можно определить два вида пользовательских функций: условные (conditional functions или test functions) и обычные (replace functions). Первые используются в условиях конструкций ветвления (scopes), вторые – в остальных контекстах.
Объявляются функции с помощью следующего неуклюжего синтаксиса:
Объявление функций
# Условная функция defineTest(testFunctionName) { # скобка обязательно в этой строке! ... } # Обычная функция defineReplace(replaceFunctionName) { # скобка обязательно в этой строке! ... }
Условные функции возвращают значение с помощью return(true) или return(false), а обычные с помощью return(все, что угодно). Обращаю ваше внимание на то, что функции обязаны что-то вернуть, даже если возвращаемое значение не используется, иначе будет ошибка во время выполнения qmake.
Условные функции вызываются в условиях конструкций ветвления с помощью привычного синтаксиса: testFunction(param1, param2, ...). Скобки обязательны даже в том случае, если функция вызывается без параметров. Обычные функции вызываются аналогично, но зачем-то с использованием оператора $$: $$replaceFunction(param1, param2, ...). Скобки также обязательны.
Функции могут принимать любое количество параметров. Обращение к ним происходит по порядковому номеру: $$1, $$2, и т. д. (верхний лимит не искал, но он точно больше 10, то есть, например, $$12 работает). Также можно обратиться ко всем параметрам сразу с помощью $$ARGS, что даст их значения, соединенные пробелами. Даже если значение параметра содержит пробелы, никаких специальных действий по этому поводу вроде заключения значения в двойные кавычки не происходит, так что в общем случае по $$ARGS параметры восстановить нельзя:
Неопределенность с $$ARGS
defineReplace(argsTest){ message($$ARGS) # вывод на консоль return($$2) # возвращаем значение второго параметра } # если возвращаемое значение не нужно, присваивание можно опустить $$argsTest(1,2,3) # 1 2 3 VAR = $$argsTest(1,2 3) # 1 2 3 message($$VAR) # 2 3
Выше был пример обычной функции, вот пример условной:
Пример условной функции
# возвращает true, если все файлы, имена которых # переданы как параметры, существуют defineTest(allFiles) { files = $$ARGS for(file, files) { !exists($$file) { return(false) } } return(true) } # пример использования !allFiles($$SOURCES) { error("Not all required *.cpp files are found!") }
Я обнаружил баг касательно параметров, который не позволяет использовать фичу переменного количества параметров в полном объеме. Из-за этого бага нельзя сказать, сколько в действительности было передано параметров в функцию, и значит, нельзя строить интерфейс функции в духе “если был передан один параметр, то один вариант выполнения, а если два – то другой”. Если вызвать функцию на уровне файла и не передать, скажем, 3-й параметр, то значение $$3 будет пустым. Но что будет, если вызвать функцию из функции?
Баг с передачей параметров
defineReplace(func1) { message(func1: $$3) return(0) } defineReplace(func2) { $$func1(1,2) return(0) } $$func1(a,b) # func1: $$func2(1,2,3) # func1: 3
Итого, если значение параметра не переопределяется в вызове функции, то оно тупо копируется из вызывающей функции. Имейте в виду. Что характерно, $$ARGS от этого бага не страдает.
Существует возможность определить, определена ли некоторая функция или нет, причем, если нужно, даже с учетом типа функции:
Определена ли функция?
defineTest(testtest) { return(false) } # поиск условной (test) функции с именем testtest defined(testtest, test) { message(There is \'testtest\' test function) } # поиск обычной функции (replace) с именем testtest !defined(testtest, replace) { message(There is no \'testtest\' replace function) } # Можно и без типа искать !defined(testtesttest) { message(There is no function of any type with name \'testtesttest\') } # Можно искать также и переменные defined(SOME_VARIABLE, var) { message(SOME_VARIABLE does exist.) }
В заключение расскажу об областях видимости, о чем официальный мануал почему-то стыдливо молчит. Все функции работают с локальными копиями переменных, создаваемых на момент вызова. Если вы, скажем, поменяете переменную SOURCES в функции, то это изменение никак не отобразится на переменной SOURCES в месте вызова; аналогично, создание новой переменной в функции не создаст переменной в месте вызова. Чтобы перенести изменения в контексты вызова, необходимо использовать встроенную функцию export(ИмяПеременной). Обратите внимание: переменная переносится во все контексты, вплоть до файла, а не только в самый последний контекст вызова, как можно было бы предположить. Другими словами, если файле вызывается функция, которая вызывает функцию, которая экспортирует некоторую переменную, то значение этой переменной обновится и в первой функции, и в файле тоже. Пример:
Экспорт новых значений переменных
defineReplace(func1) { AAA = newValue export(AAA) return(0) } defineReplace(func2) { $$func1() message(func2: $$AAA) return(0) } $$func2() # func2: newValue message(global: $$AAA) # global: newValue
aaaa