Начало работы с CMake в Qt-проектах

Добавлено 1 января 2022 в 15:38

CMake – это группа инструментов, которые позволяют создавать, тестировать и упаковывать приложения. Как и Qt, он доступен на всех основных платформах разработки. Он также поддерживается различными IDE, включая Qt Creator.

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

Если вы хотите узнать, как собрать существующий проект CMake с помощью Qt, смотрите статью о том, как собирать проекты с помощью CMake через командную строку.

Содержание

Сборка консольного приложения C++

Проект CMake определяется файлами, написанными на языке CMake. Основной файл называется CMakeLists.txt и обычно находится в том же каталоге, что и исходники программы.

Вот типичный файл CMakeLists.txt для консольного приложения, написанного на C++ с использованием Qt:

cmake_minimum_required(VERSION 3.16)

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)

find_package(Qt6 REQUIRED COMPONENTS Core)

add_executable(helloworld
    main.cpp
)

target_link_libraries(helloworld PRIVATE Qt6::Core)

Пройдемся по его содержимому.

cmake_minimum_required(VERSION 3.16)

cmake_minimum_required() указывает минимальную версию CMake, которая требуется приложению. Сам Qt требует как минимум CMake версии 3.16. Если вы используете Qt, который был собран статически (по умолчанию это Qt для iOS и Qt для WebAssembly), вам потребуется CMake 3.21.1 или новее.

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

project() устанавливает имя проекта и версию проекта по умолчанию. Аргумент LANGUAGES сообщает CMake, что программа написана на C++.

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Для Qt 6 требуется компилятор, поддерживающий C++ версии 17 или новее. Принудительное выполнение этого путем установки переменных CMAKE_CXX_STANDARD и CMAKE_CXX_STANDARD_REQUIRED позволит CMake распечатать ошибку, если компилятор слишком стар.

set(CMAKE_AUTOMOC ON)

Приложения Qt обычно используют компилятор метаобъектов (moc), поставляемый с Qt. Установка для переменной CMAKE_AUTOMOC значения ON позволит CMake автоматически устанавливать правила, чтобы при необходимости компилятор метаобъектов вызывался прозрачным образом.

find_package(Qt6 REQUIRED COMPONENTS Core)

Это говорит CMake искать Qt 6 и импортировать модуль Core. Нет смысла продолжать, если CMake не может найти модуль, поэтому мы устанавливаем флаг REQUIRED, чтобы в этом случае CMake прервал работу.

В случае успеха модуль установит некоторые переменные CMake, задокументированные в переменных модуля. Кроме того, он импортирует цель Qt6::Core, которую мы используем ниже.

Для успешного выполнения find_package CMake должен найти установку Qt. Есть разные способы сообщить CMake о Qt, но наиболее распространенный и рекомендуемый подход – установить переменную кэша CMake CMAKE_PREFIX_PATH для включения префикса установки Qt 6. Обратите внимание, что Qt Creator сделает это для вас прозрачно.

add_executable(helloworld
    main.cpp
)

add_executable() сообщает CMake, что мы хотим создать исполняемый файл (а не библиотеку) с именем helloworld в качестве цели. Цель должна быть собрана из исходного файла C++ main.cpp.

Обратите внимание, что обычно здесь не указываются заголовочные файлы. Это отличается от qmake, где файлы заголовков должны быть явно перечислены, чтобы они обрабатывались компилятором метаобъектов (moc).

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

Для создания библиотек смотрите qt_add_library.

target_link_libraries(helloworld PRIVATE Qt6::Core)

Наконец, target_link_libraries сообщает CMake, что исполняемый файл helloworld использует Qt Core, ссылаясь на цель Qt6::Core, импортированную вызовом find_package() выше. Это не только добавит компоновщику правильные аргументы, но также гарантирует, что компилятору C++ передаются правильные каталоги включений и определения компилятора. Ключевое слово PRIVATE не обязательно для исполняемого целевого объекта, но его рекомендуется указывать. Если helloworld является библиотекой, а не исполняемым файлом, то следует указать либо PRIVATE, либо PUBLIC (PUBLIC, если библиотека упоминает что-либо из Qt6::Core в своих заголовках, PRIVATE – в противном случае).

Создание приложения с графическим интерфейсом C++

В предыдущем разделе мы показали файл CMakeLists.txt для простого консольного приложения. Теперь мы расширим его, чтобы создать приложение с графическим интерфейсом, использующее модуль Qt Widgets.

Это полный файл проекта:

cmake_minimum_required(VERSION 3.16)

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)

target_link_libraries(helloworld PRIVATE Qt6::Widgets)

set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)

Давайте пройдемся по внесенным нами изменениям.

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

В дополнение к CMAKE_AUTOMOC мы устанавливаем переменную CMAKE_AUTOUIC в ON. Это автоматически создаст правила для вызова компилятора пользовательского интерфейса Qt (uic) для исходных файлов .ui.

find_package(Qt6 REQUIRED COMPONENTS Widgets)

В вызове find_package мы заменяем Core на Widgets. Это найдет модуль Qt6Widgets и предоставит цели Qt6::Widgets, с которыми мы позже свяжемся.

Обратите внимание, что приложение по-прежнему будет связываться с Qt6::Core, потому что от него зависит Qt6::Widgets.

add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)

В исходники целевого приложения мы добавляем файл Qt Designer (mainwindow.ui) и соответствующий ему исходный файл C++ (mainwindow.cpp).

target_link_libraries(helloworld PRIVATE Qt6::Widgets)

В команде target_link_libraries мы связываемся с Qt6::Widgets вместо Qt6::Core.

set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)

Наконец, мы устанавливаем свойства нашего целевого приложения со следующими эффектами:

  • запретить создание окна консоли в Windows:
  • создать пакет приложений в macOS.

Дополнительные сведения об этих целевых свойствах смотрите в документации по CMake.

Организация структуры проектов

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

Поскольку мы планируем расширить проект дополнительными целями, мы перемещаем исходные файлы приложения в подкаталог и создаем там новый файл CMakeLists.txt.

<корень проекта>
├── CMakeLists.txt
└── src
    └── app
        ├── CMakeLists.txt
        ├── main.cpp
        ├── mainwindow.cpp
        ├── mainwindow.h
        └── mainwindow.ui

CMakeLists.txt верхнего уровня содержит общую настройку проекта, вызовы find_package и add_subdirectory:

cmake_minimum_required(VERSION 3.16)

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_subdirectory(src/app)

Переменные, установленные в этом файле, видны в файлах проектов подкаталогов.

Файл проекта приложения src/app/CMakeLists.txt содержит исполняемую цель:

add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)

target_link_libraries(helloworld PRIVATE Qt6::Widgets)

set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)

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

Сборка библиотек

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

В настоящее время наше приложение содержит бизнес-логику непосредственно в main.cpp. Мы извлекаем код в новую статическую библиотеку под названием businesslogic в подкаталоге src/businesslogic, как описано в предыдущем разделе.

Для простоты библиотека состоит только из одного исходного файла C++ и соответствующего ему заголовочного файла, включенного в файл main.cpp приложения:

<корень проекта>
├── CMakeLists.txt
└── src
    ├── app
    │   ├── ...
    │   └── main.cpp
    └── businesslogic
        ├── CMakeLists.txt
        ├── businesslogic.cpp
        └── businesslogic.h

Давайте посмотрим на файл проекта библиотеки (src/businesslogic/CMakeLists.txt).

add_library(businesslogic STATIC
    businesslogic.cpp
)
target_link_libraries(businesslogic PRIVATE Qt6::Core)
target_include_directories(businesslogic INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

Пройдемся по его содержимому.

add_library(businesslogic STATIC
    businesslogic.cpp
)

Команда add_library создает библиотеку businesslogic. Позже мы позволим приложению связываться с этой целью.

Ключевое слово STATIC обозначает статическую библиотеку. Если бы мы хотели создать общую или динамическую библиотеку, мы бы использовали ключевое слово SHARED.

target_link_libraries(businesslogic PRIVATE Qt6::Core)

У нас есть статическая библиотека, и на самом деле нам не нужно связывать другие библиотеки. Но так как наша библиотека использует классы из QtCore, мы добавляем зависимость линковки на Qt6::Core. Это извлекает необходимые пути включения QtCore и определения препроцессора.

target_include_directories(businesslogic INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

API библиотеки определен в заголовочном файле businesslogic/businesslogic.h. Вызывая target_include_directories, мы убеждаемся, что абсолютный путь к каталогу businesslogic автоматически добавляется в качестве пути включения для всех целей, использующих нашу библиотеку.

Это освобождает нас в main.cpp от использования относительных путей для поиска businesslogic.h. Вместо этого мы можем просто написать

#include <businesslogic.h>

Наконец, мы должны добавить подкаталог библиотеки в файл проекта верхнего уровня:

add_subdirectory(src/app)
add_subdirectory(src/businesslogic)

Использование библиотек

Чтобы использовать библиотеку, которую мы создали в предыдущем разделе, мы указываем CMake связать ее:

target_link_libraries(helloworld PRIVATE
    businesslogic
    Qt6::Widgets)

Это гарантирует, что файл businesslogic.h будет найден при компиляции main.cpp. Кроме того, статическая библиотека businesslogic станет частью исполняемого файла helloworld.

В терминах CMake библиотека businesslogic определяет требования к использованию (путь включения), которым должен удовлетворять каждый пользователь нашей библиотеки (приложение). Об этом позаботится команда target_link_libraries.

Добавление ресурсов

Мы хотим отображать некоторые изображения в нашем приложении, поэтому добавляем их с помощью системы ресурсов Qt.

qt_add_resources(helloworld imageresources
    PREFIX "/images"
    FILES logo.png splashscreen.png
)

Команда qt_add_resources автоматически создает ресурс Qt, содержащий изображения, на которые он ссылается. Получить доступ к изображениям из исходного кода C++ вы можете, добавив указанный префикс ресурса:

logoLabel->setPixmap(QPixmap(":/images/logo.png"));

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

Добавление переводов

Переводы строк в проекте Qt закодированы в файлах .ts. Подробности смотрите в разделе Интернационализация с помощью Qt.

Чтобы добавить файлы .ts в ваш проект, используйте команду qt_add_translations.

В следующем примере к цели helloworld добавляются файлы перевода на немецкий и французский языки:

qt_add_translations(helloworld
    TS_FILES helloworld_de.ts helloworld_fr.ts)

Это создает правила системы сборки для автоматического создания файлов .qm из файлов .ts. По умолчанию файлы .qm встроены в ресурс и доступны по префиксу ресурса "/i18n".

Чтобы обновить записи в файле .ts, создайте цель update_translations:

$ cmake --build . --target update_translations

Чтобы инициировать генерацию файлов .qm вручную, создайте цель release_translations:

$ cmake --build . --target release_translations

Для получения дополнительной информации о том, как повлиять на обработку файлов .ts и встраивание в ресурс, смотрите документацию qt_add_translations.

Команда qt_add_translations представляет собой удобную обертку. Для более тонкого управления обработкой файлов .ts и .qm используйте базовые команды qt_add_lupdate и qt_add_lrelease.

Теги

CMakeQtQt CreatorПрограммированиеСистема сборки

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.