Cmake для цели не указан исполняемый файл

Обновлено: 08.07.2024

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

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

Если нет желания/времени/сил читать весь туториал и Вы используете какой-нибудь QtCreator (или любая другая IDE, умеющая работать с cmake), то:

  • Создайте в IDE проект под cmake
  • Найдите в папке с проектом CMakeFiles.txt
  • Пробегитесь глазами по туториалу, соотнося его с вашим CMakeFiles.txt

Про подключение библиотек рекомендуется все-таки прочитать целиком.

Предположим, у Вас есть исходничек "test.cpp" (// а если нет?)(А если нет, то CMake тебе трогать рано). Для начала нужно создать файлик для cmake, который обычно называют "CMakeLists.txt", и написать туда вот это:

Теперь запускаем (из консоли) в этой папке команду "cmake CMakeLists.txt" (аргументом можно передавать не только файл, но и директорию, в которой он лежит, тогда cmake найдет его сам).

cmake будет использовать переданный (или найденный) файл проекта (тот самый CMakeLists.txt), и в текущей директории будет создавать проект. Проект - это много-много файлов и директорий (примечание: поэтому лучше запускать cmake из другой директории, чтобы можно было, например, быстро удалить все бинарники), из которых нас больше всего интересует Makefile.

Makefile - это файл, нужный для утилиты make. Именно она запускает компиляторы, линковщики и прочие радости. Запускаем make в каталоге сборки (т.е. там же, где Вы запускали cmake). В консоли вылезет примерно такой текст:

А у Вас в папочке появится исполняемый файл "test". Запустите, убедитесь, что это действительно то, что ожидается от компиляции файла "test.cpp".

Поразбираемся с различными возможностями cmake.

Указывайте высокую минимальную версию CMake. Если используемая версия cmake меньше 2.6, он не захочет работать. Писать эту команду всегда - хороший стиль (cmake будет пыхтеть и обижаться, если вы не укажете версию, но собирать всё равно всё будет).

Указывает, что этот cmake-файл является корневым для некоторого проекта. С проектами связаны определенные переменные и поведение cmake (читайте документацию).

В cmake можно создавать текстовые переменные. Команда

запишет в переменную "VARIABLE" значение "The variable's value". Чтобы где-либо использовать значение этой переменной, нужно написать $.

Чтобы добавить к переменной некий текст, можно сделать так:

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

Пример коше'гного проекта со списком сорцов в отдельной переменной:

Эта команда добавит к флагам, используемым при сборке c++-кода, флаги -std=c++11 и -Wall.

Кто не знает: "-std=c++11" включает в gcc поддержку стандарта c++11, "-Wall" говорит gcc выводить все предупреждения (очень советую, помогает отловить много глупых багов и писать аккуратный код).

Если ваша версия GCC меньше, чем 4.7.0, вместо -std=c++11 нужно использовать -std=c++0x.

В GCC 4.8.0 появился флаг -std=c++1y, в котором начинают реализовывать фичи следующего стандарта.

Надеюсь, и это понятно.

Научимся искать и подключать библиотеки при помощи cmake на примере Boost. Для начала установим переменные для буста:

Итак, мы установили флаги. Давайте найдем буст!

Допустим, нам нужны компоненты буста под названием chrono (библиотека для работы со временем) и filesystem (библиотека для работы с файловой системой):

Win, будут искаться только нужные библиотеки, и их расположение будет записано в переменную Boost_LIBRARIES.

Опция "REQUIRED" говорит о том, что библиотека необходима проекту. Без нее cmake решит, что отсутствие данной библиотеки - не так уж и страшно, и будет собирать дальше.

Добавим директории с хедерами буста для поиска в них хедеров:

Итак, осталось найденные библиотеки подключить к исполняемому файлу.

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

Заметим, что эту команду нужно вызывать после того, как создан target сборки (через add_executable).

Итак, полный пример использования всего этого. У нас есть некая директория (отныне считаем ее "/sources"), и в ней лежат исходники

В корне "/" лежит файл "/CMakeLists.txt":

Если Вам что-то в нём не понятно - перечитайте соответствующую информацию выше.

Создаем директорию "/build" (не "/sources/build"), переходим в нее, запускаем в ней "cmake ..". ".." - метка родительской директории. cmake возьмет из нее наш CMakeLists.txt и по нему создаст проект в папке "/build". Чтобы проект собрать, запускаем "make" в той же папке "/build".

Таким образом, в корне у нас есть:

  • CMakeLists.txt
  • директория с исходниками
  • каталог сборки

Все разделено, автоматизировано и удобно.

Пусть в ./ лежит основной проект, а в ./subdir мы хотим сделать либу, а в ./build построить проект.

В ./build запускаем "cmake .. && make" и получаем собранный проект.

Интеграция с cmake у QtCreator не очень тесная, тем не менее, работать с ним можно.

Создаем новый проект без использования Qt, выбираем "Проект на С++ с использованием CMake". Создастся дефолтный файл сборки, который просто добавляет все исходники в директории проекта и компилирует их в один бинарник.

На первом шаге проект нужно сконфигурировать, то есть создать финальный скрипт сборки, запустив cmake <параметры> <путь-к-каталогу> в будущем каталоге сборки.

На втором шаге нужно запустить финальный скрипт. Не вызывайте make ! Утилита cmake сделает это сама:

Структура CMakeLists.txt

В начале главного файла CMakeLists.txt ставят метаинформацию о минимальной версии CMake и названии проекта:

Затем следует список инструкций, служащих для вычисления различных переменных, создания целей сборки, подключения проектов из подкаталогов и так далее. Например, подключить дополнительный CMakeLists.txt из подкаталога можно так:

Целью может стать исполняемый файл, собираемый из исходного кода

Целью также может быть библиотека, статическая или динамическая.

Автогенерация проекта для Visual Studio (Windows)

Если используется Visual C++, то путь немного другой: на шаге конфигурирования создаётся проект для Visual Studio, который затем можно собрать из IDE либо так же из командной строки.

Созданный проект Visual Studio нельзя изменять и использовать постоянно, потому что при генерации проекта используются абсолютные пути и другие неприемлемые для постоянной работы вещи.

Если проект был сконфигурирован успешно, то в каталоге ../myapp-build появятся автоматически сгенерированный BUILD_ALL.sln и проекты для Visual Studio. Их можно открыть к IDE, либо собрать из командной строки с помощью cmake. Названия опций говорят сами за себя:

Зависимости между библиотеками и приложениями

Не используйте директивы include_directories , add_definitions , add_compile_options ! Они меняют глобальные настройки для всех целей, это создаёт проблемы при масштабировании.

  • Используйте target_link_libraries для добавления статических и динамических библиотек, от которых зависит цель
  • Используйте target_include_directories вместо include_directories для добавления путей поиска заголовков, от которых зависит цель
  • Используйте target_compile_definitions вместо add_definitions для добавления макросов, с которыми собирается цель
  • Используйте target_compile_options для добавления специфичных флагов компилятора, с которыми собирается цель

Вы можете выбирать область видимости настройки:

  • PUBLIC делает настройку видимой для текущей цели и для всех зависящих от неё целей
  • PRIVATE делает настройку видимой только для текущей цели
  • INTERFACE делает настройку видимой только для всех зависящих от неё целей

Пример использования областей видимости:

Схема зависимостей условного проекта:

Схема

Выбор стандарта и диалекта C++

Для настройки стандарта и флагов языка C++ не добавляйте флаги напрямую!

В CMake версии 3.8+ вы можете прямо потребовать включить нужный стандарт:

В CMake версии до 3.7 включительно можно использовать set_target_properties (если не работает, то у вас слишком старый CMake):

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

Функции в CMake

CMake позволяет объявлять функции командами function(name) / endfunction() и макросы командами macro(name) / endmacro() . Предпочитайте функции, а не макросы, т.к. у функций есть своя область видимости переменных, а у макросов - нет.

Добавление исходников к цели с target_sources

Лучше добавлять специфичные исходники с помощью target_sources, а не с помощью дополнительных переменных.

Интерфейс к утилитам командной строки

Функция find_package

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

Пример подключения Boost, вызывающего встроенный в CMake скрипт FindBoost:

Пример подключения библиотеки Bullet с помощью встроенного скрипта FindBullet и компоновки с приложением my_app:

Чтобы установить значение опции, отличное от "по умолчанию", необходимо дописать -DНАЗВАНИЕ_ОПЦИИ=Значение к команде конфигурирования. Команда после этого может выглядеть, например, так:

Чтобы сделать такое действие в CLion, необходимо перейти в: Settings -> CMake -> CMake options.

Если используется Hunter (пакетный менеджер), то прописываются его настройки

На этапе конфигурирования, CMake ожидает файл tools/gate/cmake/HunterGate.cmake .

Если этот файл не существует, возможны 2 варианта:

  • Либо (если используется шаблонный репозиторий) необходимо обновить подмодули:

git submodule update --init --recursive

Дополнительные опции для компилятора (могут отсутствовать)

Подключение зависимых библиотек

Затем осуществляется подключение библиотек, в которых нуждается проект (Boost, GTest, Threads и т.д.)

Указания для Hunter о необходимо коллекционирования указанных пакетов

Указания о том, какие пакеты будут использованы (ожидается их наличие)

CONFIG - ключевое слово, показывающее маску названий конфигурационных файлов.

REQUIRED - обязательность подключения пакета (иначе - ошибка).

Добавление целей сборки

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

Указание директорий с заголовочными файлами

Указание библиотек для линковки

Названия библиотек из Hunter, как правило, имеют вид LibraryName::ComponentName .

Данные о библиотеках из пакета, добавленного через find_package хранятся в переменных. Например, для Threads: $

Для сборки тестирования необходимо наличие:

  1. Добавления пакета googletest (GTest в Hunter)
  2. Цели для сборки исполняемого файла
  3. Линковки gtest_main и gtest (GTest::main и GTest::gtest в Hunter) к цели
  4. Включенного тестирования в конфигурационном файле

Можно добавлять несколько тестовых целей под разными названиями. И даже с использованием разных фреймворков.

Для сборки и выполнения тестирования необходимо выполнить следующую команду (ожидается предварительное конфигурирование):

Пример тела конфигурационного файла с тестированием:

Для удобства в CLion необходимо добавить конфигурацию сборки google test.

Начало конфигурации. Как правило, его трогать не надо.

Далее прописываются цели, которые будут проанализированы на процент покрытия.

Конец конфигурации. Как правило, не надо трогать.

Для начала необходимо настроить окружение. Как правило, это не надо трогать

Далее необходимо указать jobs'ы, которые будет выполнять Travis. Jobs содержит название и команды.

К таким относятся, например, правила для веток и для уведомлений. Например:

CMake позволяет быстро и удобно писать сборочные скрипты для кроссплатформенной сборки программных проектов. Под Windows генерируются проекты для Visual Studio, под Linux - Makefiles. Поддерживаются и другие среды разработки.

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

Поиск библиотек

Для подключения внешней библиотеки нужно как минимум указать компилятору путь к заголовочным файлам и линкеру - к самой библиотеке (.lib, .so). CMake может попробовать найти эти пути с помощью команд find_path и find_library, которые в простейшей форме выглядят так:

Если файл с именем имя_заголовочного_файла будет найден, то в переменную ПЕРЕМЕННАЯ1 будет записан полный путь к директории, где этот файл лежит. Если файл с именем имя_библиотеки будет найден, то в переменную ПЕРЕМЕННАЯ2 будет записан полный путь к этому файлу.

После этого эти переменные можно будет подключить к цели сборки:

Но вот только где CMake будет производить поиск? Можно указать несколько стандартных путей, например так:

Это хорошо работает под Линуксом, где все библиотеки обычно лежат по нескольким стандартным путям. Под Windows хуже: нужная библиотека может располагаться, где угодно.

Можно договориться, что при установке библиотеки на компьютер путь к ней будет прописываться в переменную окружения ПЕРЕМЕННАЯ_ОКРУЖЕНИЯ, тогда CMake сможет это использовать:

Если ничего не помогло, то пользователю придется вручную вводить в интерфейсе CMake нужные пути.

Упрощение поиска

Для сложных сторонних библиотек с кучей модулей и версий может понадобиться большое нагромождение команд find_path и find_library. Для удобства эти команды можно выделить в отдельный скрипт с именем FindБИБЛИОТЕКА.cmake.

После этого можно вызывать скрипт командой find_package:

(и не забыть прописать в переменной CMAKE_MODULE_PATH путь к папке со скриптами).

Это серьезно упрощает структуру корневого файла CMakeLists.txt. Также для популярных библиотек уже существуют скрипты для поиска, которые устанавливаются вместе с CMake. Под Линуксом такие скрипты лежат в папке /usr/share/cmake/Modules, а под Windows в папке C:\Program Files (x86)\CMake\share\cmake-3.2\Modules.

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

Импорт целей сборки

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

CMake позволяет это. Для этого необходимо создать «виртуальную» цель сборки и описать её свойства. В терминологии CMake такая цель сборки называется импортированная.

Создать и описать импортированную цель сборки можно с помощью ключевого слова IMPORTED. Пример:

После этого можно подключить эту внешюю цель сборки к нашей цели:

При сборки нашего проекта автоматически будут прописаны пути к заголовочным файлам и библиотеке, определение препроцессора. Этот способ является альтернативным использованию функций find_library и find_path.

Описание импортированной цели сборки удобно вынести в отдельный скрипт. Но лучше, если сама внешняя библиотека сгенерирует этот скрипт за нас. Здесь описано как генерировать такой скрипт автоматически.

Тогда нашему проекту достаточно будет подключить готовый скрипт:

Сборка зависимостей

Но что, если в системе не установлена нужная зависимость? Можно попросить пользователя установить её, либо поручить это CMake-скрипту.

Есть 2 основные стратегии в зависимости от размера внешней библиотеки.

Исходный код маленькой библиотеки можно просто добавить в дерево исходников нашего проекта, например, в подпапку 3rdParty. Далее библиотеку можно подключить в из корневого файла CMakeLists.txt с помощью команды add_subdirectory:

Тогда эта библиотека будет скомпилирована вместе с нашим проектом, и будут доступны её цели сборки.

Если внешняя библиотека слишком большая, то можно использовать команду ExternalProject_Add. Например:

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

Однако, команда имеет и недостатки. Дело в том, что конфигурирование и сборка внешней библиотеки происходят не во время ExternalProject_Add. Они происходят одновременно со сборкой нашего проекта, а значит до начала сборки ни самих библиотек, ни заголовочных файлов, ни скриптов не существует. А это значит, что команды find_library, find_path будут выдавать ошибки при конфигурировании в CMake. Цели сборки внешней библиотеки будут недоступны…

Поэтому команда ExternalProject_Add не является панацеей и её нужно использовать осторожно.

Суперсборка

В обсуждениях в интернете встретил подход к сборке проекта, основанный на ExternalProject_Add, который назвали «суперсборкой».

Идея в том, что и сторонние библиотеки, и наш проект собирать с помощью ExternalProject_Add. Корневой CMakeLists.txt просто содержит вызовы этой команды и всё.

В этом случае скачивание и конфигурирование происходит во время сборки. Когда мы вызываем команду make под Линуксом или запускаем сборку в Visual Studio под Windows, то происходит следующее:

Читайте также: