Полное руководство по cmake часть первая

Полное руководство по CMake. Часть первая: Синтаксис +40

Из песочницы, C++, Системы сборки, C


Рекомендация: подборка платных и бесплатных курсов 3D-моделирования — https://katalog-kursov.ru/

Введение

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

Язык 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}. Если переменная ещё не определена и где-то потребовалось получить её значение, то данная переменная обратится в пустую строку. Пример:

# Определить переменную VARIABLE со значением "Mr. Thomas":
set(VARIABLE "Mr. Thomas")

# Напечает "His name is: Mr. Thomas":
message("His name is: " ${VARIABLE})

# Напечатает "'BINGO' is equal to: []", так как "BINGO" не определена:
message("'BINGO' is equal to: [${BINGO}]")

# Удалить переменную VARIABLE:
unset(VARIABLE)

Логические выражения

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

Условные операторы

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

# Напечатает "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() 

Циклы

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

# Напечатает в консоль три раза "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()

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

# Напечатает "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 могут определять собственные области видимости. На самом деле, все переменные по умолчанию считаются глобальными (доступ к ним есть везде), за исключением тех, которые были определены и переопределены в функциях. Также имеются кэш-переменные, у которых своя собственная область видимости, но они применяются не столь часто.

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

# Функция, определяющая переменную "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. До скорых встреч!

Введение

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

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

Запуск CMake

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

Команды

Команды в 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}. Если переменная ещё не определена и где-то потребовалось получить её значение, то данная переменная обратится в пустую строку. Пример:

# Определить переменную VARIABLE со значением "Mr. Thomas":
set(VARIABLE "Mr. Thomas")

# Напечает "His name is: Mr. Thomas":
message("His name is: " ${VARIABLE})

# Напечатает "'BINGO' is equal to: []", так как "BINGO" не определена:
message("'BINGO' is equal to: [${BINGO}]")

# Удалить переменную VARIABLE:
unset(VARIABLE)

Опции

CMake поддерживает задание опций, подлежащих модицификациям пользователей. Опции похожи на переменные и задаются командой option, принимающей всего три аргумента: имя переменной, строковое описание переменной и значение переменной по умолчанию (ON или OFF):

# Задать опцию `USE_ANOTHER_LIBRARY` с описанием
# "Do you want to use an another library?" и значением "OFF":
option(USE_ANOTHER_LIBRARY "Do you want to use an another library?" OFF)

Логические выражения

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

Условные операторы

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

# Напечатает "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() 

Циклы

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

# Напечатает в консоль три раза "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()

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

# Напечатает "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_parse_arguments позволяет производить разбор аргументов, переданных в функцию или макрос.

Данная команда принимает префикс, используемый в определении переменных (смотреть следующий абзац), список опций, используемых без последующих значений, список ключевых слов, после которых следует одно значение, список ключевых слов, после которых следуют множества значений и список всех значений, переданных в функцию или макрос.

Работа механизма разбора аргументов заключается в преобразовании полученных аргументов в значения переменных. Таким образом, рассмотренная команда для каждой опции и ключевого слова определяет собственную переменную вида <Prefix>_<OptionOrKeyword>, инкапсулирующую некоторое значение. Для опций — это булевы значения (истина — опция указана, иначе — ложь), а для ключевых слов — все переданные значения, расположенные после них.

Функция custom_function содержит вызов команды cmake_parse_arguments, а затем команды печати значений определённых переменных. Далее, функция вызывается с аргументами LOW NUMBER 30 COLORS red green blue, после чего производится печать на экран:

function(custom_function)
    # Вызвать механизм обработки аргументов для текущей функции:
    cmake_parse_arguments(CUSTOM_FUNCTION "LOW;HIGH" "NUMBER" "COLORS" ${ARGV})

    # Напечатает "'LOW' = [TRUE]":
    message("'LOW' = [${CUSTOM_FUNCTION_LOW}]")
    #Напечатает "'HIGH' = [FALSE]":
    message("'HIGH' = [${CUSTOM_FUNCTION_HIGH}]")
    # Напечатает "'NUMBER' = [30]":
    message("'NUMBER' = [${CUSTOM_FUNCTION_NUMBER}]")
    # Напечатает "'COLORS' = [red;green;blue]":
    message("'COLORS' = [${CUSTOM_FUNCTION_COLORS}]")
endfunction()

# Вызвать функцию "custom_function" с произвольными аргументами:
custom_function(LOW NUMBER 30 COLORS red green blue)

Области видимости

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

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

# Функция, определяющая переменную "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. До скорых встреч!

Время на прочтение
7 мин

Количество просмотров 485K

imageCMake — кроcсплатформенная утилита для автоматической сборки программы из исходного кода. При этом сама CMake непосредственно сборкой не занимается, а представляет из себя front-end. В качестве back-end`a могут выступать различные версии make и Ninja. Так же CMake позволяет создавать проекты для CodeBlocks, Eclipse, KDevelop3, MS VC++ и Xcode. Стоит отметить, что большинство проектов создаются не нативных, а всё с теми же back-end`ами.

Для того что бы собрать проект средствами CMake, необходимо в корне дерева исходников разместить файл CMakeLists.txt, хранящий правила и цели сборки, и произвести несколько простых шагов.
Разберёмся на примерах.

Пример 1. Hello, World:

Для начала напишем простейший хеловорлд и создадим структуру проекта:

main.cpp

#include <iostream>
int main(int argc, char** argv)
{
	std::cout << "Hello, World!" << std::endl;
	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.

add_executable(main main.cpp)		# Создает исполняемый файл с именем main
									# из исходника main.cpp

Синтаксис CMake похож на синтаксис bash, всё что после символа «#» является комментарием и обрабатываться программой не будет. CMake позволяет не засорять дерево исходных кодов временными файлами — очень просто и без лишних телодвижений сборка производится «Out-of-Source».

Создадим пустую директорию для временных файлов и перейдём туда.

fshp@panica-desktop:~$ mkdir tmp
fshp@panica-desktop:~$ cd tmp/
fshp@panica-desktop:~/tmp$

Теперь запустим команду cmake, передав ей в качестве параметра путь к папке с исходниками:

fshp@panica-desktop:~/tmp$ cmake ~/cmake/example_1/

— Build files have been written to: /home/fshp/tmp
fshp@panica-desktop:~/tmp$
fshp@panica-desktop:~/tmp$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
fshp@panica-desktop:~/tmp$

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

fshp@panica-desktop:~/tmp$ make
Scanning dependencies of target main
[100%] Building CXX object CMakeFiles/main.dir/main.cpp.o
Linking CXX executable main
[100%] Built target main
fshp@panica-desktop:~/tmp$ ./main
Hello, World!
fshp@panica-desktop:~/tmp$

Итак, наша программа собралась.
Папку tmp можно очищать\удалять без риска поломать исходники. Если CMakeLists.txt был изменен, то вызов make автоматически запустит cmake. Если исходники были перемещены, то нужно очистить временную директорию и запустить cmake вручную.

Пример 2. Библиотеки:

Если ваш проект содержит библиотеку, то CMake соберет ее без проблем.
Для этого усложним пример.

foo.cpp

#include <iostream>
void hello_world()
{
	std::cout << "Hello, World!" << std::endl;
}

main.cpp

#include "foo.h"
int main(int argc, char** argv)
{
	hello_world();
	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)	 # Проверка версии CMake.
										# Если версия установленой программы
										# старее указаной, произайдёт аварийный выход.

project(hello_world)			# Название проекта

set(SOURCE_EXE main.cpp)		# Установка переменной со списком исходников для исполняемого файла

set(SOURCE_LIB foo.cpp)			# Тоже самое, но для библиотеки

add_library(foo STATIC ${SOURCE_LIB})	# Создание статической библиотеки с именем foo

add_executable(main ${SOURCE_EXE})	# Создает исполняемый файл с именем main

target_link_libraries(main foo)		# Линковка программы с библиотекой

Переменные могут хранить списки значений, разделённых пробелами\табуляциями\переносами:

set(SOURCE main.cpp foo.cpp)
set(HEADER main.h
			foo.h)

Оба варианта правильные
Что бы получить значение переменной ипользуем конструкцию:

${var_name}

Итак, эта версия нашего проекта включает в себя одну статическую библиотеку, собираемую из исходников. Если заменить «STATIC» на «SHARED», то получим библиотеку динамическую. Если тип библиотеки не указать, по умолчанию она соберётся как статическая.
При линковке указываются все необходимые библиотеки:

target_link_libraries(main  foo
							ogg
							vorbis)

Как и при ручной компиляции, имена библиотек указываются без стандартного префикса «lib».
Итак, сборка библиотек с CMake не вызывает проблем, при этом тип библиотеки статическая\динамическая меняется лишь одним параметром.

Пример 3. Подпроекты:

Подпроекты очень удобны, если ваша программа разбита на несколько библиотек или же проект состоит из нескольких программ.
Каждый подпроект является по сути полноценным проектом и может использоваться самостоятельно.
Теперь у нас «foo» находится в субдирректории и там же находится CMakeLists.txt подпроекта.

CMakeLists.txt

cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.

project(hello_world)				# Название проекта

set(SOURCE_EXE main.cpp)			# Установка переменной со списком исходников

include_directories(foo)			# Расположение заголовочных файлов

add_executable(main ${SOURCE_EXE})	# Создает исполняемый файл с именем main

add_subdirectory(foo)				# Добавление подпроекта, указывается имя дирректории

target_link_libraries(main foo)		# Линковка программы с библиотекой

main.cpp

#include "foo.h"
int main(int argc, char** argv)
{
	hello_world();
	return 0;
}

foo/CMakeLists.txt

cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.

project(foo)				# Название проекта

set(SOURCE_LIB foo.cpp)		# Установка переменной со списком исходников

add_library(foo STATIC ${SOURCE_LIB})# Создание статической библиотеки

foo/foo.cpp

#include <iostream>
void hello_world()
{
	std::cout << "Hello, World!" << std::endl;
}

В файле подпроекта ничего нового для вас нет. А вот в основном файле новые команды:

include_directories(foo)

main.cpp мы не меняли, а foo.h перенесли. Команда указывает компилятору, где искать заголовочные файлы. Может быть вызвана несколько раз. Хидеры будут искаться во всех указаных директориях.

add_subdirectory(foo)

Указываем директорию с подпроектом, который будет собран как самостоятельный.
Вывод: проекты на CMake можно объединять в довольно сложные иерархические структуры, причем каждый подпроект в реальности является самостоятельным проектом, который в свою очередь может сам состоять из подпроектов. Это позволяет легко разбить вашу программу на необходимое количество отдельных модулей. Примером такого подхода может служить KDE.

Пример 4. Поиск библиотек:

CMake обладает достаточно развитыми средствами поиска установленых библиотек, правда они не встроеные, а реализованы в виде отдельных модулей. В стандартной поставке довольно много модулей, но некоторые проекты (например Ogre) поставляют свои. Они позволяют системе автоматически определить наличие необходимых для линковки проекта библиотек.
На debian модули располагаются в /usr/share/cmake-2.8/Modules/ (у вас версия может отличаться). За поиск библиотек отвечают модули, называющиеся FindNAME.cmake, где NAME — имя библиотеки.

find_package(SDL REQUIRED)
if(NOT SDL_FOUND)
	message(SEND_ERROR "Failed to find SDL")
	return()
else()
	include_directories(${SDL_INCLUDE_DIR})
endif()
##########################################################
find_package(LibXml2 REQUIRED)
if(NOT LIBXML2_FOUND)
	message(SEND_ERROR "Failed to find LibXml2")
	return()
else()
	include_directories(${LIBXML2_INCLUDE_DIR})
endif()
##########################################################
find_package(Boost COMPONENTS thread-mt REQUIRED)
if(NOT Boost_FOUND)
	message(SEND_ERROR "Failed to find boost::thread-mt.")
	return()
else()
	include_directories(${Boost_INCLUDE_DIRS})
endif()
##########################################################
target_link_libraries(${TARGET} 
								${SDL_LIBRARY}
								${LIBXML2_LIBRARIES}
								${Boost_LIBRARIES})

Думаю, смысл должен быть понятен. Первый и второй блок — поиск библиотеки. Если в системе её нет, выведется сообщение об ошибке и завершается выполнение cmake. Третий блок похож, только он ищет не целый пакет библиотек, а лишь необходимый компонент. Каждый такой автоматизированый поиск определяет после выполнения как минимум 3 переменные:
SDL_FOUND, LIBXML2_FOUND, Boost_FOUND — признак присутствия бибилиотеки;
SDL_LIBRARY, LIBXML2_LIBRARIES, Boost_LIBRARIES — имена библиотек для линковки;
SDL_INCLUDE_DIR, LIBXML2_INCLUDE_DIR, Boost_INCLUDE_DIRS — пути к заголовочным файлам.
Если с первыми более или менее понятно, то вторые и третьи мне доставили много хлопот — половина имеет имена в единственном числе, половина — во множественном. Но оказалось, это легко отследить. В каждом модуле вначале есть коментарии, там описаны определяемые переменные. Посмотрите, например, /usr/share/cmake-2.8/Modules/FindLibXml2.cmake
Как видите, CMake способен сам определить наличие и местоположение необходимых библиотек и заголовочных файлов. В принципе, это должна уметь любая система автоматической сборки, иначе смысл в ней?

Пример 5. Внешние библиотеки и объектные файлы:

Если вы пишите для «дяди», а злой «дядя» любит самописные библиотеки и делиться исходниками не желает, поэтому присылает готовую библиотеку, то вы по адресу.
Объектные файлы в CMake стоят на ряду с исходниками — достаточно включить объектник в список файлов для компиляции.
С библиотеками потуже. Как известно, статическая библиотека это не что иное, как ar-архив, внутри которого лежат обычные объектники, никак не связаные между собой. Вы, наверное, уже догадались, как я поступал сначала. Да, просто потрошил библиотеку. Но потом был найден способ поэлегантнее:

add_library(netutil STATIC IMPORTED)
set_property(TARGET netutil PROPERTY
             IMPORTED_LOCATION Binary/game_client/libnetutil.a)

Слово «IMPORTED», указывает, что библиотека берётся извне.
В CMake каждая цель имеет параметры, а set_property позволяет их изменять.
Линкуется такая библиотека стандартно:

target_link_libraries(${TARGET} netutil)

Для динамических библиотек все аналогично, только тип «SHARED», расширение — «.so».
К сожалению, поддержка несистемных библиотек реализована немного костыльно. Возможно, я просто не знаю правильного варианта, поэтому буду рад, если «ткнете мордочкой». С другой стороны это не навороченый экзоскелет с системой жизнеобеспечения, а простейший костыль из двух строк.

Генераторы:

Как было сказано в начале, CMake умеет генерировать множество различных видов проектов. Это удобно и позволяет использовать CMake для практически любой популярной IDE.
Если запустить cmake без параметров, в конце будут описаны доступные генераторы. Пользоваться так:

fshp@panica-desktop:~/tmp$ cmake ~/cmake/example_3/ -G «KDevelop3 — Unix Makefiles»

Заключение:

Это не перевод мануала, а результат использования CMake в одном коммерческом проекте. Буду рад, если статья поможет хотя бы одному человеку — на русском языке подобной документации довольно мало.

Чем понравился CMake лично мне:

  • один проект — один файл. Не нужно хранить кучу скриптов настройки, сборки и прочего хлама;
  • Скорость работы в сравнении с autotools;
  • простой и понятный синтаксис, конечно с элегантностью питона не потягаться, но и не брейнфак, в конце концов.;
  • является front-end`ом для множества IDE;
  • отображение прогресса — довольно удобно;
  • цветной вывод — в серые будни немного краски не помешает;

Для Sublime Text есть плагин, добавляющий подсветку синтаксиса CMake, он так и называется — «CMake».
Примеры

Introduction

CMake is a very popular build configuration generator for Fortran/C/C++ programs. The true power of it lies in its basics which is hard to find in a coherent way on the internet. This post, CMake part 1, aims to neatly mention important concepts, syntax, and commands of CMake as a programming language. For each command a reference to the manual is given for more details. I focus on installation, defining variables, if-conditions, loops, functions, and so on. Here, I ignore the commands to create libraries and executables because they will be explained in the next post.

Installation

First check if you already have it installed. In a terminal/PowerShell run

If it is not there, you can download CMake for Windows, MacOS, and Linux from here and install it.

In Windows, you can also install it using choco, open a PowerShell as administrator and run

CMake is usually installed on Linux distros by default. If not, it is included in their package manager. For example, on Ubuntu, you can install it via

sudo apt-get install cmake

Examples structure

The examples in this post are run similar to practical applications. There is a project folder that contains CMakeLists.txt file and build folder.

--myProject
  |
  ----- CMakeLists.txt
  |
  |
  ------ build

The CMake script is written in CMakeLists.txt. CMake is run from within build folder by this command in a terminal:

or from anywhere in the file system:

 cmake -S path/to/myProject -B path/to/build

CMake automatically detects CMakeLists.txt and runs the commands in it.

Minimum requirement

CMake is an evolving language. New features are added to it every day. At the beginning of a CMake script, you can set the earliest CMake version that compiles your code correctly by,

cmake_minimum_required(VERSION 3.22)

Version 3.22 is the one I use for this post.

Project

Always set the name of the project in the script

project(LinearSystemSolver)

You can also set the project version and language: C, CXX (for C++), Fortran:

project(LinearSystemSolver VERSION 1.2.0 LANGUAGES CXX)

See the manual for project here.

A text starts with # considered as a comment and CMake ignores it.

# This is a comment
cmake_minimum_required(VERSION 3.2)
project(LinearSystemSolver) # Another comment

A multi-line comment is created with #[[…]]:

#[[ this is 
a long comment
for this code.]]

Script language

CMake is a dynamically-typed language like Python. CMake script is composed of commands. Each command ends with parentheses with some arguments and keywords.

  • Commands are case insensitive,
  • variables are case sensitive,
  • Keywords are always written in upper case.

This two lines are the same:

command1(KEYWORD1 arg1)
COMMAND1(KEYWORD1 arg1)

Message

message writes its parameter on screen like print() in Python:

message("this is a message")

Message function has many modes for showing warnings, errors, and so on. Usually, mode STATUS is used to inform users that a step is started or finished:

message(STATUS "The compilation is finished.")

Note that STATUS is a CMake keyword and needs to be upper case.

Another important usage for message is debugging a CMake script. You can write the value of any suspicious variable on the screen.

See the manual for message here.

Normal variable

In CMake, there is no data type like char, integer, float, or class. All variables are strings (or text). A variable is defined or changed with set command:

set(x hello)
set(y "hello")

Both x and y are set to hello. hello is a constant or value.

I prefer quotation for constants because

  • you emphasize that it is a constant
  • white space handled correctly

Without quotations, spaces imply a list variable, explained in list section.

The value of a variable is accessed with ${variable}:

set(a "hi")
set(b ${a}) 
# b is "hi"

${a} is expanded to hi.

See the manual for set() here.

Variable operations

We can merge variables to create a new one:

set(myPath "/home")
set(myDir "projectA")
set(myFile "${myPath}/${myDir}/main.cpp")
Message(myFile) # will print /home/projectA/main.cpp

You have to dereference a variable to use its content (if-condition is an exception, but ignore it now.):

set(file1 "sample.h")
set(header file1) # This is a Mistake!

here header is set to text “file1”. We wanted it to be text “sample.h”, so the code is fixed like:

set(file1 "sample.h")
set(header ${file1}) 

Derefrencing can happen recursively:

set(a "Final")
set(b a)
message( b ) # shows b
message( ${b} ) # shows a
message( ${${b}} ) # shows Final

This example shows:

  • CMake is funny,
  • A variable stores only a string,
  • Everything is a constant unless dereferenced to be treated as a variable (there are exceptions like if-condtion),
  • The concept of reference-to-reference or pointer-to-pointer,
  • Why I like to set constants in quotations.

You may use tricks like this to have class-like data set:

set(folder1-header "/folder1/a.h")
set(folder1-source "/folder1/a.cpp")

set(folder2-header "/folder2/b.h")
set(folder2-source "/folder2/b.cpp")

set(folder folder1)
# comment the above line
# and uncomment the below line, see the message
#set(folder folder2)

message(${${folder}-header})
message(${${folder}-source})

Unset variable

A variable can be cleared/deleted:

unset(a)
set(a) # set without value

We can check if a variable is set by

if (a)
    # do something
endif()

if-condition

A condition is written similiar to other programming languages:

if (<conditon1>)
    # do something
elseif(<condition2>)
    # do another thing
else()
    # do default action
endif()

Some constant strings are translated to true/false:

  • True constants: TRUE, 1, Yes, Y, ON, …
  • False constants: FALSE, 0, NO, N, OFF, …

A variable can be put as a condition to test if it is set:

unset(a) # emphasizing a is not set
set(b "sample.cpp")

if(a)
    # the code here not run
endif()

if (b)
    # the code here is run
endif()

Note: In the above example, if-condition checks whether its argument is a set variable, DO NOT use ${}. Otherwise, if-condition checks whether the value of the variable is a variable.

Various compounds can be made for conditions. They can be related with AND and OR, negated with NOT, and separated by parentheses. The most useful operator is STREQUAL to check if two variables are equal:

set(a "book")
set(b "book")
if (a STREQUAL b) # true condition
    message("they are equal.") 
endif()

You could also write if (${a} STREQUAL ${b}), but I prefer if (a STREQUAL b) because in this way we can have this rule:

Always use the name of a variable without ${} in conditions.

We can compare a variable with a constant:

set(a "book")
if (a STREQUAL "book") # a true condition
    message("they are equal.") 
endif()

Every variable is a string but CMake can compare numbers with LESS, EQUAL, GREATER, and so forth.

if(1 EQUAL 01.0)  # a true condition
	message("1 equal to 01.0") 
endif()

See the manual for if() here.

List

A list is defined with set as well, with space separation of items:

set(myFiles a.cpp b.cpp a.h)

Or in quotations with ; separation (which I prefer):

set(myFiles1 "sample1.h;sample2.h")

set(main "x.cpp")
set(myFiles2 "sample1.h;sample2.h;${main}")

CMake comes with a variety of keywords to modify a list such as APPEND, POP_BACK, REMOVE_ITEM. For example, the previous example could be rewritten as:

set(myFiles1 "sample1.h;sample2.h")

set(main "x.cpp")
set(myFiles2 ${myFiles1})
list(APPEND myFiles2 ${main})
# So now myfiles2="sample1.h;sample2.h;x.cpp"

See the manual for list() here.

Loop

A numerical loop is defined as

foreach( i RANGE 1 5)
	message(${i})
endforeach()
# It will print 1 2 3 4 5

Note that the end of the range is included in contrast to Python.

A list can be iterated as

set(names "Jack;Kate;Sara")

foreach(name IN LISTS names)
	message(${name})
endforeach()
# It will print Jack Kate Sara

See the manual for foreach here.

Cached Variable

The state of normal variables is lost after a cmake run. To overcome this, we have cached variables which are written in CMakeCache.txt file. Whenever we run cmake command they are loaded from that file. These variables aim to store user preferences on disk. Some examples of user preferences are:

  • installation directory,
  • build type (release or debug),
  • special compiler flags,
  • option to install some libraries.

They are created and set firstever time that cmake is called. They are defined with this template:

set(<variable> <value>  CACHE <type> <docstring>)

An example is:

set(libAPath "/home/libA" CACHE PATH "info about libAPath for user")

After the first time, anymore cmake is called, the line above will be ignored because libAPath is already in the cache.

The idea behind it is that /home/libA is the default value, and a user is responsible for changing it to something that suits their need. They can use, cmake -D flag, ccmake command or cmake-gui to modify cached variables.

<type> tells ccmake and cmake-gui what we are expecting to get from the user. Types are:

  • FILEPATH: GUI shows a file selector dialog.
  • PATH: GUI shows a directory selector dialog.
  • STRING: GUI shows a textbox.
  • BOOL: GUI shows a checkbox.
  • INTERNAL: Hidden from GUI, for the developer.

A user can run the cmake-gui from a terminal with

cmake-gui -S pathToSourceFolder -B pathToBuildFolder 

You can also set a cached variable when running cmake with -D flag:

cmake -D <var>:<type>=<value>

See this example:

cmake -D compilesModule1:BOOL=ON -S path/to/source -B path/to/build

Every time you set a variable with -D, it overwrites the cached value.

Besides set, another way to create a boolean (ON/OFF) cache variable in a script is option:

option(hasModule1 "info about this option" ON)

which is the same as this:

set(hasModule1 "ON" CACHE BOOL "info about this option")

While it is not recommended, we can also overwrite a cached variable from the script every time cmake is run using FORCE:

set(libAPath "/home/libA" CACHE PATH "some info" FORCE)

Sometimes we want to store some variables on disk as a developer, but we don’t want them to be changed by the user, then we write

set(libAPath "/home/libA" CACHE INTERNAL "some info")

The INTERNAL variables are global variables accessible in every scope. FORCE is not necessary for them as they are always forced. Therefore, to work with them, we can write this:

if (NOT libAPath) # if it is not in the cache file
    # set the default value
    set(libAPath "/home/libA" CACHE INTERNAL "some info")
endif()
# work with libAPath

Never choose the same name for a cached and a normal variable unless you know what you are doing.

See the manual for set(), option(), and flags of cmake executable.

String

With string command you can find-and-replace, manipulate, compare strings. You can even work with JSON strings. See below examples:

string(TOUPPER "hello" a) # a is set to "HELLO"
string(LENGTH "hello" b) # b is "5" 
string(SUBSTRING "hello" 2 3 c) # c is "llo"

See the manual for string here.

Math

A math equation is solved with this template:

math(EXPR output_variable math_expression)

for example

math(EXPR x "5*(1+1-1)/5") # x will be 1

See the manual for math here.

File

With File command you can

  • read and write files,
  • perform file system actions such as copy, remove, and rename files,
  • upload or download files
  • create or extract archives (zip, 7zip, …) and many more actions.

The below keywords are common to get the list of files in the project:

  • GLOB for getting the list of files in the directory of the current CMakeLists.txt,
  • GLOB_RECURSE for getting the list of files in the current directory and all its subdirectories.

You have to set globbing expressions to find desired files. The example below finds the list of files with .h and .cpp in sub1 directory. The results are stored in myfiles variable:

file(GLOB_RECURSE myfiles LIST_DIRECTORIES false ${PROJECT_SOURCE_DIR}/sub1/*.cpp ${PROJECT_SOURCE_DIR}/sub1/*.h)

The last two terms are globbing expressions, you can add as many globbing expressions as you like.

Note that GLOB is not recommended for collecting source files. For more info, see the manual for file() here.

Function

A function in CMake is defined as

function(NameOfFunction arg1 arg2)
    # body of function
endfunction()

A function that prints its arguments

function(print a b)
    message("${a} ${b}")
endfunction()

print("March" "May")

The arguments are stored in ARGV list, so for a function which accepts different number of arguments, we write:

function(print)
    foreach(arg IN LISTS ARGV)
       message(${arg})
    endforeach()
endfunction()

print("March" "May" "June")

The parameters set in a function are local to the scope of function and not accessible outside:

function(doSomething)
    set(name "Sara")
endfunction()

doSomething()

if (NOT name)
    message("name is not set!") # This line is reached
endif()

However, a function has access to copy of variables in the scope it is called i.e. a function has access to a copy of variables in its parent scope:

set(name "Sara")

function(doSomething)
    message(${name})
endfunction()

doSomething() # prints Sara

We say it has access to a copy of the parent scope because if you change a parent variable in the function, it will not change it in the parent scope:

set(name "Sara")

function(doSomething)
    set(name "Jack")
    message(${name})
endfunction()

doSomething() # Jack
message(${name}) # Sara

If you are willing to do so, you have to set the variable again with PARENT_SCOPE:

set(name "Sara")

function(doSomething)
    # for local scope
    set(name "Jack")
    # for parent scope
    set(name "Jack" PARENT_SCOPE)
    message(${name})
endfunction()

doSomething() # Jack
message(${name}) # Jack

Now we can define a function that returns a variable to its parent

function(findName outFullName first last)
    set(${outFullName} "${first} ${last}" PARENT_SCOPE)
endfunction()

findName(fullName "Steve" "Jobs")

message(${fullName})

A function can be terminated with return() command.

See the manual for function here.

Macro

Macro is defined the same as a function. However, while a function hides its content from a caller scope, macro pastes its content at the caller’s place. Therefore, the variables and commands defined in the macro will be exposed to the caller scope.

macro(setName)
    set(name "Sara")
endmacro()

setName()

message(${name}) 

In function ARGV contains a list of arguments, but in macro ${ARGV} does so.

Macro vs Function

Generally, a function is the first pick as it leads toward clean code and less bug. A macro can be used for wrapping commands that make some changes like setting some variables in the scope they are called.

Manual for macro is here.

Special CMake variables

Any variable that starts with CMAKE_ is a reserved variable for CMake. It will populate them when a script is run.

add_subdirectory

In any subdirectory of a project, you can have a CMakeLists.txt file. Imagine we have a file system like this

-- myProject
    |
    ----- build
    |
    ----- library1
    |        |
    |        ---- CMakeLists.txt
    |
    ------ CMakeLists.txt

In library1/CMakelists.txt we have

message("Hello from library1")
message(${CMAKE_CURRENT_SOURCE_DIR})
message(${PROJECT_SOURCE_DIR})

And in myProject/CMakeLists.txt we have this line

project(myProject)
message("Hello from myProject")
message(${CMAKE_CURRENT_SOURCE_DIR})
message(${PROJECT_SOURCE_DIR})
add_subdirectory("library1")

Running cmake, it will set CMAKE_CURRENT_SOURCE_DIR variable to the myProject path. When it reaches the add_subdirectory line, it will jump into library1/CMakeLists.txt, set CMAKE_CURRENT_SOURCE_DIR to library1 path, and runs the command there. Afterward, CMake comes back to the project scope and sets CMAKE_CURRENT_SOURCE_DIR to myProject path. The variables in subdirectory scope are private and not visible to the project scope.

While CMAKE_CURRENT_SOURCE_DIR is dependent on the location of focused CMakeLists.txt, PROJECT_SOURCE_DIR is always set to the top-level folder containing CMakeLists.txt which has project() command in it.

A subdirectory usually contains some source files that need to be compiled.

Include

We can include CMake scripts from another file by include. No new scope is created as if the content of the file is pasted at the include() line.

Let’s create a file in the project folder, sample.txt which contains

message("Hello from sample.txt")

In CMakeLists.txt file include it as:

It will write the message on the screen.

If the file has the extension of .cmake, it is called a module and we don’t need to mention its extension. It is common to add the folder containing modules to CMAKE_MODULE_PATH list variable. So, include command automatically search those folders for the mentioned module.

For example, let’s create mymodules folder in the project directory. In that directory we put sample.cmake module contains

message("Hello from sample module")

So the file system will look like this

--myProject
  |
  ----- build
  |
  ----- mymodules
  |       |
  |       --- sample.cmake
  |
  ----- CMakeLists.txt

Now in CMakeLists.txt we can include the module as

project(myProject)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/mymodules")
include(sample) 

Running cmake you will see the message.

The difference between include and add_subdirectory is:

  • include is used to add modules that may contain functions, macros, instruction to install packages and so forth.

  • add_subdirectory is used to add folders that contain source code to be compiled.

See manual for include() here.

More on CMake

The part 2 and 3 of this series are

  • Build and install with CMake,
  • Create a CMake config file for find_package().

Latest Posts

  • A C++ MPI code for 2D unstructured halo exchange
  • Essential bash customizations: prompt, ls, aliases, and history date
  • Script to copy a directory path in memory in Bash terminal
  • What is the difference between .bashrc, .bash_profile, and .profile?
  • A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks

Comments

3 comments

Введение

CMake означает кроссплатформенную систему Make. CMake используется для управления процессом компиляции программного обеспечения с помощью простых файлов конфигурации, независимых от платформы и компилятора. CMake создает собственные make-файлы и рабочие пространства, которые можно использовать в выбранной вами среде компилятора.

Автор: Энди Седильник, Билл Хоффман, Брэд Кинг, Кен Мартин, Александр Нойндорф.

Год: 2000

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

CMake или Make

Make (или, скорее, Makefile) — это система сборки, которая управляет компилятором и другими инструментами сборки для создания вашего кода.

CMake — это генератор систем сборки, он может создавать файлы Makefile. Поэтому, если у вас есть проект, независимый от платформы, CMake — это способ сделать его независимым от системы.

Вот преимущества CMake перед Make:

  1. Язык, используемый для написания файлов CMakeLists.txt, удобочитаем и понятен.
  2. Кроссплатформенное обнаружение системных библиотек/функций/заголовков/флагов.
  3. Легче компилировать ваши файлы в общую библиотеку независимо от платформы и в целом проще в использовании, чем Make.
  4. CMake не только полагается на «Make» для сборки проекта, но и поддерживает несколько генераторов, таких как Xcode, Eclipse, Visual Studio и т. д.

Чтобы упростить задачу, вы можете выполнить большинство функций, доступных в CMake, с помощью Make, но с ДОПОЛНИТЕЛЬНЫМИ УСИЛИЯМИ.

CMakeLists.txt и процесс компиляции

CMake задуман как кроссплатформенный диспетчер процессов сборки, поэтому он определяет собственный язык сценариев с определенным синтаксисом и встроенными функциями. Язык CMake записывается либо в CMakeLists.txt, либо файл заканчивается файлом ‹project_name›.cmake.

Процесс компиляции в CMake состоит из двух этапов:
1. Конфигурация
2. Генерация

Команды конфигурации и генерации (сборки) указываются в одном месте и полностью обрабатываются CMake.

Конфигурация: здесь выполняется сценарий CMakeLists.txt. Этот скрипт отвечает за определение целей. Здесь target может быть исполняемым файлом, библиотекой или каким-либо другим выходом конвейера сборки ( конвейер означает файл, который может помочь компилятору искать исполняемые файлы/библиотеки для ссылки на этот проект, который может присутствовать в системной библиотеке, пока вы пытаетесь для кросс-компиляции это будет видно).

Если шаг настройки завершится успешно — это означает, что CMakeLists.txt завершен без ошибок — CMake сгенерирует конвейер сборки, используя цели, определенные сценарием ( CMakeLists.txt ). Тип создаваемого конвейера сборки зависит от типа используемого генератора.
CmakeCache.txt создается таким образом, что он может помочь сократить время создания конвейера в будущем.

Генерация. Этот этап предназначен для создания исполняемого файла в зависимости от целей, созданных на этапе конфигурации.

Вышеупомянутые детали могут быть визуализированы из рисунка ниже.

Иерархия файлов для CMake

Мы попробуем собрать cmakeSampleProject, который пытается выполнять сложение и умножение (определяемые пользователем функции).

Примечание:
1. Наличие файла сценария CmakeLists.txt в каждой папке обязательно.
2. →: представлять папки
3. ->: представлять файлы

Команды для настройки и сборки

Теперь давайте посмотрим, как создать и запустить исполняемый файл для cmakeSampleProject.

$ cmake -H. -Bbuild
Создает файлы конфигурации CMake внутри папки build.

$ cmake — build build — -j3
Создаст выходной файл SampleProjectOutput в папке сборки.

$ ./build/SampleProjectOutput
Hello World
Для запуска исполняемого файла.

Переменные CMake

Вот некоторые глобальные переменные CMake, которые вы должны знать

  1. CMAKE_BINARY_DIR: если вы создаете исходный код, это то же самое, что и CMAKE_SOURCE_DIR, в противном случае это каталог верхнего уровня вашего дерева сборки.
  2. CMAKE_SOURCE_DIR: это каталог, из которого был запущен CMake, т. е. исходный каталог верхнего уровня.
  3. EXECUTABLE_OUTPUT_PATH: установите эту переменную, чтобы указать общее место, куда CMake должен помещать все исполняемые файлы (вместо CMAKE_CURRENT_BINARY_DIR).
    Пример: SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
  4. LIBRARY_OUTPUT_PATH: установите эту переменную, чтобы указать общее место, куда CMake должен поместить все библиотеки (вместо CMAKE_CURRENT_BINARY_DIR).
    Пример: SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
  5. PROJECT_NAME: имя проекта, заданное командой PROJECT().
  6. PROJECT_SOURCE_DIR: содержит полный путь к корню исходного каталога вашего проекта, т. е. к ближайшему каталогу, где CMakeLists.txt содержит команду PROJECT().

Команды Cmake

Это некоторые наиболее часто используемые команды CMake,

  1. cmake_minimum_required (): эта команда гарантирует, что для процесса сборки требуется минимальная версия CMake.
    В случае сбоя команды возникает исключение.
    Пример: cmake_minimum_required (ВЕРСИЯ 2.6)
  2. project (): эта команда используется для присвоения имени проекту, над которым вы работаете.
    Пример: проект (учебник)
  3. add_executable(): здесь вы говорите CMake, чтобы начать процесс компиляции.
    В приведенном ниже примере мы говорим о компиляции с помощью test.cpp.
    Пример: add_executable (Учебник tutorial.cpp)
  4. configure_file (): копирует файл ‹входной› в файл ‹выходной› и заменяет значения переменных, указанные в содержимом файла.
    синтаксис: configure_file(‹input› ‹output›)
    Пример: configure_file («${PROJECT_SOURCE_DIR}/TutorialConfig.h.in» «${PROJECT_BINARY_DIR}/TutorialConfig.h»)
  5. option (): предоставляет параметр, который пользователь может выбрать по желанию.
    синтаксис: option(‹option_variable› «строка справки, описывающая параметр» [начальное значение])
    В приведенном ниже например, мы говорим о компиляции с помощью test.cpp.
    Ex: опция (USE_MYMATH «Использовать математическую реализацию, предоставленную учебником» ON)
  6. include_directories(): загружает и запускает CMake с файлами, которые требуется включить.
    Пример: include_directories («${PROJECT_SOURCE_DIR}/MathFunctions»).
  7. add_subdirectory(): добавляет запрошенный подкаталог в сборку.
    Пример: add_subdirectory (MathFunctions).
  8. set (): используется для установки заданного значения переменной CMake/cache/environment.
    Пример: set (Tutorial_VERSION_MAJOR 1)
  9. target_link_libraries (): связывает библиотеки с исполняемым файлом.
    Пример: target_link_libraries (учебник ${EXTRA_LIBS})
  10. check_function_exists (): используется для проверки существования системной функции, если она присутствует, можно установить макрос.
    Пример: check_function_exists (printf HAVE_PRINTF)
  11. install (): указывает правила, которым необходимо следовать во время установки, может быть выполнено извне с помощью команды $ make install

Ex:

установить (TARGETS Tutorial DESTINATION bin)

установить (ФАЙЛЫ «${PROJECT_BINARY_DIR}/TutorialConfig.h» НАЗНАЧЕНИЕ включают)

В приведенном выше примере TARGETS используется для исполняемых файлов, а для библиотечных файлов используется FILES.

Параметры CMake:

Вот некоторые варианты, используемые наиболее часто,

  1. — построить ‹dir›: построить бинарное дерево проекта, сгенерированное CMake.
  2. -i: запуск в режиме мастера, режим мастера запускает CMake в интерактивном режиме без графического интерфейса.
  3. -D ‹var›:‹type›=‹value›: создать запись кэша cmake. При первом запуске cmake в пустом дереве сборки создается кэш CMake.
    Он также используется для перезаписи значений переменных на этапе настройки.
  4. -H: распечатать информацию об использовании и выйти.
  5. -B: указывает каталог сборки.

Кросс-компиляция с помощью CMake:

Для кросс-компиляции требуется дополнительный файл, который указывает CMake перезаписать уже определенные переменные CMake. Кроме того, вам необходимо изменить переменные среды в соответствии с путем к ‹целевому каталогу цепочки инструментов›.

Пример: $ cmake -H. -Bcross_build -DCMAKE_CXX_FLAGS=»-g -std=c++11 -Wall» -DCMAKE_TOOLCHAIN_FILE=./sample_cross_compile_toolchain_file.cmake -DCMAKE_BUILD_TYPE=Debug

В приведенном выше примере мы перезаписываем определенный «CMAKE_TOOLCHAIN_FILE» на «./sample_cross_compile_toolchain_file.cmake», используя параметр «-D».

Практические примеры:

Эта ссылка ведет к учебнику Git Hub CMake, следуйте инструкциям, приведенным в разделе «Практические примеры» на этой вики-странице.

Ссылки:

Понравилась статья? Поделить с друзьями:
  • Soehnle весы напольные инструкция по применению на русском языке
  • Скачать бесплатно руководство по ремонту для рав 4
  • Reed diffuser как пользоваться на русском инструкция
  • Пролит супер септо инструкция по применению цена отзывы аналоги
  • Спецслужбы в руководстве россии