В предыдущих главах было показано, как определять основные цели и создавать результаты сборки. Само по себе это уже полезно, но CMake поставляется с целым рядом других функций, которые обеспечивают большую гибкость и удобство. В этой главе мы рассмотрим одну из наиболее фундаментальных частей CMake, а именно использование переменных.
6.1. Основные понятия
Как и в любом другом машинном языке, переменные являются краеугольным камнем для выполнения задач в CMake. Самый простой способ определения переменной - это команда set(). Обычная переменная может быть определена в файле CMakeLists.txt следующим образом:
Имя переменной, varName, может содержать буквы, цифры и символы подчеркивания, причем буквы учитываются в регистре. Имя также может содержать символы ./-+, но они редко встречаются на практике. Другие символы также возможны через косвенные средства, но, опять же, они не встречаются в обычном использовании.
В CMake переменная имеет определенную область видимости, подобно тому, как в других языках область видимости переменных ограничена определенной функцией, файлом и т. д. Переменная не может быть прочитана или изменена за пределами своей области видимости. По сравнению с другими языками, область видимости переменных в CMake немного более гибкая, но пока считайте, что область видимости переменной - это файл, в котором она определена. В разделе 5.4, «Блоки области видимости», рассматривается, как определить локальную область видимости и передать информацию обратно в окружающие области видимости. В главе 7 «Использование подкаталогов» и главе 8 «Функции и макросы» описаны другие ситуации, в которых возникают локальные области видимости.
CMake рассматривает все переменные как строки. В различных контекстах переменные могут интерпретироваться как другой тип, но в конечном итоге это просто строки. При задании значения переменной CMake не требует, чтобы эти значения были заключены в кавычки, если только значение не содержит пробелов. Если задано несколько значений, они будут объединены вместе с точкой с запятой, отделяющей каждое значение - результирующая строка представляет собой то, что в CMake называется списком. Следующие примеры демонстрируют это:
Значение переменной можно получить с помощью конструкции ${myVar}, которая может быть использована везде, где ожидается строка или переменная. CMake отличается особой гибкостью, поскольку эту форму можно использовать рекурсивно или указать имя другой переменной, которую нужно установить. Кроме того, CMake не требует, чтобы переменные были определены перед их использованием. Использование неопределенной переменной просто приводит к подстановке пустой строки, подобно тому, как ведут себя скрипты оболочки Unix. По умолчанию предупреждение об использовании неопределенной переменной не выдается, но для включения таких предупреждений в команде cmake можно использовать опцию --warn-uninitialized. Однако имейте в виду, что такое использование очень распространено и не обязательно является симптомом проблемы, поэтому польза от этой опции сомнительна.
Содержимое строк не ограничиваются одной строкой, они могут содержать встроенные символы перевода строки. Они также могут содержать кавычки, которые необходимо экранировать обратными слэшами.
Если вы используете CMake 3.0 или более позднюю версию, альтернативой кавычкам может быть использование синтаксиса скобок, вдохновленного языком lua, где начало содержимого обозначается как [=[, а конец - ]=]. Между квадратными скобками может находиться любое количество символов =, в том числе ни одного, но в начале и в конце должно быть использовано одинаковое количество символов =. Если за открывающими скобками сразу следует символ новой строки, то первая строка игнорируется, а последующие - нет. Кроме того, никакие дальнейшие преобразования содержимого скобок не выполняются (т. е. нет подстановки переменных или экранирования).
Как видно из приведенного выше примера, синтаксис скобок особенно хорошо подходит для определения такого содержимого, как скрипты оболочки Unix. Такое содержимое использует синтаксис ${...} для своих нужд и часто содержит кавычки, но использование синтаксиса скобок означает, что эти вещи не нужно экранировать, в отличие от традиционного стиля определения содержимого CMake с помощью кавычек. Гибкость использования любого количества символов = между маркерами [ и ] также означает, что встроенные квадратные скобки не будут неправильно истолкованы. В главе 20 «Работа с файлами» приведены дополнительные примеры, подчеркивающие ситуации, в которых синтаксис скобок может быть лучшей альтернативой. Переменная может быть снята либо вызовом unset(), либо вызовом set() без значения именованной переменной. Следующие команды эквивалентны и не вызывают ошибок или предупреждений, если myVar еще не существует:
В дополнение к переменным, определяемым проектом для собственного использования, на поведение многих команд CMake может влиять значение определенных переменных в момент вызова команды. Это общий паттерн, используемый CMake для настройки поведения или изменения значений по умолчанию, чтобы не повторять их каждый раз при вызовах команд, определения цели и т. д. В справочной документации CMake для каждой команды обычно перечислены все переменные, которые могут повлиять на поведение этой команды. В последующих главах этой книги мы также рассмотрим ряд полезных переменных и то, как они влияют на сборку или предоставляют информацию о ней.
6.2. Переменные среды.
CMake также позволяет получать и устанавливать значения переменных окружения, используя модифицированную форму обозначения переменных CMake. Значение переменной окружения можно получить с помощью специальной формы $ENV{varName}, которая может быть использована везде, где может быть использована обычная форма ${varName}. Установка переменной окружения может быть выполнена аналогично установке переменной CMake, только вместо varName в качестве устанавливаемой переменной используется ENV{varName}. Например:
Однако обратите внимание, что подобная установка переменной окружения влияет только на текущий запущенный экземпляр CMake. Как только запуск CMake будет завершен, изменение переменной окружения будет потеряно. В частности, изменение переменной окружения не будет видно во время сборки. Поэтому установка переменных окружения в файле CMakeLists.txt редко бывает полезной.
6.3. Переменные кэша.
В дополнение к обычным переменным, о которых говорилось выше, CMake также поддерживает кэш-переменные. В отличие от обычных переменных, время жизни которых ограничено обработкой файла CMakeLists.txt, кэш-переменные хранятся в специальном файле CMakeCache.txt в каталоге сборки и сохраняются между запусками CMake. Установленные однажды, кэш-переменные остаются установленными до тех пор, пока что-то явно не удалит их из кэша. Значение кэш-переменной извлекается точно так же, как и обычной переменной (т. е. с помощью формы ${myVar}), но команда set() отличается, когда используется для установки кэш-переменной:
При наличии ключевого слова CACHE команда set() будет применяться не к обычной переменной, а к кэш-переменной с именем varName. Кэш-переменные содержат больше информации, чем обычные переменные, включая тип и строку документации. Оба эти значения должны быть указаны при установке кэш-переменной, хотя строка документации может быть пустой. Строка документации не влияет на то, как CMake обращается с переменной, она используется только инструментами графического интерфейса для предоставления таких вещей, как справка, всплывающие подсказки и т. д.
При обработке CMake всегда будет рассматривать переменную как строку. Тип используется в основном для улучшения пользовательского опыта в инструментах графического интерфейса, за некоторыми важными исключениями, которые обсуждаются в разделе 5.5, «Потенциально неожиданное поведение переменных». Тип должен быть одним из следующих:
- BOOL Переменная кэша представляет собой булево значение включено/выключено. В инструментах GUI для представления этой переменной используется флажок или что-то подобное. Базовое строковое значение, хранящееся в переменной, будет соответствовать одному из способов, которыми CMake представляет булевы значения в виде строк (ON/OFF, TRUE/FALSE, 1/0 и т. д. - подробности см. в разделе 6.1.1, «Базовые выражения»).
- FILEPATH Переменная кэша представляет собой путь к файлу на диске. Средства графического интерфейса представляют пользователю диалог выбора файла для изменения значения переменной.
- PATH Как и FILEPATH, но инструменты GUI представляют диалог, в котором выбирается каталог, а не файл.
- STRING Переменная рассматривается как произвольная строка. По умолчанию инструменты GUI используют однострочный виджет редактирования текста для работы со значением переменной. Проекты могут использовать свойства переменной кэша, чтобы предоставить инструментам GUI заранее определенный набор значений в виде combobox или аналогичных элементов (см. раздел 9.6, «Свойства переменной кэша»).
- INTERNAL Переменная не предназначена для предоставления пользователю. Внутренние переменные кэша иногда используются для постоянной записи внутренней информации проекта, например, для кэширования результатов интенсивного запроса или вычислений. Инструменты графического интерфейса не отображают переменные INTERNAL. INTERNAL также подразумевает FORCE (см. далее).
GUI-инструменты обычно используют helpString в качестве всплывающей подсказки для переменной кэша или в качестве короткого однострочного описания при выборе переменной. Это описание должно быть коротким и состоять из обычного текста (т.е. без HTML-разметки и т.д.). Установка булевой кэш-переменной - настолько распространенная потребность, что CMake предоставляет для нее отдельную команду. Вместо несколько многословной команды set() разработчики могут использовать option():
Если initialValue опущено, будет использоваться значение по умолчанию OFF. Если указано, initialValue должно соответствовать одному из булевых значений, принимаемых командой set(). Для справки, вышеприведенное можно считать более или менее эквивалентным:
По сравнению с командой set(), команда option() более четко выражает поведение булевых переменных кэша, поэтому в общем случае предпочтительнее использовать именно ее. Однако помните, что в некоторых ситуациях эффект от этих двух команд может быть разным (см. раздел 5.5, «Потенциально неожиданное поведение переменных»).
Важное различие между обычными и кэш-переменными заключается в том, что команда set() перезаписывает кэш-переменную только при наличии ключевого слова FORCE, в отличие от обычных переменных, для которых команда set() всегда перезаписывает уже существующее значение. Команда set() действует скорее как set-if-not-set, когда используется для определения переменных кэша, как и команда option() (которая не имеет возможности FORCE). Основная причина этого заключается в том, что переменные кэша в первую очередь предназначены для разработчиков в качестве средств настройки. Вместо того чтобы жестко кодировать значение обычных переменных в файле CMakeLists.txt, можно использовать кэш-переменную, чтобы разработчик мог изменить значение без необходимости редактировать файл CMakeLists.txt. Переменная может быть изменена интерактивными средствами графического интерфейса или скриптами без необходимости изменять что-либо в самом проекте.
6.4. Блоки области видимости.
Как уже говорилось в разделе 5.1, «Основные понятия», переменная имеет область видимости. Кэш-переменные имеют глобальную область видимости, поэтому они всегда доступны. В материале, представленном до сих пор, область видимости некэшируемой переменной - это файл CMakeLists.txt, в котором переменная определена. Это часто называют областью видимости каталога. Подкаталоги и функции наследуют переменные из родительской области видимости (рассматривается в разделе 7.1.2, «Область видимости» и разделе 8.4, «Возвращение значений» соответственно).
В CMake 3.25 и более поздних версиях команды block() и endblock() можно использовать для определения области локальных переменных. При входе в блок он получает копии всех переменных, определенных в окружающей области видимости в данный момент времени. Любые изменения переменных в блоке выполняются в его копиях, оставляя переменные окружающей области видимости неизменными. При выходе из блока все переменные, которые были скопированы в блок или созданы в блоке, удаляются. Это может быть полезным способом изолировать определенный набор команд от основной логики.
Блок не всегда может быть полностью изолирован от вызывающей его стороны. Он может захотеть выборочно модифицировать некоторые переменные в окружающей области видимости. PARENT_SCOPE команд set() и unset() может быть использован для изменения переменной не в текущей, а в окружающей области видимости:
Когда используется PARENT_SCOPE, переменная, которая устанавливается или снимается, находится в родительской, а не в текущей области видимости. Важно отметить, что это не означает, действие применимо к переменной как в родительской, так и в текущей области видимости. Это может сделать PARENT_SCOPE неудобным в использовании, так как придется часто повторять одну и ту же команду для двух разных областей видимости, когда изменение должно затронуть обе. Команда block() поддерживает ключевое слово PROPAGATE, которое может быть использовано для обеспечения такого поведения более надежным и лаконичным способом. Когда поток управления покидает блок, значение каждой переменной, указанной после ключевого слова PROPAGATE, передается из блока в окружающую область. Если пропагируемая переменная была снята внутри блока командой unset(), она перестанет существовать в окружающей области при выходе из блока.
Команда block() может использоваться не только для управления областями переменных. Полная сигнатура команды задается так:
Ключевое слово SCOPE_FOR может быть использовано для указания того, какую область видимости должен создать блок. Если SCOPE_FOR опущено, block() создает новую локальную область видимости как для переменных, так и для политик (о последних см. раздел 12.2, «Область видимости политики»). Следующий пример имеет тот же эффект, что и предыдущий, но в нем создается только область видимости переменной, а область видимости политики остается неизменной:
Хотя SCOPE_FOR VARIABLES, скорее всего, будет тем, что нужно проекту в большинстве случаев, часто не будет лишним позволить создать и новую область видимости политики. Использование block(), вместо block(SCOPE_FOR VARIABLES) может быть несколько менее эффективным, но все же предпочтительнее из-за своей простоты.
О том, как команда block() взаимодействует с другими командами потока управления, читайте в разделе 6.2.3, «Прерывание циклов», разделе 7.4, «Досрочное завершение обработки» и разделе 8.4, «Возврат значений».
6.5. Потенциально неожиданное поведение переменных.
Часто не все понимают, что обычные и кэш-переменные - это две разные вещи. Можно иметь обычную и кэш-переменную с одним и тем же именем, но разными значениями. В таких случаях при использовании ${myVar} CMake будет извлекать значение нормальной переменной, а не кэш-переменной. Другими словами, обычные переменные имеют приоритет над кэш-переменными. Исключением является то, что при установке значения кэш-переменной любая обычная переменная с тем же именем удаляется из текущей области видимости в следующих ситуациях (с учетом настроек политики, рассмотренных ниже):
- Переменная кэша не существовала до вызова функции set() или option().
- Переменная кэша существовала до вызова set() или option(), но не имела определенного типа (о том, как это может происходить, см. раздел 5.6.1, «Установка значений кэша в командной строке»).
- В вызове set() была использована опция FORCE или INTERNAL.
В первых двух случаях это означает, что между первым и последующими запусками CMake может быть разное поведение. В первом запуске кэш-переменная не будет существовать или не будет иметь определенного типа, но в последующих запусках она будет существовать. Поэтому в первом запуске обычная переменная будет удалена, а в последующих - нет. Пример должен помочь проиллюстрировать проблему:
В главе 7 «Использование подкаталогов» и главе 8 «Функции и макросы» подробнее рассматривается, как область видимости переменной может повлиять на значение, которое вернет ${myVar}.
В CMake 3.13 поведение option() было изменено таким образом, что если обычная переменная с таким же именем уже существует, команда ничего не делает. Это новое поведение обычно соответствует интуитивным ожиданиям разработчиков. Аналогичное изменение было сделано для команды set() в CMake 3.21, но обратите внимание на следующие различия в новом поведении обеих команд:
- Для set() переменная кэша все равно устанавливается, если она не существовала ранее, а для option() - нет.
- Если в функции set() используется INTERNAL или FORCE, переменная кэша всегда будет установлена или обновлена.
Разработчикам следует помнить об этих несоответствиях и о разных версиях CMake, обеспечивающих новое поведение. Политики CMP0077 и CMP0126 контролируют фактическое поведение (см. главу 12 «Политики» для понимания того, как ими можно управлять).
Взаимодействие между обычными и кэш-переменными может привести и к другому потенциально неожиданному поведению. Рассмотрим следующие три команды:
Можно было бы подумать, что оценка ${foo} всегда будет давать пустую строку после любого из этих трех случаев, но только в последнем случае это гарантировано. И unset(foo), и set(foo) удаляют некэшируемую переменную из текущей области видимости. Если существует кэш-переменная с именем foo, то эта кэш-переменная будет оставлена в покое, а ${foo} будет содержать значение этой кэш-переменной. В этом смысле unset(foo) и set(foo) эффективно снимают маску с кэш-переменной foo, если она существует. С другой стороны, set(foo "") не удаляет некэшируемую переменную, а явно устанавливает ее в пустое значение, так что ${foo} всегда будет оцениваться как пустая строка, независимо от того, существует ли кэшируемая переменная с именем foo. Поэтому установка переменной в пустую строку, а не ее удаление, скорее всего, будет более надежным способом реализации замысла разработчика.
Для тех редких ситуаций, когда проекту может потребоваться получить значение переменной кэша и проигнорировать любую одноименную переменную, не входящую в кэш, в CMake 3.13 добавлена документация по форме $CACHE{someVar}. Обычно проекты не должны использовать эту форму, кроме как для отладки, поскольку она нарушает давно устоявшееся представление о том, что обычные переменные будут переопределять значения, установленные в кэше.
6.6. Манипулирование кэш-переменными.
Используя set() и option(), проект может создать полезный набор настроек для своих разработчиков. Можно включать и выключать различные части сборки, задавать пути к внешним пакетам, изменять флаги компиляторов и компоновщиков и так далее. В последующих главах мы рассмотрим эти и другие способы использования переменных кэша, но сначала необходимо понять, как манипулировать такими переменными. Существует два основных способа, с помощью которых разработчики могут это делать: либо из командной строки cmake, либо с помощью инструмента GUI.
6.6.1. Установка значений кэш-переменных в командной строке.
CMake позволяет управлять переменными кэша напрямую с помощью опций командной строки, передаваемых в cmake. Основной рабочей лошадкой является опция -D, которая используется для определения значения переменной кэша.
someValue заменит любое предыдущее значение кэш-переменной myVar. Поведение по сути такое же, как если бы переменная присваивалась с помощью команды set() с опциями CACHE и FORCE. Опцию командной строки нужно указать только один раз, поскольку она сохраняется в кэше для последующих запусков и поэтому ее не нужно указывать при каждом запуске cmake. Можно указать несколько опций -D, чтобы задать в командной строке cmake несколько переменных одновременно. При таком определении переменных кэша их не нужно задавать в файле CMakeLists.txt (т. е. не требуется соответствующая команда set()). Кэш-переменные, заданные в командной строке, имеют пустую строку описания. Тип также может быть опущен, в этом случае переменная будет иметь неопределенный тип, или, что более точно, ей будет присвоен специальный тип, похожий на INTERNAL, но интерпретируемый CMake как неопределенный. Ниже показаны различные примеры установки переменных кэша через командную строку.
Обратите внимание, что при установке переменной кэша со значением, содержащим пробелы, все значение, заданное с помощью опции -D, должно быть заключено в кавычки. Существует специальный случай для работы со значениями, изначально объявленными без типа в командной строке cmake. Если затем в файле CMakeLists.txt проекта попытаться установить ту же кэш-переменную и указать тип FILEPATH или PATH, то если значение этой кэш-переменной является относительным путем, CMake будет рассматривать его как относительное к директории, из которой был вызван cmake, и автоматически преобразует его в абсолютный путь. Это не очень надежно, поскольку cmake может быть вызван из любого каталога, а не только из каталога сборки. Поэтому разработчикам рекомендуется всегда указывать тип при задании переменной в командной строке cmake для переменной, которая представляет собой какой-то путь. В любом случае, указывать тип переменной в командной строке - хорошая практика, чтобы в GUI-приложениях она отображалась в наиболее подходящем виде. Это также предотвратит один из сценариев, упомянутых ранее в разделе 5.5, «Потенциально неожиданное поведение переменных».
Также можно удалить переменные из кэша с помощью опции -U, которую можно повторить при необходимости, чтобы удалить более одной переменной. Обратите внимание, что опция -U поддерживает подстановочные знаки * и ?, но нужно быть осторожным, чтобы не удалить больше, чем предполагалось, и не оставить кэш в состоянии, не пригодном для сборки. В целом, рекомендуется удалять только конкретные записи без подстановочных знаков, если нет полной уверенности в том, что используемые подстановочные знаки безопасны.
6.6.2. Средства CMake с графическим интерфейсом.
Установка переменных кэша через командную строку - неотъемлемая часть сценариев автоматической сборки и всего, что управляет CMake с помощью команды cmake. Однако для повседневной разработки инструменты графического интерфейса, предоставляемые CMake, часто оказываются более удобными. CMake предоставляет два эквивалентных GUI-инструмента, cmake-gui и ccmake, которые позволяют разработчикам интерактивно управлять переменными кэша. cmake-gui - это полнофункциональное GUI-приложение, поддерживаемое на всех основных настольных платформах, тогда как ccmake использует интерфейс на основе curses, который можно использовать только в текстовом окружении, например, через ssh-соединение. cmake-gui входит в официальные пакеты CMake для всех платформ, ccmake - для всех платформ, кроме Windows. Если вы используете системные пакеты в Linux, а не официальные релизы, обратите внимание, что во многих дистрибутивах cmake-gui выделен в отдельный пакет.
Пользовательский интерфейс cmake-gui показан на рисунке ниже. В верхней части можно определить каталог исходных текстов и каталог сборки проекта. В средней части можно просматривать и редактировать переменные кэша. В нижней части находятся кнопки Configure и Generate, а затем область журнала, в которой отображаются результаты этих операций.
В качестве каталога исходных текстов должен быть выбран каталог, содержащий файл CMakeLists.txt в верхней части дерева исходных текстов проекта. Каталог сборки - это место, где CMake будет генерировать все выходные данные сборки (рекомендуемые схемы расположения каталогов обсуждались в главе 2, «Настройка проекта»). Для новых проектов необходимо установить оба параметра, но для существующих проектов установка каталога сборки также приведет к обновлению каталога исходных текстов, поскольку расположение исходных текстов хранится в кэше каталога сборки.
Двухэтапный процесс установки CMake был представлен в разделе 2.3, «Генерация файлов проекта». На первом этапе читается файл CMakeLists.txt и в памяти создается представление проекта. Этот этап называется этапом конфигурирования. Если этап configure прошел успешно, то затем можно выполнить этап generate для создания файлов проекта инструмента сборки в каталоге сборки. При запуске cmake из командной строки оба этапа выполняются автоматически, но в GUI-приложении они запускаются отдельно с помощью кнопок Configure и Generate.
При каждом запуске шага конфигурирования переменные кэша, отображаемые в центре пользовательского интерфейса, обновляются. Все переменные, которые были недавно добавлены или изменили значение по сравнению с предыдущим запуском, будут выделены красным цветом (при первой загрузке проекта все переменные отображаются выделенными). Хорошей практикой является повторный запуск этапа конфигурирования до тех пор, пока не останется никаких изменений. Это обеспечивает надежное поведение для более сложных проектов, где включение некоторых опций может добавить дополнительные опции, которые могут потребовать еще одного прохода configure. Когда все переменные кэша отображаются без красного выделения, можно запускать этап генерации. В примере на предыдущем снимке экрана показан типичный вывод журнала после выполнения этапа configure, на котором не было внесено изменений ни в одну из переменных кэша.
При наведении курсора мыши на любую переменную кэша появляется всплывающая подсказка, содержащая строку описания для этой переменной. Новые кэш-переменные также можно добавить с помощью кнопки Add Entry, что эквивалентно выполнению команды set() с пустой helpString. Переменные кэша можно удалить с помощью кнопки Remove Entry, хотя CMake, скорее всего, воссоздаст эту переменную при следующем запуске.
Щелчок по переменной позволяет редактировать ее значение в виджете, соответствующем типу переменной. Булевы переменные отображаются в виде чекбокса, файлы и пути имеют кнопку просмотра файловой системы, а строки обычно представлены в виде текстового блока. Как частный случай, кэш-переменным типа STRING можно задать набор значений, чтобы они отображались в combobox в графическом интерфейсе CMake вместо простого виджета ввода текста. Это достигается установкой свойства STRINGS кэш-переменной (подробно рассмотрено в разделе 9.6, «Свойства кэш-переменной», но для удобства показано здесь):
В приведенном выше примере переменная кэша TRAFFIC_LIGHT изначально будет иметь значение Green. Когда пользователь попытается изменить TRAFFIC_LIGHT в cmake-gui, ему будет предложен combobox, содержащий три значения Red, Orange и Green, вместо простого виджета редактирования строки, который в противном случае позволил бы ввести произвольный текст. Обратите внимание, что установка свойства STRINGS для переменной не препятствует присвоению ей других значений, она влияет только на виджет, используемый cmake-gui при ее редактировании. Переменной по-прежнему можно присвоить другие значения с помощью команд set() в файле CMakeLists.txt или другими способами, например, вручную отредактировав файл CMakeCache.txt.
У переменных кэша также может быть свойство, отмечающее их как расширенные (advanced) или нет. Это тоже влияет только на то, как переменная отображается в cmake-gui, но никак не на то, как CMake использует переменную в процессе обработки. По умолчанию cmake-gui показывает только не расширенные переменные, что соответствует тому, что разработчику было бы интересно просматривать или изменять. Включение опции Advanced показывает все переменные кэша, кроме тех, которые помечены как INTERNAL (единственный способ увидеть переменные INTERNAL - это отредактировать файл CMakeCache.txt с помощью текстового редактора, поскольку они не предназначены для непосредственного манипулирования разработчиками). Переменные могут быть помечены как расширенные с помощью команды mark_as_advanced() в файле CMakeLists.txt:
Ключевое слово CLEAR указывает что переменные будут помечены как не-расширенные, а ключевое слово FORCE наоборот. Без ключевого слова переменные будут помечены как расширенные, только если они еще не имеют состояния расширенный/нерасширенный.
Выбор опции Grouped в cmake-gui может упростить просмотр расширенных переменных, сгруппировав их по именам до первого символа подчеркивания. Другой способ отфильтровать список отображаемых переменных - ввести текст в область поиска, в результате чего будут показаны только те переменные, в имени или значении которых содержится указанный текст.
При первом запуске этапа configure в новом проекте перед разработчиком появляется диалог, подобный тому, что показан на следующем снимке экрана:
В этом диалоге задается генератор CMake и инструментарий компилятора. Выбор генератора обычно зависит от личных предпочтений разработчика, а доступные опции представлены в combobox. В зависимости от проекта выбор генератора может быть более ограниченным, чем позволяют опции combobox, например, если проект полагается на специфическую функциональность генератора. Частым примером является проект, для которого требуется генератор Xcode из-за уникальных возможностей платформы Apple, таких как подпись кода и поддержка iOS/tvOS/watchOS. Выбрав генератор для проекта, его нельзя изменить, не удалив кэш и не начав работу заново.
Для представленных вариантов инструментария компилятора каждый из них требует от разработчика все больше информации. Использование родных компиляторов по умолчанию является обычным выбором для обычной настольной разработки, и выбор этой опции не требует дополнительных сведений. Если требуется больший контроль, разработчик может переопределить родные компиляторы, указав пути к ним в последующем диалоге. Если имеется отдельный toolchain-файл, его можно использовать не только для настройки компилятора, но и окружения или других параметров. Использование toolchain-файла характерно для кросс-компиляции, которая подробно рассматривается в главе 23 «Toolchains и кросс-компиляция». Наконец, для максимального контроля разработчики могут указать полный набор опций для кросс-компиляции, но это не рекомендуется для обычного использования, так как toolchain-файл может предоставить ту же информацию, но имеет то преимущество, что его можно использовать повторно по мере необходимости.
Инструмент ccmake предлагает большинство тех же функций, что и приложение cmake-gui, но делает это через текстовый интерфейс:
Вместо выбора каталогов исходного текста и сборки, как в cmake-gui, каталог исходного текста или сборки должен быть указан в командной строке ccmake, как и в команде cmake.
Небольшим недостатком интерфейса ccmake является отсутствие возможности фильтрации отображаемых переменных. Методы редактирования переменных также не так богаты, как в cmake-gui. Тем не менее, инструмент ccmake является полезной альтернативой, когда полноценное приложение cmake-gui нецелесообразно или недоступно, например, через консольное соединение, которое не поддерживает переадресацию UI.
6.7. Вывод значений переменных.
При усложнении проектов или при исследовании неожиданного поведения может оказаться полезным выводить диагностические сообщения и значения переменных во время работы CMake. Обычно для этого используется команда message(), которая подробно рассматривается в главе 13, «Отладка и диагностика». Пока же достаточно знать, что в своей простейшей форме команда message() просто выводит свои аргументы. Она не добавляет разделитель между аргументами, если указано более одного аргумента, и в конце сообщения автоматически добавляется перевод строки. Перевод строки также может быть явно указан с помощью обычной нотации \n. Значение переменной может быть включено в сообщение с помощью обычной нотации ${myVar}.
Результат:
6.8. Обработка строк.
По мере роста сложности проекта во многих случаях возникает необходимость реализовать более сложную логику управления переменными. Основным инструментом CMake для этого является команда string(), которая предоставляет широкий спектр полезных функций по работе со строками. Эта команда позволяет проектам выполнять операции поиска и замены, сопоставления регулярных выражений, преобразования верхнего и нижнего регистра, удаление пробельных символов и другие распространенные задачи. Некоторые из наиболее часто используемых функций представлены ниже, но справочная документация CMake должна рассматриваться как канонический источник информации обо всех доступных операциях и их поведении.
Первый аргумент string() определяет операцию, которую необходимо выполнить, а последующие аргументы зависят от запрашиваемой операции. Как правило, эти аргументы требуют как минимум одной входной строки и, поскольку команды CMake не могут возвращать значение, выходной переменной для результата операции. В приведенном ниже материале эта выходная переменная называется outVar.
FIND ищет подстроку в inputString и сохраняет индекс найденной подстроки в outVar (первый символ - индекс 0). Находится первое вхождение, если не указано REVERSE, в этом случае будет найдено последнее вхождение. Если подстрока не встречается в inputString, то outVar будет присвоено значение -1.
Результат:
Замена простой подстроки происходит по аналогичной схеме:
Операция REPLACE заменит каждое вхождение matchString во входных строках на replaceWith и сохранит результат в outVar. Если задано несколько входных строк, то перед поиском замен они объединяются без разделителя между строками. Иногда это может привести к неожиданным совпадениям, поэтому в большинстве случаев разработчики указывают только одну входную строку.
Регулярные выражения также хорошо поддерживаются операцией REGEX, причем доступно несколько различных вариантов, определяемых вторым аргументом:
Значение regex - регулярное выражение для сопоставления, которое может использовать типичный базовый синтаксис регулярных выражений (полную спецификацию см. в справочной документации CMake), хотя некоторые общие функции, такие как отрицание, не поддерживаются. Входные строки перед подстановкой конкатенируются. Операция MATCH находит только первое совпадение и сохраняет его в outVar. MATCHALL находит все совпадения и сохраняет их в outVar в виде списка. REPLACE возвращает всю входную строку с каждым совпадением, замененным на replaceWith. Для обозначения совпадений в replaceWith можно использовать обычные маркеры \1, \2 и т. д., но обратите внимание, что сами обратные слеши должны быть экранированы, если не используется синтаксис скобок. Следующий пример и его вывод демонстрируют вышеизложенные моменты:
Результат:
Извлечение подстроки тоже поддерживается:
index - это целое число, определяющее начало подстроки, которую нужно извлечь из входных данных. Будет извлечено length кол-во символов, или, если length равно -1, возвращаемая подстрока будет содержать все символы до конца входной строки. Обратите внимание, что в CMake 3.1 и более ранних версиях ошибка выдавалась, если length указывала на конец строки.
Длина строки может быть тривиально определена, а строки легко преобразуются в верхний или нижний регистр. Также легко удалить пробельные символы из начала и конца строки. Синтаксис для всех этих операций имеет одинаковую форму:
В случае LENGTH по историческим причинам команда считает байты, а не символы. Для строк, содержащих многобайтовые символы, это означает, что сообщаемая длина будет отличаться от количества символов.
CMake предоставляет и другие операции, такие как сравнение строк, хеширование, временные метки, работа с JSON и многое другое, но их использование в повседневных проектах CMake менее распространено. Заинтересованный читатель должен обратиться к справочной документации CMake по команде string() для получения подробной информации.
6.9. Списки.
Списки широко используются в CMake. В конечном итоге списки представляют собой одну строку с элементами списка, разделенными точками с запятой (за одним исключением, которое рассматривается в разделе 5.9.1, «Проблемы с несбалансированными квадратными скобками»). Это может сделать работу с отдельными элементами списка менее удобной. Для облегчения таких задач CMake предоставляет команду list(). Как и в случае с командой string(), list() ожидает в качестве первого аргумента операцию, которую необходимо выполнить. Вторым аргументом всегда является список, с которым нужно работать, и он должен быть переменной (то есть передача литерала-списка типа a;b;c недопустима).
Самые основные операции со списком - подсчет количества элементов и извлечение одного или нескольких элементов из списка:
Пример:
Результат:
Вставка, добавление в конец и начало элементов также является распространенной задачей:
В отличие от методов LENGTH и GET, INSERT, APPEND и PREPEND действуют непосредственно на listVar и изменяют его на месте, как показано в следующем примере:
Результат:
Поиск определенного элемента в списке происходит по ожидаемой схеме:
Пример:
Результат:
Для удаления элементов предусмотрено три операции, каждая из которых изменяет список напрямую:
Операция REMOVE_ITEM может использоваться для удаления всех элементов равных value из списка. Если элемента нет в списке, это не является ошибкой. REMOVE_AT, с другой стороны, указывает один или несколько индексов для удаления, и CMake остановится с ошибкой, если любой из указанных индексов окажется за пределами конца списка. REMOVE_DUPLICATES гарантирует, что список будет содержать только уникальные элементы.
В CMake 3.15 добавлена поддержка извлечения элементов из передней или задней части списка и возможность сохранения извлеченных элементов:
Если переменная outVar не указана, то один элемент будет извлечен из передней или задней части и отброшен. Если задано одно или несколько имен outVar, извлеченные элементы будут сохранены в этих переменных, причем количество извлеченных элементов будет равно количеству заданных имен переменных.
Элементы списка можно также упорядочить с помощью операций REVERSE или SORT:
Все необязательные ключевые слова для list(SORT) доступны только в CMake 3.13 или более поздней версии. Если присутствует опция COMPARE, метод должен быть одним из следующих:
- STRING Сортировка по алфавиту. Это поведение по умолчанию, когда опция COMPARE не задана.
- FILE_BASENAME Сортировка производится в предположении, что каждый элемент является путем и что элементы должны быть отсортированы в соответствии с базовой частью имен файлов в пути.
- NATURAL Аналогично STRING, за исключением того, что смежные цифры внутри элемента сортируются численно. Это наиболее полезно для сортировки строк, содержащих встроенные номера версий. Правила сортировки те же, что и для функции strverscmp() языка C (расширение GNU). Этот метод сортировки доступен только в CMake 3.18 или более поздней версии.
За ключевым словом CASE может следовать одно из двух значений SENSITIVE или INSENSITIVE, а за ключевым словом ORDER значения ASCENDING или DESCENDING.
Для всех операций со списком, в которых используется индекс, отрицательный индекс означает, что отсчет начинается с конца списка. При таком использовании последний элемент списка имеет индекс -1, предпоследний -2 и так далее.
Выше описано большинство доступных подкоманд list(). Все они поддерживаются, по крайней мере, с версии CMake 3.0, если не указано иное, поэтому проекты, как правило, могут рассчитывать на их наличие. Для получения полного списка поддерживаемых подкоманд читателю следует обратиться к документации CMake.
6.9.1. Проблемы с несбалансированными квадратными скобками.
Есть одно исключение из того, как CMake обычно относится к точке с запятой в качестве разделителя списков. По историческим причинам, если элемент списка содержит открывающую квадратную скобку [, он также должен иметь соответствующую закрывающую квадратную скобку ]. CMake будет рассматривать любую точку с запятой между этими квадратными скобками как часть элемента списка, а не как разделитель списка. Если попытаться построить список с несбалансированными квадратными скобками, список будет интерпретирован не так, как ожидалось. Ниже показано такое поведение:
Результатом вышеизложенного будет:
В разделе 8.8.3, «Особые случаи расширения аргументов», рассматриваются другие аспекты этой особенности.
6.10. Математика.
Еще одной распространенной формой манипулирования переменными являются математические вычисления. CMake предоставляет команду math() для выполнения базовых математических вычислений:
Первым аргументом должно быть ключевое слово EXPR, а mathExpr определяет выражение, которое будет вычислено, и результат будет сохранен в outVar. В выражении может использоваться любой из следующих операторов, которые имеют то же значение, что и в коде на языке C: + - * / % | & ^ ~ << >>. Также поддерживаются круглые скобки, которые имеют обычное математическое значение. На переменные в mathExpr можно ссылаться с помощью обычной нотации ${myVar}.
Если вы используете CMake 3.13 или более позднюю версию, ключевое слово OUTPUT_FORMAT может быть задано для управления тем, как результат будет храниться в outVar. Формат должен быть либо DECIMAL, который используется по умолчанию, либо HEXADECIMAL.
Результат:
6.11. Рекомендуемые практики.
Если среда разработки позволяет, инструмент CMake GUI - полезный способ быстро и легко понять параметры сборки проекта и изменять их по мере необходимости в процессе разработки. Немного времени, потраченного на знакомство с ним, упростит работу с более сложными проектами в дальнейшем. Кроме того, он дает разработчикам хорошую базу для работы, если им понадобится поэкспериментировать с такими вещами, как настройки компилятора, поскольку их легко найти и изменить в среде GUI.
Предпочтительнее предоставлять переменные кэша для управления включением и выключением необязательных частей проекта вместо того, чтобы кодировать логику в скриптах вне CMake. Это позволит легко включать и выключать их в графическом интерфейсе CMake и других инструментах, которые понимают, как работать с кэшем CMake (все большее число сред IDE обретают такую возможность).
Старайтесь не полагаться на определение переменных окружения, за исключением, возможно, вездесущего PATH или аналогичных переменных уровня операционной системы. Сборка должна быть предсказуемой, надежной и простой в настройке, но если для ее корректной работы необходимо задать переменные окружения, это может стать причиной разочарования для начинающих разработчиков, которые пытаются настроить окружение сборки. Кроме того, окружение в момент запуска CMake может измениться по сравнению с моментом самой сборки. Поэтому по возможности предпочитайте передавать информацию в CMake напрямую через переменные кэша.
Постарайтесь заблаговременно установить соглашение об именовании переменных. Для переменных кэша рассмотрите возможность группировки связанных переменных под общим префиксом, за которым следует знак подчеркивания, чтобы воспользоваться преимуществами того, как графический интерфейс CMake автоматически группирует переменные на основе того же префикса. Также учитывайте, что проект может однажды стать частью какого-то более крупного проекта, поэтому желательно, чтобы имя начиналось с названия проекта или чего-то, тесно связанного с ним.
Старайтесь избегать определения в проекте некэшируемых переменных, которые имеют то же имя, что и кэшируемые переменные. Взаимодействие между этими двумя типами переменных может быть неожиданным для разработчиков, только начинающих работать с CMake. В последующих главах также будут рассмотрены другие распространенные ошибки и неправильное использование обычных переменных, которые имеют то же имя, что и переменные кэша.
CMake предоставляет большое количество предопределенных переменных, которые предоставляют подробную информацию о системе или влияют на определенные аспекты поведения CMake. Некоторые из этих переменных активно используются в проектах, например те, которые определяются только при сборке под определенную платформу (WIN32, APPLE, UNIX и т. д.). Поэтому разработчикам рекомендуется время от времени просматривать страницу документации CMake с перечнем предопределенных переменных, чтобы ознакомиться с их возможностями.
Это был ознакомительный фрагмент книги Professional CMake: A Practical Guide by Craig Scott