Cmake windows подключить библиотеку

Oh, and the documentation: It’s extensive but never tells me what I need to know.

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

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

Ошибки?

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

Оглавление

  • Подключение библиотек через FetchContent

    • Библиотеки без CMakeLists.txt

    • Скомпилированные библиотеки

    • Header-only библиотеки

    • Ускорение загрузки библиотек

    • Подключение Vulkan

  • Настройка и компиляция исходников

    • Автоматическое добавление исходников к цели

    • Добавление define во все исходники

    • Предварительная компиляция заголовков

  • Компиляция шейдеров

Подключение библиотек через FetchContent

Много моих товарищей, которые только начинали изучение C++, тратили ужасно большое количество времени каждый раз, когда надо было установить библиотеку. Здесь я покажу только один из способов — CMake + FetchContent, однако есть и другие: vcpkg, скачивание вручную, github modules…

Стандартная последовательность действий:

  1. Включить модуль FetchContent.

include(FetchContent)
  1. Указать репозиторий, откуда будет загружаться библиотека, и дать этому контенту имя.

FetchContent_Declare(
  <name>
  GIT_REPOSITORY <url>
  GIT_TAG <tag>
)

<tag> может быть как хэшем коммита, так и тэгом.

  1. Загрузить и интегрировать библиотеку (добавить цели) в проект.

FetchContent_MakeAvailable(<name>)
  1. Связать библиотеку с нужной целью.

target_link_libraries(<your_target> <library_target>)

<library_target> — это цель, которую создала библиотека. Её можно найти в примерах использования библиотеки, в README.md (там вообще можно много найти) или в CMakeLists.txt. В последнем случае надо искать строки вида add_library(<library_target> ...).

Пример

include(FetchContent)

FetchContent_Declare(
        glfw
        GIT_REPOSITORY https://github.com/glfw/glfw.git
        GIT_TAG 3.4
)

FetchContent_MakeAvailable(
        glfw
)

target_link_libraries(engine PRIVATE glfw)

Такая последовательность действует в большинстве случаев, но не во всех.

Библиотеки без CMakeLists.txt

Если у библиотеки нет CMakeLists.txt, то можно её собрать самостоятельно. Нужно написать такой же код, который нужен для сборки вашего проекта с некоторыми нюансами.

Мы как и с другими библиотеками после использование FetchContent_MakeAvailable создаём цель.

add_library(imgui_l STATIC)

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

Решить эту проблему помогает то, что FetchContent_MakeAvailable, создаёт переменную вида <name>_SOURCE_DIR для каждого имени из FetchContent_Declare, которая содержит путь до скачанного репозитория. Поэтому мы можем использовать её для того, чтобы указать нужные файлы.

target_include_directories(imgui_l PRIVATE ${imgui_SOURCE_DIR})

target_sources(imgui_l PRIVATE
        ${imgui_SOURCE_DIR}/imgui.h
        ${imgui_SOURCE_DIR}/imgui.cpp

        ${imgui_SOURCE_DIR}/imgui_demo.cpp
        ${imgui_SOURCE_DIR}/imgui_draw.cpp
        ${imgui_SOURCE_DIR}/imgui_widgets.cpp

        ${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp
)

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

target_link_libraries(imgui_l PRIVATE Vulkan::Vulkan)

И в конце как и остальные библиотеки нужно связать с целью.

target_link_libraries(engine PRIVATE imgui_l)

Однако иногда очень сложно написать самому такой скрипт, так как предполагалась сборка библиотеки с помощью других инструментов.

Скомпилированные библиотеки

Нередко разработчики предоставляют файлы .a (используется в Unix-подобных системах (например, Linux, macOS) и компиляторами, такими как GCC ) или .lib (используется в Windows и компиляторами, такими как MSVC ) — статические библиотеки.

Их обычно скачивают и кладут в отдельную папку lib или external в корне проекта, однако помимо самих файлов библиотеки ещё нужно положить куда-то заголовки этой библиотеки (обычно папка include).

Соответственно в самом CMake нужно сделать:

target_link_libraries(engine PRIVATE
  ${CMAKE_CURRENT_SOURCE_DIR}/lib/lib-mingw-w64/libglfw3.a)
# Or
target_link_libraries(engine PRIVATE
  ${CMAKE_CURRENT_SOURCE_DIR}/lib/lib-vc2022/glfw3.lib)

target_include_directories(engine PRIVATE 
  ${CMAKE_CURRENT_SOURCE_DIR}/include)

Полный пример

Здесь используется способ загрузки, описанный в Ускорение загрузки библиотек.

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

FetchContent_Declare(
        glfw
        URL https://github.com/glfw/glfw/releases/download/3.4/glfw-3.4.bin.WIN64.zip
)

FetchContent_MakeAvailable(
        glfw
)

target_link_libraries(engine PRIVATE ${glfw_SOURCE_DIR}/lib-mingw-w64/libglfw3.a)
target_include_directories(engine PRIVATE
        ${glfw_SOURCE_DIR}/include)

Такой способ имеет свои минусы: мы не можем настроить определения или флаги компиляции для конкретно этой библиотеки. Поэтому будет правильней добавить отдельную цель для этой библиотеки (этот способ я взял из комментариев к статье).

add_library(glfw STATIC IMPORTED)

Параметр IMPORTED указывает на то, что библиотека уже скомпилирована.

Для этой цели можно также указать target_link_libraries и target_include_directories, но с модификатором PUBLIC, так как нам нужно прокинуть эти заголовки в места, где используется эта библиотека. Однако более корректным будет использовать свойства.

set_target_properties(glfw PROPERTIES
  IMPORTED_LOCATION "${glfw_SOURCE_DIR}/lib-mingw-w64/libglfw3.a"
  INTERFACE_INCLUDE_DIRECTORIES "${glfw_SOURCE_DIR}/include"
)

Остаётся как и во всех случаях только подключить эту библиотеку.

target_link_libraries(engine PRIVATE glfw)

Header-only библиотеки

Ещё один возможный вариант — репозиторий только с заголовками. Однако ничего сложного тут нет: единственное, что нужно — сделать директорию с ними доступной в проекте после использования FetchContent_MakeAvailable.

target_include_directories(engine PRIVATE ${stb_SOURCE_DIR})

Или как и в предыдущем случае создать библиотеку, но в отличие от импортированной нам нужно использовать параметр INTERFACE, так как библиотека состоит из заголовков и её не нужно компилировать.

add_library(stb INTERFACE)
set_target_properties(stb PROPERTIES
        INTERFACE_INCLUDE_DIRECTORIES ${stb_SOURCE_DIR}
)

Ускорение загрузки библиотек

Так как описанный изначально способ скачивания библиотек скачивает не только нужный вам коммит, а всю историю репозитория, из-за чего первый раз проект CMake может очень долго загружаться. Посмотреть, что происходит при загрузке библиотеки сначала нужно отключить FETCHCONTENT_QUIET (по умолчанию стоит ON), что перестанет подавлять вывод информации. А после уже в FetchContent_Declare указать, что мы хотим получать информацию о прогрессе.

set(FETCHCONTENT_QUIET OFF)
FetchContent_Declare(
        json
        GIT_REPOSITORY https://github.com/nlohmann/json
        GIT_TAG v3.11.3
        GIT_PROGRESS ON
)
Receiving objects: 100% (44890/44890), 193.92 MiB | 1.32 MiB/s, done.

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

Receiving objects: 100% (8981/8981), 161.64 MiB | 3.79 MiB/s, done.

Поэтому есть другое решение: скачивать архив по конкретной ссылке.

FetchContent_Declare(
        json
        URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)

Чтобы найти нужную ссылку, мы заходим на страницу репозитория, в раздел релизов и копируем ссылку на файл с исходниками. Так мы скачаем только нужный коммит. Остальная часть алгоритма будет прежней (FetchContent_MakeAvailable и target_link_libraries()).

Подключение Vulkan

Так как Vulkan — это полноценный SDK (набор инструментов разработки), то он содержит в себе библиотеки, заголовки, документацию, примеры и инструменты. Поэтому его приходится устанавливать отдельно как программу. Стандартная последовательность действий такова:

  • Скачайте Vulkan SDK с официального сайта LunarG Vulkan SDK.

  • Установить его.

  • Найти и подключить в CMake.

find_package(Vulkan REQUIRED)
target_link_libraries(engine PRIVATE Vulkan::Vulkan)

Однако CMake может не найти этот пакет, так как что-то произошло с переменной окружения VULKAN_SDK, которая указывает путь до установленного Vulkan. Эта переменная автоматически должна быть создана при установке, поэтому если она отсутствует, то её надо создать.

Проблемы с CLion

Лично я столкнулся с немного иной проблемой: в системных переменных окружения VULKAN_SDK была, но CLion её не подгрузил и в Settings -> Build, Execution, Deployment -> CMake в используемом профиле Environment её не было (надо нажать на кнопку странички, чтобы просмотреть переменные). Позже CLion её подгрузит, но на время можно самостоятельно добавить её.

VULKAN_SDK=<path_to_vulkan>

Настройка и компиляция исходников

Автоматическое добавление исходников к цели

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

file(GLOB_RECURSE CPP_SOURCE_FILES CONFIGURE_DEPENDS
        "${PROJECT_SOURCE_DIR}/src/*.cpp"
)

GLOB_RECURSE собирает в список все файлы, соответствующие предоставленному выражению: в нашем случае все .cpp файлы. Стоит отметить, что GLOB_RECURSE рекурсивно проверяет все директории, в то время как просто GLOB этого не делает.

Ещё одна важная деталь — это CONFIGURE_DEPENDS. Этот параметр пересобирает проект CMake, если значение переменной (CPP_SOURCE_FILES) должно измениться.

Однако также стоит обратить внимание на критику GLOB: в документации предупреждают, что CONFIGURE_DEPENDS работает надёжно не на всех генераторах, а также отмечают, что эта проверка всё равно требует время при каждой сборке. Для средних и больших проектов эти факторы могут играть большую роль, из-за чего в интернете часто появляется вопрос «Why is cmake file GLOB evil?», но об этом способе стоит упомянуть, так как он сильно облегчает жизнь на первых порах.

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

target_include_directories(engine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")

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

Такие библиотеки как GLFW и GLM требуют перед каждым включением заголовка прописывать директиву define для настройки каких-либо параметров, которые по факту едины для всего проекта.

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>

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

target_compile_definitions(engine PRIVATE
        GLM_FORCE_DEPTH_ZERO_TO_ONE
        GLM_FORCE_RADIANS

        GLFW_INCLUDE_VULKAN
)

Предварительная компиляция заголовков

Хороший способ сократить время компиляции — это выделить все заголовки, которые практически не меняются (часто берут именно из библиотек) и часто используются, и сделать для них предкомпиляцию.

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

target_precompile_headers(engine PRIVATE
        <optional>
        <memory>
        <string>
        <vector>
        <unordered_map>
        <glm/mat4x4.hpp>
        <glm/vec4.hpp>
        <vulkan/vulkan.h>
)

На просторах интернета нашёл вот такой небольшой список, подходящий моему проекту.

Компиляция шейдеров

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

file(GLOB_RECURSE GLSL_SOURCE_FILES CONFIGURE_DEPENDS
        "${PROJECT_SOURCE_DIR}/shaders/*.frag"
        "${PROJECT_SOURCE_DIR}/shaders/*.vert"
        "${PROJECT_SOURCE_DIR}/shaders/*.comp"
)

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

foreach (GLSL IN LISTS GLSL_SOURCE_FILES)
  # ...
endforeach (GLSL)

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

file(RELATIVE_PATH FILE_NAME "${PROJECT_SOURCE_DIR}/shaders/" "${GLSL}")
set(SPIRV "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/compiled_shaders/${FILE_NAME}.spv")

Далее нужно определить команду, которая создаст наш файл.

add_custom_command(
            OUTPUT ${SPIRV}
            COMMAND Vulkan::glslc ${GLSL} -o ${SPIRV}
            DEPENDS ${GLSL})

DEPENDS — файл, при изменении которого будет исполняться команда.

COMMAND — сама команда, которую мы фактически можем вставить в консоль с параметрами, использующимися при запуске этой команды. В данном случае CMake сам подставит вместо Vulkan::glslc путь к исполняемому файлу.

glslc — это компонент (программа), который был найден в процессе выполнения команды find_package(Vulkan REQUIRED), а конкретнее инструмент компиляции и оптимизации шейдеров. Однако этот инструмент — только обёртка для glslangValidator (компилятор) и spirv-opt (оптимизатор), которые также можно использовать.

OUTPUT — файл, который будет получен в результате выполнения команды. Этот параметр указывается, чтобы CMake автоматически мог построить зависимости между командой и целью из той же области видимости. Сама по себе команда не будет выполняться, так как не прикреплена к цели, поэтому важно создать зависимость какой-либо цели от сгенерированных файлов. Для этого мы соберём их в список и создадим цель, зависящую от этих файлов (параметр DEPENDS) и которая будет частью стандартной сборки (параметр ALL).

foreach (GLSL IN LISTS GLSL_SOURCE_FILES)
    file(RELATIVE_PATH FILE_NAME "${PROJECT_SOURCE_DIR}/shaders/" "${GLSL}")
    set(SPIRV "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/compiled_shaders/${FILE_NAME}.spv")

    add_custom_command(
            OUTPUT ${SPIRV}
            COMMAND Vulkan::glslc ${GLSL} -o ${SPIRV}
            DEPENDS ${GLSL})

    list(APPEND SPIRV_BINARY_FILES ${SPIRV})
endforeach (GLSL)

add_custom_target(
        ShadersTarget ALL
        DEPENDS ${SPIRV_BINARY_FILES}
)

Заключение

Я потратил довольно много времени на настройку, а не на программирование, что меня немного удручает, однако в будущем с этими знаниями я смогу намного быстрее приступить к непосредственной разработке. А потому в дополнение хочу рассказать ещё об одной вещи, на которую я потратил очень много времени.

Многие слышали фразу «исключения очень медленные». Но также многие (как я раньше или мои товарищи) неправильно её понимают: нам кажется, что простое наличие исключений в коде делает его значительно медленней (ведь там генерируется какая-то магия для их обработки), однако сейчас используется модель zero-cost exceptions, которая позволяет избавиться от накладных затрат в коде, который не выбрасывает исключения. Из этого следует, что пытаться отключить полностью их с помощью флагов компилятора практически бессмысленно (-fno-exceptions). Однако кинуть исключение всё равно очень дорого, а значит делать это стоит только в исключительных ситуациях (а-ля не может инициализироваться какая-то критически важная библиотека), в остальных ситуациях нужно обходиться кодами возврата или Result Type.

Надеюсь кому-то эта статья поможет. Спасибо, что дочитали.


Содержание

См. также статью Современный CMake: 10 советов по улучшению скриптов сборки.

Терминология

  • Файл CMakeLists.txt служит скриптом (рецептом, сценарием) сборки проекта. Обычно один такой файл собирает все исходники в своём каталоге и в подкаталогах, при этом подкаталоги могут содержать, а могут не содержать дочерние файлы CMakeLists.txt. С точки зрения IDE, таких как CLion или Visual Studio, файл CMakeLists.txt также служит проектом, с которым работает программист внутри IDE.
  • В cmake есть “цель” (“target”) — компонент, который следует собрать. Компонент может быть исполняемым файлом, так и статической либо динамической библиотекой.
  • В cmake есть “проект” (“project”) — это набор компонентов, по смыслу похожий на Solution в Visual Studio.
  • В cmake есть “флаги” (flags) — это аргументы командной строки для компилятора, компоновщика и других утилит, вызываемых при сборке.
  • В cmake есть переменные, и в процессе интерпретации файла CMakeLists.txt система сборки cmake вычисляет ряд встроенных переменных для каждой цели, тем самым получая флаги. Затем cmake создаёт вторичный скрипт сборки, который будет напрямую вызывать компилятор, компоновщик и другие утилиты с вычисленными флагами.

Сборка проекта из командной строки (Linux)

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

# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Создадим каталог `myapp-release` и перейдём в него.
mkdir --parents ../myapp-release
cd ../myapp-release

# Сконфигурируем проект для сборки в Release.
# Флаг установит опцию CMAKE_BUILD_TYPE в значение "Release",
#  интерпретатор cmake считает это переключением на Release конфигурацию.
cmake -DCMAKE_BUILD_TYPE=Release ../myapp

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

# Просим CMake запустить сборку в каталоге `myapp-release`
# Можно добавить флаг `--target mylibrary` для сборки только mylibrary
# Можно добавить флаг `--clean-first`, чтобы в начале новой сборки
#  стирать остатки предыдущей.
cmake --build .

# Аналогичный способ для GNU/Linux. Его по привычке советуют в сети, хотя
#  make доступен не всегда, а cmake --build работает на всех платформах.
make

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

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

# Указывайте последнюю доступную вам версию CMake.
cmake_minimum_required(VERSION 3.8)

# Синтаксис: project(<имя> VERSION <версия> LANGUAGES CXX),
#  теги VERSION и LANGUAGES необязательные.
project(libmodel3d)

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

# Простая версия: подключает скрипт по пути <подкаталог>/CMakeLists.txt
add_subdirectory(<подкаталог>)

# Расширенная версия: дополнительно установит подкаталог сборки подпроекта
add_subdirectory(<подкаталог> <подкаталог сборки>)

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

# Синтаксис: add_executable(<имя> <список исходников...>)
# Добавлять `.h` необязательно, но лучше для работы из IDE:
#  - IDE определит заголовок как часть проекта
#  - cmake будет отслеживать изменения в заголовке и пересобирать
#    проект при изменениях.
add_executable(pngconverter main.cpp PngReader.h PngReader.cpp)

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

# Синтаксис: add_library(<имя> [STATIC|SHARED|INTERFACE] <список исходников...>)

# Тип библиотеки (staic или shared) зависит от параметров сборки
add_library(libpngutils PngUtils.h PngUtils.cpp)

# Тип библиотеки: static
add_library(libpngtools STATIC PngTools.h PngTools.cpp)

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

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

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

# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Сгенерируем проект Visual Studio для сборки.
mkdir --parents ../myapp-build
cd ../myapp-build

# Конфигурируем для сборки с Visual Studio 2017, версия тулчейна v140
cmake -G "Visual Studio 2017"

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

cmake --build . \
    --target myapp \
    --config Release \
    --clean-first

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

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

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

Пример:

# Добавляем цель - статическую библиотеку
add_library(mylibrary STATIC \
    ColorDialog.h ColorDialog.cpp \
    ColorPanel.h ColorPanel.cpp)

# ! Осторожно - непереносимый код !
# Добавляем к цели путь поиска заголовков /usr/include/wx-3.0
# Лучше использовать find_package для получения пути к заголовкам.
target_include_directories(mylibrary /usr/include/wx-3.0)

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

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

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

# Каталог include будет добавлен к путям поиска заголовков в текущей цели и во всех зависимых целях
target_include_directories(myTarget PUBLIC ./include)

# Каталог src будет добавлен к путям поиска заголовков только в текущей цели
target_include_directories(myTarget PUBLIC ./src)

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

Схема

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

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

# ! Устаревший метод - прямое указание флага !
target_compile_options(hello PRIVATE -std=c++11)

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

# Источник: https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html

# Включаем C++ 2017
target_compile_features(myapp cxx_std_17)

# Альтернатива: включаем C++ 2014
target_compile_features(myapp cxx_std_14)

# Альтернатива: включаем C++ 2011
target_compile_features(myapp cxx_std_11)

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

# Стандарт: C++ 2014, расширения языка от производителя компилятора отключены
set_target_properties(myapp PROPERTIES
    CXX_STANDARD 14
    CXX_STANDARD_REQUIRED YES
    CXX_EXTENSIONS NO
)

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

# API библиотеки (т.е. заголовки) требуют лямбда-функций и override,
#  реализация библиотеки требует ещё и range-based for.
target_compile_features(mylibrary PUBLIC cxx_override cxx_lambdas PRIVATE cxx_range_for)

Функции в CMake

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

function(hello_get_something var_name)
  ...
  # Установить переменную в области видимости вызывающей стороны
  #  можно с помощью тега PARENT_SCOPE
  set(${var_name} ${ret} PARENT_SCOPE)
endfunction()

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

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

add_library(hello hello.cxx)

if(WIN32)
  target_sources(hello PRIVATE system_win.cxx)
elseif(UNIX)
  target_sources(hello PRIVATE system_posix.cxx)
else()
  target_sources(hello PRIVATE system_generic.cxx)
endif()

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

Подробнее см. Command-Line Tool Mode

# Создать каталог debug-build
cmake -E make_directory debug-build
# Перейти в каталог debug-build
cmake -E chdir debug-build

Функция find_package

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

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

# Весь Boost без указания конкретных компонентов
find_package(Boost REQUIRED)
# Теперь доступны переменные
# - Boost_INCLUDE_DIRS: пути к заголовочным файлам
# - Boost_LIBRARY_DIRS: пути к статическим/динамическим библиотекам
# - Boost_LIBRARIES: список библиотек для компоновщика
# - Boost_<C>_LIBRARY: библиотека для компоновки с компонентом <C> библиотек Boost

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

# Вызываем встроенный скрипт FindBullet.cmake
find_package(Bullet REQUIRED)

# Добавляем пути поиска заголовков к цели my_app
target_include_directories(my_app ${BULLET_INCLUDE_DIRS})

# Добавляем список библиотек для компоновки с целью my_app
target_link_libraries(my_app ${BULLET_LIBRARIES})

Эта статья находится в разработке!

Содержание

  • 1 Что это и зачем нужно
    • 1.1 Краткое описание
  • 2 Старт
  • 3 Подробное описание
    • 3.1 Указание необходимой версии cmake
    • 3.2 Название проекта
    • 3.3 Переменные
    • 3.4 Устанавливаем команды компилятору
    • 3.5 Папка с хедерами
    • 3.6 Самое важное — подключение библиотек
    • 3.7 Пример хорошего CMakeLists.txt и где он будет лежать
    • 3.8 Как создать библиотеку в поддиректории и слинковать ее с основной программой
  • 4 Как использовать CMake в связке с QtCreator
    • 4.1 Как добавить header в проект, чтобы его было видно в списке файлов

Что это и зачем нужно

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

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

Краткое описание

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

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

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

Старт

Предполагается, что найти и скачать сам cmake ты, %username%, в состоянии. //а если нет?

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

add_executable(test test.cpp)

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

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

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

Scanning dependencies of target test
[100%] Building CXX object CMakeFiles/test.dir/test.cpp.o
Linking CXX executable test
[100%] Built target test

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

Подробное описание

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

Указание необходимой версии cmake

cmake_minimum_required(VERSION 2.6)

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

Название проекта

project(visualization)

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

Переменные

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

set(VARIABLE The variable's value)

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

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

set(VARIABLE "${VARIABLE} new text")

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

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

cmake_minimum_required(VERSION 2.6)

set(SOURCES test.cpp lib1.cpp lib2.cpp)

add_executable(test ${SOURCES})

Устанавливаем команды компилятору

add_definitions(-DSOME_IMPORTANT_DEFINITION)

Эта команда используется для установки дефайнов, которыe можно проверить в коде через, например, #ifdef SOME_IMPORTANT_DEFINITION.

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

Эта команда добавит к флагам, используемым при сборке 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, в котором начинают реализовывать фичи следующего стандарта.

Папка с хедерами

Допустим, Вы хотите, чтобы хедеры (файлики, подключаемые через #include) искались еще и в каталогах «headers/» и «more_headers/»:

include_directories("headers/" "more_headers/")

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

Самое важное — подключение библиотек

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

set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)

Первое — мы не хотим, чтобы буст подключался к нам статически (т.е. хотим динамическую линковку). Если ты, %username%, не знаешь, что это, пока просто забей и используй этот флаг так, как написано. Но в ближайшее время узнай, о чем речь.
Второй флаг разрешает бусту внутри своих магических реализаций использовать треды для распараллеливания и прочих радостей.

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

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

find_package(Boost COMPONENTS chrono filesystem REQUIRED)

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

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

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

include_directories(${Boost_INCLUDE_DIRS})

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

target_link_libraries(test ${Boost_LIBRARIES})

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

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

Пример хорошего CMakeLists.txt и где он будет лежать

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

/sources/lib1/main.cpp
/sources/lib2/main.cpp
/sources/main.cpp

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

cmake_minimum_required(VERSION 2.8)
project(cmake-example)

set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
find_package(Boost COMPONENTS chrono filesystem REQUIRED)

set(CMAKE_CXX_FLAGS "$\$${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

set(SRC_LIST lib1/main.cpp lib2/main.cpp main.cpp)

add_executable($\$${PROJECT_NAME} $\$${SRC_LIST})
target_link_libraries($\$${PROJECT_NAME} $\$${Boost_LIBRARIES})

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

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

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

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

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

Как создать библиотеку в поддиректории и слинковать ее с основной программой

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

./subdir/CMakeLists.txt

project(MegaLibrary)
set(SOURCES lib.cpp)
set(HEADERS lib.h)
add_library(lib $\$${SOURCES} $\$${HEADERS})
target_include_directories(lib PUBLIC $\$${CMAKE_CURRENT_SOURCE_DIR})

./CMakeLists.txt

project(MainProject)
set(MAIN_PROJECT_SRC_LIST main)
# Other stuff
add_executable(main $\$${MAIN_PROJECT_SRC_LIST})
add_subdirectory(subdir)
target_link_libraries(main lib)

Теперь можно в файлах основного проекта делать #include «lib.h» (см. документацию по target_include_directories).

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

Как использовать CMake в связке с QtCreator

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

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

Если вы создали файл header.h в директорию проекта, просто строчку

add_executable($\$${PROJECT_NAME} $\$${SRC_LIST})

измените на

add_executable($\$${PROJECT_NAME} $\$${SRC_LIST} "header.h")

In Part 1 of our series on CMake, an open-source, cross-platform family of tools designed to build, test and package software, I provided an overview and showed you how to build a basic CMake project. In today’s Part 2, I’ll show you how to find libraries and link them to your application.

When working on almost any code project you’ll probably want to use another library. There are several kinds of libraries you may need to use. Here’s a look at different types of libraries, and insight on how to use them within an example CMake project.

Libraries can either be compiled within your source code (embedded) or made available by the system as a so-called “system library.” There’s are the most common types of libraries:

Embedded libraries, which embed their resulting code in the final binary, whether that’s an application or another library.

Source-only libraries, which are meant to be compiled within your code.

System libraries, which are libraries on your computer system. Along with the library you will have headers or a .lib file to use when including files from the lib.

How CMake Handles Different Types of Libraries

Embedded libraries

These are the easiest to work with: just copy them into your source tree and add them to your project as if they were any other source file. Any Library that is a single header is this kind of library an example of one would be Json.h. Consider placing them in a sub directory to keep your source tree clean.

Source-only libraries as part of the main application

For this type method you only need to copy the source and add them to the list of sources for the target. When you use the object, you need to use a normal include statement that includes the header via a relative path. For this, I’ll use a “numbers” library as an example:

Ex:  Folder Structure

exapp/main.cpp

exapp/CMakeList.txt

exapp/numbers/numbers.cpp

exapp/numbers/numbers.h 

Ex: CMakeList.txt

add_executable(sampleApp
main.cpp
            numbers/numbers.cpp
            numbers/numbers.h
)

Ex: Including the library main.cpp 

  #include «numbers/numbers.h» 

This will build the library into our example application and will not require linking to any new libraries other than the libraries numbers needs to build. For our example, numbers has no library dependencies.

Source-only libraries as a separate library

You have the option to build this as a separate static (or shared) library and link it to your application. This provides some benefits, such as:

  • Reducing compilation time while developing. (As long as you do not touch the library’s code, you only need to compile it once.)
  • You can re-use the library for different parts of the project without recompiling. (For example you could use the library in your units tests by simply linking to it.)
  • More easily keep the code well-structured.

Using the same library as before, let’s look at what we can do to make “numbers” its own library. The first thing to do is start to clean up the main CMakeLists.txt. We can accomplish this by removing the numbers source files from our target and instead use add_subdirectory to add the numbers directory. This will look for a CMakeLists.txt file in that directory so we will write that next. 

We also need to link our target to the library that we are going to create. To do this, we call target_link_libraries. Libraries can be linked PUBLIC, PRIVATE or INTERFACE. 

  • PUBLIC: Used by the library and its consumers.
  • PRIVATE: Only needed by the library.
  • INTERFACE: Not used by the library but needed for consumers.

After the edits our main CMakeLists.txt will look like this: 

  cmake_minimum_required(VERSION 3.5)
  project(numConvert LANGUAGES CXX)
  add_subdirectory(numbers)
  add_executable(numConvert main.cpp)
  target_link_libraries(numConvert PRIVATE numbers)

Inside the numbers directory we need to create a new CMakeLists.txt that will contain instructions for building our library. It only needs to have a single add_library call. This is how we make library target:

   add_library(numbers numbers.cpp numbers.h)

This will build our library into the target as a static library. We use the same “include” statement for numbers used in the previous example. When we talk later about creating shippable libraries, we’ll explore methods that can be used to modify the way we include the library headers. 

(Note: You can use the same setup for other libraries that you plan to include, as long as they already have a CMake configuration. However it’s not always ideal to build everything. One more note: embedded dynamic (shared) libraries have a similar approach, but this is a far more advanced concept we will cover in a future blog.)

System libraries

This is going to be the most common way you will use libraries. System libraries normally provide not only a way to tell you where they are but also how to properly use them. For instance, they let you know whether you need other libraries, flags required to link, etc. The library also typically provides this information in a file. Of course, CMake can provide .cmake files, but it can also handle other methods like pkg-config (.pc) files. 

For the purposes of this blog, I’ll focus on the first case. The main thing we will use here is find_package. Most simply, we can do something like find_package(zlib REQUIRED) and zlib will need to be found before we can build our project. 

After configuration, we can use libraries and link libraries from it to our target. This requires that the package you’re looking for is already built and installed. If CMake can not find zlib then you can not build the project. In some situations, you may want to add the HINTS option to your call to provide a “hint” as to where Cmake should look for the findZlib.cmake file. 

To link, you can add zlib to your list. This will not ship the library when deploying your application. I’ll talk more on this topic later in our blog series. 

When find_package is called, CMake checks several directories for a find<PACKAGENAME>.cmake or <PACKAGE>Config.cmake. When found it will be used to set all the internal variables that Cmake will use for the library. 

Package search starts in the CMAKE_PREFIX_PATH and will check several directories from here. The `Package Config` search will check some additional directories, as well. In the case that CMake is not able to find the package, you will need to provide an option of <PACKAGE>_DIR when configuring to help find it. 

In addition, you can use the version number and HINTS in the find_package call to limit the search to a version compatible with the version you provided, and to also check the paths listed as hints in addition to the normal search path. 

If we planned to include a copy of zlib 1.2.11 on our system but had not yet installed it, in our CMakeLists.txt we could use:

find_package(zlib 1.2.11 REQUIRED HINTS C:\zlib\lib\cmake)

Or if we know exactly where it is we can use the configure option -DZLIB_ROOT=C:\zlib to force it to look in that path for the “ROOT” of the zlib install. 

The FindZlib.cmake module is part of CMake. It is often useful to check the CMake script that will be finding the package so you have an idea of the targets it creates. This makes it easier to link the package. In this case ZLIB::ZLIB is the target name we would use for linking and we see that some other variables like ZLIB_FOUND are also set by the FindZlib.cmake module. 

CMake provides a few of its own Find modules. If your project is not one of those for which CMake provides a find module, you must provide one with your library. 

A Word About Qt

I often use Qt with my projects. Since the release of Qt 5 in 2012, CMake has been an option. And since Qt 6’s release in 2020, it has become the preferred method of making a Qt project. 

The first thing you should know about using Qt with CMake are the different ways to find Qt: 

Use find_package(Qt5 … ) to find Qt 5 

Use find_package(Qt6… )to find Qt 6 

Use these two calls together to find a version of Qt:

find_package(QT NAMES Qt6 Qt5 …)

find_package(Qt${QT_VERSION_MAJOR} ….)

There is one important variable that I always set in my Qt projects. QT_DEFAULT_MAJOR_VERSION will set the version of Qt that will be used for any versionless commands. It also will enforce the version as being Qt5 or Qt6. 

You’re probably thinking that find_package will completely take care of this for you, but that’s not entirely true with Qt. If you’re using QtCreator you’re using a kit along with your CMake configuration. This kit is setting up all the Qt stuff, as well as some system info like compilers, debuggers, etc. When you have Both Qt5 and Qt6 and are using some tools that set their own CMake variables (like Qt Creator or VSCode) you can have some oddities where when using a Qt 6 kit you use some Qt 5 parts.

To remove any chance of this happening on a system with both Qt5 and Qt6, I set this variable. The nice thing about this is that find_package for Qt can be changed into: 

 find_package (Qt${QT_DEFAULT_MAJOR_VERSION}  … )   and this will find the version of Qt I want just by setting the QT_DEFAULT_MAJOR_VERSION. 

It also allows someone to use either Qt 5 or Qt 6 to build the project. This can be useful in a variety of situations, such as in the process of porting from Qt 5 to Qt 6, or when having to support both Qt 5 and Qt 6 due to a requirement to have a platform that is currently still only using Qt 5.

Let’s Talk Components

A lot of real-world projects are a set of smaller libraries. Each of the libraries in CMake is a component. This structure means you only have to worry about finding and linking the parts you need from larger projects. Qt is a project made of many components. If you have used qmake before you have seen and used the Qt components, which qmake called Qt Modules. Where you would have set  QT+= in qmake, instead you will use components in CMake.

Each Qt Modules Document page will tell you how to find it for both qmake and CMake. If the project will use Qt Widget, change the find_package call to look like this:

find_package(Qt${QT_DEFAULT_MAJOR_VERSION} REQUIRED COMPONENTS Widgets)

Linking to widgets is also easy. Use this: 

target_link_library(Foo Public Qt::Widgets)

You can add more than one component in a single call or add additional components in separate calls. Just keep in mind that if your target needs a component you must find it either directly or as a dependency of a component you have found. 

Example: CMakeLists.txt

The example code here comes from Qt Creator when you hit “new widget project” and choose the CMake build system. For our example, we will have no C++ changes from this code. 

cmake_minimum_required(VERSION 3.5)

project(untitled VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)  
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(untitled
    MANUAL_FINALIZATION
    ${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET untitled APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#             ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
    add_library(untitled SHARED
        ${PROJECT_SOURCES}
    )
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR «${CMAKE_CURRENT_SOURCE_DIR}/android»)
else()
    add_executable(untitled
        ${PROJECT_SOURCES}
    )
endif()
endif()

target_link_libraries(untitled PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

set_target_properties(untitled PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)

install(TARGETS untitled
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(untitled)
endif()

There are a lot of new cmake items in this file. Starting near the top, you will notice the: 

set(CMAKE_AUTOUIC ON)  
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

These are Qt-specific variables that provide some hints for CMake as to what to do when it sees some of the Qt-specific files or objects that need preprocessing before they can be built into the target. 

Setting CMAKE_AUTOMOC ON will make it so CMake will automatically add a moc call for any QObject or item that needs it, and include the moc_ header with the object. Setting CMAKE_AUTORCC ON will ensure that any qrc file added has rcc called on it and that the results are used as source. 

Finally, setting CMAKE_AUTOUIC ON will make it so any ui file added  has uic called and the results are used as source. These should be on for any Qt project since not turning them on will result in the need to manually add any desired calls.

Moving on with our example, we now have these two lines:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

They ensure our compiler will support C++17  – and its support is not optional. Attempts to configure this project with a compiler that doesn’t support C++17 will fail to configure.

We then see this: 

qt_add_executable(untitled
    MANUAL_FINALIZATION
    ${PROJECT_SOURCES}
)

What does this qt_add_executable call do that add_executable does not ? It’s all about that MANUAL_FINALIZATION call. This will ensure that if the target needs to be finalized at a time after building, it will happen. (this is the default if you are using Cmake >=3.19).

If you have MANUAL_FINALIZATION you must have a matching  qt_finalize_executable() call. We can see this as the last line in the example. 

The next new bit of CMake including all the Android stuff is well commented so I will skip over that and instead look at:

set_target_properties(untitled PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)

This will set the properties of the target untitled. There are a lot of properties that you can set so each has to be explicitly added to the list. 

All the MACOSX-BUNDLE_FOO calls set properties of the AppBundle on Mac so that is what you would see under the details of Untitled.App.

We know that an Appbundle will be made because the MACOSX_BUNDLE property is set to TRUE . We also see a Property WIN32_EXECUTABLE is set TRUE. On Windows this will set the application’s entry point to that of a WIN32 Gui application and not a Console application. 

The install has a new Target type BUNDLE we have not seen before that just tells CMake where to Install the Bundle That is the .app created on macOS. 

Looking Ahead

In the next installment in this series, we’ll explore how to make a simple library and install it into the system using CMake. If you missed part 1, read it here.

To add external libraries to a cmake install, you can use the find_package command in your CMakeLists.txt file. This command allows you to locate and include external libraries in your project.

You can specify the package name and version in the find_package command, as well as any required components. Once the external library is found, you can include it in your project using the target_link_libraries command.

Make sure to also set the CMAKE_PREFIX_PATH variable to the directory where your external libraries are installed, so that cmake can find them during the build process.

By following these steps, you can easily add external libraries to a cmake install and use them in your project.

Best Software Engineering Books to Read of December 2024




1


Rating is 5 out of 5

Software Engineering at Google: Lessons Learned from Programming Over Time




2


Rating is 4.9 out of 5

Modern Software Engineering: Doing What Works to Build Better Software Faster




3


Rating is 4.8 out of 5

Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures




4


Rating is 4.7 out of 5

Fundamentals of Software Architecture: An Engineering Approach




5


Rating is 4.6 out of 5

Software Engineering for Absolute Beginners: Your Guide to Creating Software Products




6


Rating is 4.5 out of 5

The Effective Engineer: How to Leverage Your Efforts In Software Engineering to Make a Disproportionate and Meaningful Impact




7


Rating is 4.4 out of 5

Observability Engineering: Achieving Production Excellence




8


Rating is 4.3 out of 5

Software Engineering: Basic Principles and Best Practices




9


Rating is 4.2 out of 5

Essential Software Development Career + Technical Guide: Engineers/Developers/Programmers: Interviewing, Coding, Multithreading, Management, Architecture, Agile, Crypto, Security, Performance, UI/UX..




10


Rating is 4.1 out of 5

Software Engineering, 10th Edition

What is the process of incorporating external libraries into a CMake project?

To incorporate external libraries into a CMake project, you can follow these steps:

  1. Download or install the external library you want to use. Make sure you have the library files in a directory on your system.
  2. Create a CMakeLists.txt file in your project directory, if you don’t already have one.
  3. Add the following lines to your CMakeLists.txt file:
1
2
3
4
5
6
7
8
# Specify the path to the directory containing the external library headers
include_directories(/path/to/external/library/include)

# Specify the path to the directory containing the external library binaries
link_directories(/path/to/external/library/lib)

# Specify the name of the external library you want to link against
target_link_libraries(your_project_name external_library_name)
  1. Replace «/path/to/external/library» with the actual path to the directory containing the headers and binaries of the external library.
  2. Replace «your_project_name» with the name of your CMake project and «external_library_name» with the name of the external library you want to link against.
  3. Run CMake to generate the necessary build files for your project, then compile and build your project using the generated build files.

By following these steps, you should be able to successfully incorporate external libraries into your CMake project.

What is the significance of specifying library paths when integrating external libraries in CMake?

Specifying library paths when integrating external libraries in CMake is important because it allows CMake to find the necessary libraries during the build process. By specifying the library paths, CMake knows where to look for the libraries when linking the executable or shared library. This ensures that the project can be built successfully without any issues related to missing library dependencies.

Additionally, specifying library paths helps in managing dependencies and ensuring that the correct version of the library is being used. It also makes the project more portable as it allows the project to be easily built on different systems with different library installation locations.

Overall, specifying library paths in CMake is essential for correctly integrating external libraries into a project and ensuring that the project can be built successfully with all the required dependencies.

How to document external library dependencies in CMake projects?

There are a few ways to document external library dependencies in CMake projects:

  1. Use a README file: Create a README file in the root directory of your project and list all external library dependencies along with installation instructions.
  2. Use comments in the CMakeLists.txt file: You can add comments in your CMakeLists.txt file next to the target_link_libraries() command that specifies the external library dependencies. This will help developers understand which libraries are being used and why.
  3. Use a separate documentation file: You can create a separate documentation file (e.g. dependencies.md) in the root directory of your project and list all external library dependencies along with any specific configurations or considerations.
  4. Use CMake’s built-in support for package management: CMake has built-in support for finding and using external libraries through the find_package() command. You can document the find_package() commands in your CMakeLists.txt file and provide information about how to install and configure the required libraries.

Overall, it’s important to provide clear and comprehensive documentation for external library dependencies in your CMake projects to help other developers understand the project requirements and dependencies.

What are the best practices for organizing external library dependencies in CMake?

  1. Use find_package() to search for installed libraries: CMake provides a built-in way to search for installed libraries on the system using the find_package() command. This makes it easy to locate and include external dependencies in your CMake project.
  2. Use find_package() with a config file or module: When using find_package(), try to use a config file or module provided by the library’s developers if available. This helps ensure that the library is properly configured and included in your project.
  3. Use target_include_directories() and target_link_libraries() for each target: When including external libraries in your CMake project, use the target_include_directories() and target_link_libraries() commands to specify the include directories and libraries needed for each target in your project. This helps keep dependencies organized and ensures that they are linked correctly.
  4. Use CMake’s INTERFACE libraries for header-only libraries: If a library is header-only (i.e., does not have any compiled code), you can use CMake’s INTERFACE libraries to include it in your project. This allows you to simply specify the include directory for the library without needing to link against any compiled code.
  5. Consider using ExternalProject_Add() for complex dependencies: If a library has complex build requirements or dependencies, consider using CMake’s ExternalProject_Add() command to download, build, and include the library as part of your build process. This can help simplify the management of complex external dependencies.
  6. Document external dependencies in your CMakeLists.txt: Make sure to document the external dependencies used in your project in the CMakeLists.txt file, including the version requirements and any custom configuration options. This helps ensure that other developers can easily understand and work with your project’s dependencies.
  7. Use CMake’s package management tools for managing dependencies: Consider using CMake’s package management tools, such as CPack, CTest, and CPack, for managing and distributing your project’s dependencies. These tools can help automate the process of building and packaging external libraries for distribution with your project.

What is the difference between using CMake’s built-in find_package and manually adding external libraries?

Using CMake’s built-in find_package function is a preferred way to add external libraries to a CMake project because it automates the process of locating and configuring the library.

When using find_package, CMake will search for the library on the system and set up the necessary include directories, library directories, and compile/link flags for the library. This makes it easier to integrate external libraries into your project and ensures that the library is correctly configured for your build environment.

Manually adding external libraries, on the other hand, requires the user to specify the include directories, library directories, and compile/link flags for the library. While this approach gives the user more control over the configuration of the library, it is more error-prone and can be cumbersome, especially for larger projects with multiple dependencies.

In general, it is recommended to use find_package whenever possible to simplify the process of adding external libraries to your CMake project and ensure that the libraries are correctly configured for your build environment.

How do I specify external library paths in CMake?

To specify external library paths in CMake, you can use the link_directories command. This command adds a directory to the list of directories searched for linked libraries during the linking stage.

Here’s an example of how you can specify external library paths in CMake:

1
link_directories(/path/to/external/library)

You can also use the target_link_directories command to specify library paths for a specific target. Here’s an example:

1
target_link_directories(my_target PUBLIC /path/to/external/library)

Alternatively, you can set the CMAKE_LIBRARY_PATH variable to specify a list of additional directories to search for libraries. Here’s an example:

1
set(CMAKE_LIBRARY_PATH /path/to/external/library)

Remember to replace /path/to/external/library with the actual path to the external library directory in your system.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Whatsapp не устанавливается на компьютер windows 7
  • Программа для создания макросов для windows
  • Как установить chromium на windows
  • Windows не удалось применить параметры tcpip
  • Копировать папку windows cmd