Найти в Дзене
coding style

CMake - Практическое руководство. Глава 10. Свойства (Крэйг Скотт, перевод на русский язык)

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

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

Новички в CMake иногда путают свойства с переменными. Хотя поначалу может показаться, что они похожи, свойства служат совершенно иным целям. Переменная не привязана к какому-либо конкретному объекту, и очень часто проекты определяют и используют свои собственные переменные. Сравните это со свойствами, которые строго определены и документированы в CMake и всегда применяются к конкретной сущности. Вероятно, путаница между этими двумя понятиями связана с тем, что значение свойства по умолчанию иногда задается переменной. Имена, которые CMake использует для свойств и связанных переменных, обычно следуют одной и той же схеме: имя переменной - это имя свойства с добавлением CMAKE_ в качестве префикса.

10.1. Основные команды для свойств

CMake предоставляет ряд команд для работы со свойствами. Наиболее общие из них, set_property() и get_property(), позволяют установить и получить любое свойство для любого типа сущности.

-2

entitySpecific определяет сущность, свойство которой устанавливается. Это должно быть одно из следующих значений:

-3

Первое слово определяет тип сущности, свойство которой устанавливается. GLOBAL означает всю сборку, поэтому конкретное имя сущности не требуется. Для DIRECTORY, если не указан dir, используется текущий каталог исходных текстов. Для всех остальных типов сущностей может быть перечислено любое количество элементов этого типа. Тип сущности SOURCE также поддерживает некоторые дополнительные опции при использовании CMake 3.18 или более поздней версии, которые рассматриваются в разделе 10.5, «Свойства исходников».

Ключевое слово PROPERTY определяет все остальные аргументы как имя свойства и его значение(я). Имя свойства обычно соответствует одному из свойств, определенных в документации CMake, некоторые из которых рассматриваются в последующих главах. Значение(я) зависит от самого свойства.

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

-4

Приведенный выше пример определяет пользовательское свойство MYPROJ_CUSTOM_PROP, значением которого будет список val1;val2;val3. Он также демонстрирует, как установить свойство сразу для нескольких целей.

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

-5

Команда get_property() имеет аналогичную форму:

-6

Ключевое слово PROPERTY и propertyName всегда обязательны. Часть entitySpecific аналогична части set_property() и должна быть одной из следующих:

-7

Как и раньше, GLOBAL относится к сборке в целом и поэтому не требует указания конкретного объекта. DIRECTORY может использоваться как с указанием, так и без указания конкретного каталога, при этом если каталог не указан, то предполагается текущий каталог исходного кода. Опять же, тип SOURCE поддерживает дополнительные опции при использовании CMake 3.18 или более поздней версии, и они рассматриваются в разделе 10.5, «Свойства исходников». Тип VARIABLE немного отличается: имя переменной указывается как propertyName, а не присоединяется к ключевому слову VARIABLE. Это может показаться несколько неинтуитивным, но подумайте о ситуации, если бы имя переменной было указано вместе с ключевым словом VARIABLE, как и для других типов entitySpecific. В такой ситуации не нужно было бы указывать имя свойства. Возможно, вам поможет представление о том, что VARIABLE задает текущую область видимости, а интересующее вас свойство - это переменная, указанная в propertyName. При таком понимании VARIABLE соответствует тому, как обрабатываются другие типы entitySpecific.

Если ни одно из необязательных ключевых слов DEFINED, SET, BRIEF_DOCS или FULL_DOCS не указано, значение свойства сохраняется в переменной с именем resultVar. Это типичное использование команды get_property(). Вместо использования ключевого слова VARIABLE, значения переменных можно и нужно получать более непосредственно, используя синтаксис ${}. Дополнительные ключевые слова могут использоваться для получения других сведений о свойстве:

  • DEFINED Результатом поиска будет булево значение, указывающее, было ли именованное свойство определено. В случае запросов с ключевым словом VARIABLE результат будет истинным, только если именованная переменная была явно определена с помощью команды define_property() (см. ниже).
  • SET Результатом поиска будет булево значение, указывающее, было ли именованное свойство установлено. Эта функция отличается от DEFINED тем, что она запрашивает, было ли именованное свойство действительно установлено в какое-то значение (само значение не имеет значения). В большинстве случаев в проектах требуется именно SET, а не DEFINED. Обратите внимание, что свойство может возвращать true для DEFINED и false для SET, или наоборот.
  • BRIEF_DOCS Получает строку краткой документации для именованного свойства. Если для свойства не была определена краткая документация, результатом будет строка NOTFOUND.
  • FULL_DOCS Получает строку полной документации для именованного свойства. Если для свойства не определена полная документация, результатом будет строка NOTFOUND.

Из необязательных ключевых слов все, кроме SET, не имеют большого значения, если только проект не вызвал define_property():

-8

Команда define_property() не устанавливает значение свойства. Скорее, она управляет тем, как это свойство инициализируется или наследуется, и, возможно, предоставляет документацию. CMake 3.22 и более ранние версии требуют наличия BRIEF_DOCS и FULL_DOCS, но они не используются в CMake, за исключением предоставления их обратно в проект через get_property(). Вероятно, эти опции будут устаревшими в будущих версиях CMake из-за их бесполезности.

entityType должен быть одним из GLOBAL, DIRECTORY, TARGET, SOURCE, TEST, VARIABLE или CACHED_VARIABLE. PropertyName указывает на определяемое свойство. В случае если entityType равно VARIABLE, как и в команде get_property(), в качестве propertyName указывается имя переменной.

Если указана опция INHERITED, команда get_property() будет цепочкой подниматься к родительской области видимости, если свойство не установлено в именованной области видимости. Например, если запрашивается свойство DIRECTORY, но оно не установлено для указанного каталога, то родительский каталог запрашивается рекурсивно вверх по иерархии каталогов, пока свойство не будет найдено или не будет достигнут верхний уровень иерархии каталогов исходников. Если в каталоге верхнего уровня свойство все еще не найдено, то выполняется поиск в области GLOBAL. Аналогично, если запрашивается свойство TARGET, SOURCE или TEST, но оно не установлено для указанной сущности, то будет произведен поиск в области DIRECTORY (включая рекурсивный поиск по иерархии каталогов и, в конечном счете, в области GLOBAL, если это необходимо). Для VARIABLE и CACHED_VARIABLE такой функции цепочки не предусмотрено, так как они по своей сути уже привязаны к родительской области видимости.

Наследование свойств INHERITED распространяется только на команду get_property() и аналогичные ей функции get_... для определенных типов свойств (рассматриваются в разделах ниже). При вызове set_property() с опциями APPEND или APPEND_STRING учитывается только непосредственно переданное значение (т. е. наследование не происходит).

В CMake 3.23 добавлена поддержка ключевого слова INITIALIZE_FROM_VARIABLE, которое указывает переменную, используемую для инициализации именованного свойства. Оно может использоваться только со свойствами цели и действует только на цели, созданные после вызова define_property(). Имя переменной должно заканчиваться именем свойства и не может начинаться с CMAKE_ или CMAKE. Эта возможность особенно полезна для предоставления значения по умолчанию для пользовательского свойства, определенного в проекте. Учитывая это, имя свойства также должно содержать хотя бы одно подчеркивание. Это ограничение существует для того, чтобы в проектах поощрять именование пользовательских свойств с префиксом, характерным для конкретного проекта.

-9

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

10.2. Глобальные свойства

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

В дополнение к общим командам set_property() и get_property(), CMake также предоставляет функцию get_cmake_property() для запроса глобальных сущностей. Это нечто большее чем просто сокращение get_property(), хотя его можно использовать для получения значения любого глобального свойства.

-10

Как и в случае с get_property(), resultVar - это имя переменной, в которую будет сохранено значение запрашиваемого свойства после возврата команды. Аргумент property может быть именем любого глобального свойства или одним из следующих псевдосвойств:

  • VARIABLES Возвращает список всех обычных (т.е. не кэшированных) переменных.
  • CACHE_VARIABLES Возвращает список всех переменных кэша.
  • COMMANDS Возвращает список всех определенных команд, функций и макросов. Команды предопределены CMake, в то время как функции и макросы могут быть определены либо CMake (обычно через модули), либо непосредственно в проекте. Некоторые из возвращаемых имен могут соответствовать недокументированным или внутренним сущностям, не предназначенным для прямого использования. Имена могут быть возвращены в верхнем/нижнем регистре, отличном от того, в котором они были изначально определены.
  • MACROS Возвращает список только определенных макросов. Это будет подмножество того, что возвращает псевдосвойство COMMANDS, но обратите внимание, что верхний/нижний регистр имен может отличаться.
  • COMPONENTS Возвращает список всех компонентов, определенных командами install(), которые рассматриваются в главе 35, «Установка».

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

10.3. Свойства каталога

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

Для удобства CMake предоставляет специальные команды для установки и получения свойств каталогов, которые немного более лаконичны, чем их общие аналоги:

-11

Будучи более лаконичной, первая команда, специфичная только для каталогов не имеет опции APPEND или APPEND_STRING. Это означает, что она может быть использована только для установки или замены свойства, ее нельзя использовать для добавления к существующему свойству напрямую. Еще одним ограничением этой команды по сравнению с более общей set_property() является то, что она всегда применяется к текущему каталогу. Проекты могут использовать эту более специфичную форму там, где это удобно, и использовать общую форму в других местах, или для согласованности можно использовать общую форму везде. Ни один из подходов не является более правильным, это скорее вопрос предпочтений.

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

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

10.4. Свойства цели

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

В CMake появилось несколько методов для работы со свойствами цели. Помимо общих команд set_property() и get_property(), CMake также предоставляет некоторые эквиваленты, специфичные для конкретной цели:

-12

Как и команда set_directory_properties(), set_target_properties() не обладает полной гибкостью set_property(), но обеспечивает более простой синтаксис для распространенных случаев. Команда set_target_properties() не поддерживает добавление к существующим значениям свойств, и если для данного свойства необходимо указать значение в виде списка, команда set_target_properties() требует, чтобы это значение было указано в строковой форме, например, «this;is;a;list».

Команда get_target_property() - это упрощенная версия get_property(). Она направлена исключительно на предоставление простого способа получения значения свойства цели и, по сути, является сокращенной версией общей команды.

Помимо общих и специфических для конкретной цели геттеров и сеттеров свойств, в CMake есть ряд других команд, изменяющих свойства цели. В частности, семейство команд target_...() является важной частью CMake, и все проекты, кроме самых тривиальных, обычно используют их. Эти команды определяют не только свойства конкретной цели, но и то, как эта информация может быть передана другим целям, связанным с ней. В главе 16 «Инструментарий компилятора и компоновщика» подробно рассматриваются эти команды и то, как они связаны со свойствами цели.

10.5. Свойства исходников

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

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

-13

Опять же, для сеттера не предусмотрено никакой функциональности APPEND, а геттер - это просто синтаксическое сокращение для общей команды get_property(), не предлагающее никакой новой функциональности. В следующем примере показано, как установить свойство для исходного файла, в данном случае для предотвращения его объединения с другими исходниками в режиме сборки unity (обсуждается в разделе 26.1, «Модульная сборка»):

-14

В CMake 3.17 и более ранних версиях свойства исходника видны только целям, определенным в той же области видимости каталога. Если установка свойства исходника происходит в другой области видимости каталога, цель не увидит изменения этого свойства и, следовательно, компиляция и т. д. этого исходного файла не будет затронута. В CMake 3.18 и более поздних версиях доступны дополнительные опции для указания области видимости каталога, в котором следует искать или применять свойства исходного файла. Ниже показан полный набор опций, доступных для установки свойств исходного файла в CMake 3.18 или более поздней версии:

-15

Опция DIRECTORY может быть использована для указания одного или нескольких каталогов, в которых должны быть установлены свойства исходника. Любые цели, созданные в этих каталогах, будут знать об этих свойствах. Эти каталоги должны быть предварительно добавлены в сборку предыдущим вызовом add_subdirectory(). Любые относительные пути будут рассматриваться как относительные к текущему каталогу исходных файлов.

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

В CMake 3.18 также добавлены аналогичные опции для получения свойств исходных файлов:

-16

При получении свойств исходного файла можно указать не более одной цели или каталога, чтобы определить область каталогов, из которых нужно получить свойство. Если не указаны ни DIRECTORY, ни TARGET_DIRECTORY, предполагается текущий каталог исходника.

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

Разработчики должны знать об одной особенности реализации, которая может стать серьезным препятствием для использования свойств исходников в некоторых ситуациях. В некоторых генераторах CMake (в частности, Unix Makefiles) зависимости между исходными текстами и свойствами исходных текстов сильнее, чем можно было бы ожидать. Если свойства исходника используются для изменения флагов компилятора для конкретных исходных файлов, а не для всей цели, это все равно приведет к перестройке всех исходных текстов цели, а не только затронутого исходного файла. Это ограничение связано с тем, как обрабатываются зависимости в Makefile, где проверка того, изменились ли флаги компилятора каждого отдельного исходника, приводит к непомерно большому снижению производительности. Чтобы избежать этой проблемы, соответствующие зависимости Makefile были реализованы на уровне целей.

Типичный сценарий, в котором у проектов может возникнуть соблазн использовать свойства исходника, - это передача сведений о версии только одному или двум исходникам в виде опций компилятора. Как уже говорилось в разделе 22.2, «Доступ к сведениям о версии из исходного кода», существуют лучшие альтернативы свойствам исходников, которые не имеют проблем с производительностью. Установка некоторых свойств исходников также может снизить производительность, поскольку не позволяет этим исходникам участвовать в режиме сборки Unity (см. раздел 26.1, «Модульная сборка»).

Генератор Xcode также имеет ограничение в поддержке свойств исходников, которое не позволяет ему обрабатывать значения свойств, специфичных для конфигурации. См. раздел 16.7, «Флаги компилятора, специфичные для языка», где показано, насколько это ограничение может быть важным.

10.6. Свойства кэш-переменных

Свойства переменных кэша немного отличаются по назначению от других типов свойств. По большей части свойства переменных кэша направлены на то, как переменные кэша обрабатываются в графическом интерфейсе CMake и консольном инструменте ccmake, а не влияют на сборку каким-либо ощутимым образом. Для работы с ними также не предусмотрено никаких дополнительных команд, поэтому общие команды set_property() и get_property() должны использоваться с ключевым словом CACHE.

В разделе 6.3, «Переменные кэша», обсуждался ряд аспектов переменных кэша, которые в конечном итоге отражаются в свойствах переменных кэша.

  • Каждая переменная кэша имеет тип, который должен быть одним из BOOL, FILEPATH, PATH, STRING или INTERNAL. Этот тип можно получить с помощью функции get_property() с именем свойства TYPE. Тип влияет на то, как графический интерфейс CMake и ccmake представляют эту переменную кэша в пользовательском интерфейсе и какой вид виджет используется для редактирования ее значения. Любая переменная с типом INTERNAL не будет отображаться вообще.
  • Переменная кэша может быть помечена как расширенная с помощью команды mark_as_advanced(), которая на самом деле просто устанавливает булево свойство переменной кэша ADVANCED. И графический интерфейс CMake, и инструмент ccmake предоставляют возможность показывать или скрывать расширенные переменные кэша. Пользователь может выбрать, сосредоточиться ли ему только на основных базовых переменных или просмотреть весь список.
  • Строка справки переменной кэша обычно задается при вызове команды set(), но ее также можно изменить или прочитать с помощью свойства переменной кэша HELPSTRING. Эта справочная строка используется в качестве подсказки в графическом интерфейсе CMake и в качестве однострочной подсказки в инструменте ccmake.
  • Если переменная кэша имеет тип STRING, то CMake GUI будет искать свойство переменной кэша с именем STRINGS. Если оно не пустое, то ожидается, что это будет список допустимых значений для переменной, и CMake GUI представит эту переменную в виде комбинированного окна с этими значениями, а не в виде произвольного виджета для ввода текста. В случае ccmake нажатие клавиши Enter на этой переменной кэша приведет к циклическому перебору предоставленных значений. Обратите внимание, что CMake не требует, чтобы переменная кэша была с одним из значений из свойства STRINGS, это лишь удобство для графического интерфейса CMake и инструментов ccmake. Когда CMake выполняет свой шаг configure, он по-прежнему рассматривает переменную кэша как произвольную строку, поэтому ей можно присвоить любое значение как в командной строке cmake, так и с помощью команд set() в проекте.

10.7. Другие типы свойств

CMake также поддерживает свойства для отдельных тестов и предоставляет обычные для свойств версии команд:

-17

Как и их эквиваленты, это просто немного более сжатые версии общих команд, которые не обладают функцией APPEND, но могут быть более удобными в некоторых обстоятельствах. Тесты подробно рассматриваются в главе 27 «Тестирование».

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

10.8. Рекомендуемые практики

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

  • Всеми свойствами, кроме специальных глобальных псевдосвойств, можно полностью управлять с помощью общей команды set_property(), что делает ее предсказуемой для разработчиков и предлагает гибкую функциональность APPEND там, где это необходимо. Специфические сеттеры свойств могут быть более удобными в некоторых ситуациях, например, позволяя устанавливать несколько свойств одновременно, но отсутствие у них функциональности APPEND может подтолкнуть некоторые проекты к использованию только set_property(). Ни то, ни другое не является правильным или неправильным, хотя распространенной ошибкой является использование специфических сеттеров для замены значения свойства вместо добавления к нему.
  • Для свойств цели настоятельно рекомендуется использовать различные команды target_...(), а не манипулировать свойствами цели напрямую. Эти команды не только управляют свойствами конкретных целей, но и устанавливают отношения зависимости между целями, так что CMake может распространять некоторые свойства автоматически. В главе 16 «Инструментарий компилятора и компоновщика», обсуждается ряд тем, которые подчеркивают, что команды target_...() предпочтительнее.
  • Свойства исходников обеспечивают тонкий контроль над опциями компилятора и т. д. Однако они могут оказывать нежелательное негативное влияние на поведение сборки проекта. В частности, некоторые генераторы CMake могут пересобирать больше, чем это необходимо, при изменении параметров компиляции всего нескольких исходных файлов. Генератор Xcode также имеет ограничения, которые не позволяют ему поддерживать специфические для конфигурации свойства исходных файлов. Проектам следует рассмотреть возможность использования других альтернатив свойствам исходников, если они доступны, например, методов, описанных в разделе 22.2, «Доступ к сведениям о версии из исходного кода».

Это был ознакомительный фрагмент книги Professional CMake: A Practical Guide by Craig Scott