Cmake

CMake install() и упаковка Qt-приложения для распространения

Введение

Сборка приложения — только часть пути; для передачи его конечным пользователям нужно собрать дистрибутив: исполняемый файл, нужные библиотеки Qt и плагины (платформенные, форматов изображений, стилей), ресурсы. CMake предоставляет команды install() для описания того, что и куда копировать при установке, а Qt дополняет это инструментами для автоматического разрешения зависимостей (windeployqt на Windows, macdeployqt на macOS, linuxdeployqt или ручная сборка AppImage на Linux). В статье разберём базовую настройку install() и интеграцию с инструментами деплоя.

Концепция

install(TARGETS ... RUNTIME DESTINATION ...) указывает, куда устанавливать собранный исполняемый файл, а install(FILES ...)/install(DIRECTORY ...) — куда копировать дополнительные ресурсы (иконки, конфиги, переводы). Эти команды формируют основу для cmake --install и для генерации пакетов через CPack.

Для Qt-приложений ключевая сложность — собрать рядом с исполняемым файлом все нужные Qt-библиотеки (Core, Gui, Widgets и так далее) и платформенные плагины (без qwindows.dll/libqxcb.so приложение вообще не запустится, так как Qt не найдёт платформенный плагин). Решение — запускать windeployqt/macdeployqt как часть процесса сборки или установки, либо вручную прописывать копирование нужных библиотек через install().

Пример кода: CMake install() и

cmake_minimum_required(VERSION 3.21)
project(MyApp LANGUAGES CXX)

find_package(Qt6 REQUIRED COMPONENTS Core Widgets)

add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE Qt6::Core Qt6::Widgets)

install(TARGETS MyApp
    RUNTIME DESTINATION bin
)

install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources/
    DESTINATION share/myapp/resources
)

# Автоматический запуск windeployqt после сборки на Windows
if (WIN32)
    find_program(WINDEPLOYQT_EXECUTABLE windeployqt
        HINTS "${Qt6_DIR}/../../../bin"
    )
    if (WINDEPLOYQT_EXECUTABLE)
        add_custom_command(TARGET MyApp POST_BUILD
            COMMAND ${WINDEPLOYQT_EXECUTABLE}
                    --no-translations
                    "$<TARGET_FILE:MyApp>"
            COMMENT "Развёртывание зависимостей Qt через windeployqt"
        )
    endif()
endif()

# Базовая настройка CPack для генерации установочного пакета
include(CPack)
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_GENERATOR "ZIP") # или NSIS, DEB, RPM, DragNDrop и т.д.

Пояснения к коду

Основные install() команды описывают, что входит в дистрибутив: сам исполняемый файл (RUNTIME DESTINATION bin) и директория с ресурсами приложения. Блок if (WIN32) показывает типичный паттерн автоматизации деплоя: после успешной сборки (POST_BUILD) автоматически запускается windeployqt, который анализирует бинарник, находит все нужные Qt-библиотеки и плагины и копирует их рядом с исполняемым файлом — без этого шага собранный .exe не запустится на машине без установленного Qt SDK.

Подключение CPack и указание генератора (ZIP, NSIS для Windows-инсталляторов, DEB/RPM для Linux-пакетов) позволяет получить готовый к распространению пакет одной командой cpack после сборки.

Подводные камни

  • Забытые плагины Qt (стили, форматы изображений, платформенные плагины). windeployqt обычно справляется с этим автоматически, но при использовании менее распространённых модулей Qt (например, QtSvg для иконок в формате SVG) нужно убедиться, что соответствующие плагины попали в дистрибутив — их отсутствие может проявляться не как явная ошибка, а как «пустые» иконки или отсутствующий визуальный стиль.
  • На Linux нет единого инструмента, аналогичного windeployqt/macdeployqt — linuxdeployqt существует, но менее интегрирован с CMake «из коробки», и для серьёзного распространения часто используют AppImage, Flatpak или системные пакеты с явным указанием зависимости от пакета Qt в дистрибутиве.
  • Версионная несовместимость системных библиотек Qt с собранным приложением. Если приложение собрано под одну минорную версию Qt, а на целевой машине системный Qt другой версии, могут возникать ABI-несовместимости — поэтому для десктопных приложений предпочтительнее разворачивать Qt-библиотеки вместе с приложением (как делает windeployqt), а не рассчитывать на системный Qt.
  • Несоответствие путей RPATH/RUNPATH на Linux. Если бинарник ищет shared-библиотеки Qt по абсолютному пути сборочной машины, на целевой машине (где этих путей не существует) запуск завершится с ошибкой cannot open shared object file — нужно настраивать INSTALL_RPATH и относительные пути ($ORIGIN) для переносимости.