Хабрахабр

[Из песочницы] Полное руководство по CMake. Часть первая: Синтаксис

Написав однажды небольшой и понятный всем скрипт, Вы тем самым обеспечите одинаковую сборку Вашего проекта на любых платформах, где доступен CMake. CMake — это открытый и кросс-платформенный набор утилит, предназначенных для автоматизации тестирования, компиляции и создания пакетов проектов на C/C++.

В Вашем распоряжении, с функциональной стороны, есть лишь команды, которые могут образовываться в довольно сложные конструкции. Язык CMake, будучи транслированным в нативный файл сборки (например, Makefile или Ninja), определяет процесс всего управления проектом. С них мы и начнём.

Экспериментируйте с исходным кодом, меняя существующие команды и добавляя новые. Ниже приведены примеры использования языка CMake, по которым Вам следует попрактиковаться. Чтобы запустить данные примеры, следуйте этим шагам:

  1. Установите программу CMake с официального сайта
  2. Создайте на рабочем столе текстовый файл CMakeLists.txt
  3. Добавьте в начало файла cmake_minimum_required(VERSION 3.0)
  4. Скопируйте туда исходные тексты необходимых примеров
  5. Если у Вас установлен консольный CMake, то запустить скрипт можно с помощью команды "cmake .". Если у Вас графический CMake, то в первые два верхних поля приложения вбейте адрес Вашего рабочего стола, затем нажмите кнопку Generate. Результат появится в нижнем текстовом поле.

Чтобы вызвать команду, необходимо написать её имя, а затем передать ей обрамлённые в круглые скобки аргументы, отделённые символами пробелов. Команды в CMake подобны функциям во многих языках программирования. В приведённом примере команде message передаются шесть аргументов для вывода в консоль:

# Напечатает в консоль "CMake is the most powerful buildsystem!"
message("CMake " "is " "the " "most " "powerful " "buildsystem!")

Необрамлённые аргументы не позволяют производить подобных вещей и не могут включать в себя символы ()#"\ и пробелы, однако более удобны для использования. Аргументы, обрамлённые в двойные кавычки, позволяют внутри себя совершать экранирование и подстановку переменных. Пример:

# Напечатает "Hello, my lovely CMake", один таб и "!":
message("Hello, my lovely CMake\t!") # Напечатает "Hello,_my_lovely_CMake!" без пробелов:
message(Hello,_my_lovely_CMake!)

Об этой особенности поведовали в комментариях. Стоит отметить, что аргумент Walk;around;the;forest расширится до списка Walk around the forest, так как любой необрамлённый аргумент автоматически расширяется до списка значений (при условии, что значения изначального аргумента разделены символами точки с запятой), но с обрамлённым в двойные кавычки аргументом такая трансформация не происходит.

Текст, заключённый в комментариях, игнорируется системой сборки и не оказывает никакого эффекта на её работу. Комментарии начинаются с символа решётки и заканчиваются в конце той строки, где они были напечатаны. Примеры выше также демонстрируют использование комментариев.

Получить значение переменной можно по конструкции $. Переменные можно определить путём вызова команды set, а удалить вызовом unset. Пример:

# Определить переменную VARIABLE со значением "Mr. Thomas":
set(VARIABLE "Mr. Thomas") # Напечает "His name is: Mr. Thomas":
message("His name is: " ${VARIABLE}) # Удалить переменную VARIABLE:
unset(VARIABLE)

Логические выражения используются при проверки условий и могут принимать одно из двух значений: правда или ложь. Прежде чем приступать к изучению условных операторов и циклических конструкций, необходимо понимать работу логических выражений. Выражение 88 EQUAL 88 обратится в правду, 63 GREATER 104 обратится в ложь. Например, выражение 52 LESS 58 обратится в правду, так как 52 < 58. Полный список логических выражений можно посмотреть тут. Сравнивать можно не только числа, но и строки, версии, файлы, принадлежность к списку и регулярные выражения.

В данном примере сработает лишь первый условный оператор, который проверяет, что 5 > 1. Условные операторы в CMake работают в точности как в других языках программирования. Блоки команд elseif(5 LESS 1) и else() необязательны, а endif() обязательна и сигнализирует о завершении предыдущих проверок. Второе и третье условия ложны, так как 5 не может быть меньше или равняться одному.

# Напечатает "Of course, 5 > 1!":
if(5 GREATER 1) message("Of course, 5 > 1!")
elseif(5 LESS 1) message("Oh no, 5 < 1!")
else() message("Oh my god, 5 == 1!")
endif()

В приведённом примере устанавливается значение переменной VARIABLE в Airport, а затем четыре вложенные команды последовательно исполняются пока значение переменной VARIABLE будет равняться Airport. Циклы в CMake подобны циклам других языков программирования. Команда endwhile() сигнализирует о завершении списка вложенных в цикл команд. Последняя четвёртая команда set(VARIABLE "Police station") устанавливает значение проверяемой переменной в Police station, поэтому цикл сразу остановится, не дойдя до второй итерации.

# Напечатает в консоль три раза "VARIABLE is still 'Airport'":
set(VARIABLE Airport)
while(${VARIABLE} STREQUAL Airport) message("VARIABLE is still '${VARIABLE}'") message("VARIABLE is still '${VARIABLE}'") message("VARIABLE is still '${VARIABLE}'") set(VARIABLE "Police station")
endwhile()

Когда значений в списке не остаётся, то цикл завершает своё выполнение. Данный пример цикла foreach работает следующим образом: на каждой итерации данного цикла переменной VARIABLE присваивается следующее значение из списка Give me the sugar please!, а затем исполняется команда message(${VARIABLE}), которая выводит текущее значение переменной VARIABLE. Команда endforeach() сигнализирует о завершении списка вложенных в цикл команд.

# Напечатает "Give me the sugar please!" с новых строк:
foreach(VARIABLE Give me the sugar please!) message(${VARIABLE})
endforeach()

Первый цикл в данном примере на место списка генерирует целые числа от 0 до 10, второй цикл генерирует в диапазоне от 3 до 15, а третий цикл работает в сегменте от 50 до 90, но с шагом 10. Существуют ещё 3 формы записи цикла foreach.

# Напечатает "0 1 2 3 4 5 6 7 8 9 10" с новых строк:
foreach(VARIABLE RANGE 10) message(${VARIABLE})
endforeach() # Напечатает "3 4 5 6 7 8 9 10 11 12 13 14 15" с новых строк:
foreach(VARIABLE RANGE 3 15) message(${VARIABLE})
endforeach() # Напечатает "50 60 70 80 90" с новых строк:
foreach(VARIABLE RANGE 50 90 10) message(${VARIABLE})
endforeach()

Приведённый ниже пример демонстрирует использование функций и макросов: сначала определяются функция и макрос со своими собственными командами, а при их вызове их команды исполняются последовательно. Синтаксис CMake позволяет определять собственные команды, которые можно будет вызывать в точности как встроенные.

# Определение функции "print_numbers":
function(print_numbers NUM1 NUM2 NUM3) message(${NUM1} " " ${NUM2} " " ${NUM3})
endfunction() # Определение макроса "print_words":
macro(print_words WORD1 WORD2 WORD3) message(${WORD1} " " ${WORD2} " " ${WORD3})
endmacro() # Вызов функции "print_numbers", которая напечатает "12 89 225":
print_numbers(12 89 225) # Вызов макроса "print_words", который напечатает "Hey Hello Goodbye":
print_words(Hey Hello Goodbye)

Параметры видимы лишь определяемой функции, значит вне функции доступ к её параметрам мы получить не можем. Команда function первым аргументов принимает имя будущей функции, а остальные аргументы — это имена параметров, с которыми можно работать как с обычными переменными. Более того, все другие переменные, определяемые и переопределяемые внутри функции, видны лишь ей самой.

Более подробно о различиях макросов и функций Вы можете почитать здесь. Макросы аналогичны функциям за тем исключением, что они не имеют собственной области видимости: все переменные внутри макросов рассматриваются как глобальные.

Как отметили в комментариях, макросы в CMake подобны макросам в препроцессоре языка Си: если в тело макроса поместить команду return(), то произойдёт выход из вызвавшей функции (или из всего скрипта), что демонстрирует данный пример:

# Определить макрос, содержащий команду выхода:
macro(demonstrate_macro) return()
endmacro() # Определить функцию, вызывающую предыдущий макрос:
function(demonstrate_func) demonstrate_macro() message("The function was invoked!")
endfunction() # Напечатает "Something happened with the function!"
demonstrate_func()
message("Something happened with the function!")

В приведённом выше примере функция demonstrate_func не успеет напечатать сообщение The function was invoked!, так как прежде, на место вызова макроса demonstrate_macro будет подставлена и выполнена команда выхода.

На самом деле, все переменные по умолчанию считаются глобальными (доступ к ним есть везде), за исключением тех, которые были определены и переопределены в функциях. В предыдущем разделе Вы узнали о том, что некоторые конструкции в CMake могут определять собственные области видимости. Также имеются кэш-переменные, у которых своя собственная область видимости, но они применяются не столь часто.

PARENT_SCOPE). Как упомянули в комментариях, переменные можно определять в "родительской" области видимости с помощью команды set(VARIABLE ... Данный пример демонстрирует эту особенность:

# Функция, определяющая переменную "VARIABLE" со значением
# "In the parent scope..." в родительской области видимости:
function(demonstrate_variable) set(VARIABLE "In the parent scope..." PARENT_SCOPE)
endfunction() # Определить переменную "VARIABLE" в текущей области видимости:
demonstrate_variable() # Теперь возможно получить к переменной "VARIABLE" доступ:
message("'VARIABLE' is equal to: ${VARIABLE}")

Если из определения переменной VARIABLE убрать PARENT_SCOPE, то переменная будет доступна лишь функции demonstrate_variable, а в глобальной области видимости она примет пустое значение.

Следующая статья выйдет примерно через пару дней и будет вводить в использование системы сборки CMake. На этом синтаксис языка CMake заканчивается. До скорых встреч!

Показать больше

Похожие публикации

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»