ScreenPlay — это опенсорсное приложение для Windows (а скоро — ещё и для Linux и macOS), предназначенное для работы с обоями и виджетами. Оно создано с использованием современных инструментов (C++/Qt/QML), активная работа над ним ведётся с первой половины 2017 года. Код проекта хранится на платформе GitLab.
Автор статьи, перевод которой мы сегодня публикуем, занимается разработкой ScreenPlay. Он столкнулся с рядом проблем, решить которые ему помог переход с QMake на CMake.
При разработке достаточно сложных приложений обычно лучше всего разбивать их на небольшие фрагменты, которыми удобно управлять. Например, если нужно представить приложение в виде основного исполняемого файла, к которому подключаются библиотеки, то, пользуясь QMake, сделать это можно лишь с помощью проекта, основанного на шаблоне
При таком подходе в нашем распоряжении оказываются несколько подпроектов, в которых нужно организовать совместное использование кода. Для того чтобы сообщить компилятору о том, где именно в других проектах ему нужно искать заголовочные файлы и файлы с исходным кодом, нужно передать линковщику сведения о том, какие именно библиотеки ему требуется подключать, и о том, где искать скомпилированные файлы. В QMake это делается через создание огромных .pri-файлов, которые используются исключительно для описания того, что нужно включить в проект. Это напоминает использование обычных C++-конструкций вида
Работа с внешними зависимостями, предназначенными для различных операционных систем, сводится, в основном, к копированию путей к соответствующим зависимостям и к вставке их в .pro-файл. Это — скучная и утомительная работа, так как у каждой ОС есть, в этом плане, свои особенности. Например, в Linux нет отдельных подпапок
Ещё один недостаток QMake заключается в периодическом возникновении проблем с компиляцией. Так, если в проекте есть множество подпроектов, которые представляют собой библиотеки, используемые в других подпроектах, то компиляция периодически завершается с ошибкой. Причиной ошибки может быть примерно такая ситуация: библиотека
Сообщение о прекращении поддержки QBS (Qt Build System, система сборки Qt) стало для меня настоящим шоком. Я даже был одним из инициаторов попытки это изменить. При использовании QBS применяются приятные синтаксические конструкции, знакомые каждому, кто когда-либо писал QML-код. Не могу сказать того же о CMake, но после того, как я несколько месяцев поработал с этой системой сборки проектов, я могу с уверенностью заявить о том, что переход на неё с QBS был правильным решением, и о том, что я продолжу пользоваться CMake.
CMake, хотя и имеет некоторые недостатки синтаксического плана, работает надёжно. А проблемы QBS больше относятся к сфере политики, чем к технической стороне вопроса.
Это — один из основных факторов, заставляющих программистов, недовольных размером Qt (и в плане количества строк кода, и в плане размера библиотеки), искать альтернативу. Кроме того, многим крайне не нравится MOC. Это — метаобъектный компилятор, который преобразует C++-код, написанный с использованием Qt, в обычный C++. Благодаря этому компилятору можно, например, пользоваться удобными конструкциями, вроде тех, которые позволяют работать с сигналами.
В нашем распоряжении, помимо QBS, имеются такие системы сборки проектов, как build2, CMake, Meson, SCons. Они, за пределами экосистемы Qt, используются во многих проектах.
Насколько мне известно, единственной IDE, поддерживающей QBS, является QtCreator.
Помните, как выше я возмущался проблемами с внешними зависимостями? Поэтому неудивительно то, сколько положительных эмоций у меня вызвал диспетчер пакетов vcpkg. Для установки зависимости достаточно одной команды! Полагаю, vcpkg может пригодиться любому C++-программисту.
Если судить о CMake по первой десятке ссылок найденных Google, то может показаться, что в этой системе используются весьма непривлекательные синтаксические конструкции. Но проблема тут в том, что Google выводит первыми старые материалы о CMake со Stack Overflow, датированные 2008 годом. Тут же попадаются и ссылки на старую документацию к версии CMake 2.8. Синтаксические конструкции, используемые при работе с CMake, могут быть очень даже симпатичными. Дело в том, что применение CMake предусматривает, в основном, использование конструкций, показанных ниже (это — сокращённый вариант файла CMakeList.txt из проекта ScreenPlay).
Роль CMake заключается лишь в том, чтобы генерировать инструкции для выбранной разработчиком системы сборки проектов. Это может оказаться огромным плюсом при работе с людьми, которые пользуются не Qt Creator, а Visual Studio. При использовании CMake можно (и нужно) выбрать Ninja в качестве системы сборки, используемой по умолчанию. Компиляция проектов с применением связки CMake+Ninja — это очень приятно. И то и другое можно найти в наборе инструментов Qt Maintenance. Кроме прочего, эти инструменты очень быстро обрабатывают изменения при итеративном подходе к разработке. На самом деле, всё работает так быстро, что при использовании Godot со SCons мне очень хочется и тут пользоваться CMake.
Управление зависимостями в C++-проектах — это непростая задача. Для её решения многие проекты даже размещают в своих Git-репозиториях необходимые DLL. А это плохо, так как из-за этого неоправданно увеличиваются размеры репозиториев (Git LFS мы тут не касаемся). Недостаток vcpkg заключается лишь в том, что этот диспетчер пакетов поддерживает лишь одну глобальную версию некоего пакета (то есть, приходится самостоятельно устанавливать разные версии vcpkg, но это — нечто вроде хака, да и нужно это редко). Правда, в планах развития проекта можно увидеть то, что он идёт в правильном направлении.
Для установки пакетов используется такая команда:
Мы, при работе над ScreenPlay, просто создали скрипты install_dependencies_windows.bat и install_dependencies_linux_mac.sh для клонирования репозитория vcpkg, для его сборки и установки всех наших зависимостей. При работе с Qt Creator необходимо записывать в
Нужно установить ещё какую-нибудь библиотеку? Для этого достаточно воспользоваться командой вида
У подхода, когда пользуются всем самым новым и популярным, есть свои плюсы. Но что делать, например, когда системы сборки с большим потенциалом, вроде QBS, вдруг оказываются на обочине? В конечном счёте, разработчик сам принимает решения о том, чем ему пользоваться в его проектах. Именно поэтому я решил перевести свой проект на CMake. И, надо сказать, это было правильное решение. Сегодня, в 2020 году, CMake смотрится очень даже хорошо.
Пользуетесь ли вы CMake и vcpkg?
Автор статьи, перевод которой мы сегодня публикуем, занимается разработкой ScreenPlay. Он столкнулся с рядом проблем, решить которые ему помог переход с QMake на CMake.
QMake и разработка больших проектов
?Совместное использование кода в QMake — это очень неудобно
При разработке достаточно сложных приложений обычно лучше всего разбивать их на небольшие фрагменты, которыми удобно управлять. Например, если нужно представить приложение в виде основного исполняемого файла, к которому подключаются библиотеки, то, пользуясь QMake, сделать это можно лишь с помощью проекта, основанного на шаблоне
subdirs
. Это — проект, представленный файлом, скажем, с именем MyApp.pro
, который содержит запись об используемом шаблоне и список папок проекта: TEMPLATE = subdirs
SUBDIRS = src/app \ # относительные пути
src/lib src/lib2
При таком подходе в нашем распоряжении оказываются несколько подпроектов, в которых нужно организовать совместное использование кода. Для того чтобы сообщить компилятору о том, где именно в других проектах ему нужно искать заголовочные файлы и файлы с исходным кодом, нужно передать линковщику сведения о том, какие именно библиотеки ему требуется подключать, и о том, где искать скомпилированные файлы. В QMake это делается через создание огромных .pri-файлов, которые используются исключительно для описания того, что нужно включить в проект. Это напоминает использование обычных C++-конструкций вида
#include <xyz.h>
. В результате оказывается, что, например, файл MyProjectName.pri
включается в состав MyProjectName.pro
. А для исправления проблемы, связанной с относительными путями, нужно добавить текущий абсолютный путь в каждую строку.?Внешние зависимости
Работа с внешними зависимостями, предназначенными для различных операционных систем, сводится, в основном, к копированию путей к соответствующим зависимостям и к вставке их в .pro-файл. Это — скучная и утомительная работа, так как у каждой ОС есть, в этом плане, свои особенности. Например, в Linux нет отдельных подпапок
debug
и release
.?CONFIG += ordered — убийца производительности компиляции
Ещё один недостаток QMake заключается в периодическом возникновении проблем с компиляцией. Так, если в проекте есть множество подпроектов, которые представляют собой библиотеки, используемые в других подпроектах, то компиляция периодически завершается с ошибкой. Причиной ошибки может быть примерно такая ситуация: библиотека
libA
зависит от библиотек libB
и libC
. Но к моменту сборки libA
библиотека libC
ещё не готова. Обычно проблема исчезает при перекомпиляции проекта. Но то, что такое вообще происходит, указывает на серьёзные проблемы QMake. И эти проблемы не удаётся решить, пользуясь чем-то вроде libA.depends = libB
. Вероятно (и, пожалуй, так оно и есть), я что-то делаю не так, но справиться с проблемой не удалось ни мне, ни моим коллегам. Единственный способ решить проблему с порядком сборки библиотек заключается в использовании настройки CONFIG += ordered
, но из-за этого, за счёт отказа от параллельной сборки, сильно страдает производительность.QBS и CMake
?Почему QBS проигрывает CMake?
Сообщение о прекращении поддержки QBS (Qt Build System, система сборки Qt) стало для меня настоящим шоком. Я даже был одним из инициаторов попытки это изменить. При использовании QBS применяются приятные синтаксические конструкции, знакомые каждому, кто когда-либо писал QML-код. Не могу сказать того же о CMake, но после того, как я несколько месяцев поработал с этой системой сборки проектов, я могу с уверенностью заявить о том, что переход на неё с QBS был правильным решением, и о том, что я продолжу пользоваться CMake.
CMake, хотя и имеет некоторые недостатки синтаксического плана, работает надёжно. А проблемы QBS больше относятся к сфере политики, чем к технической стороне вопроса.
Это — один из основных факторов, заставляющих программистов, недовольных размером Qt (и в плане количества строк кода, и в плане размера библиотеки), искать альтернативу. Кроме того, многим крайне не нравится MOC. Это — метаобъектный компилятор, который преобразует C++-код, написанный с использованием Qt, в обычный C++. Благодаря этому компилятору можно, например, пользоваться удобными конструкциями, вроде тех, которые позволяют работать с сигналами.
?Альтернативы QBS
В нашем распоряжении, помимо QBS, имеются такие системы сборки проектов, как build2, CMake, Meson, SCons. Они, за пределами экосистемы Qt, используются во многих проектах.
?Плохая поддержка QBS в IDE
Насколько мне известно, единственной IDE, поддерживающей QBS, является QtCreator.
?Блестящий союз vcpkg и CMake
Помните, как выше я возмущался проблемами с внешними зависимостями? Поэтому неудивительно то, сколько положительных эмоций у меня вызвал диспетчер пакетов vcpkg. Для установки зависимости достаточно одной команды! Полагаю, vcpkg может пригодиться любому C++-программисту.
О кажущейся непривлекательности синтаксиса CMake
Если судить о CMake по первой десятке ссылок найденных Google, то может показаться, что в этой системе используются весьма непривлекательные синтаксические конструкции. Но проблема тут в том, что Google выводит первыми старые материалы о CMake со Stack Overflow, датированные 2008 годом. Тут же попадаются и ссылки на старую документацию к версии CMake 2.8. Синтаксические конструкции, используемые при работе с CMake, могут быть очень даже симпатичными. Дело в том, что применение CMake предусматривает, в основном, использование конструкций, показанных ниже (это — сокращённый вариант файла CMakeList.txt из проекта ScreenPlay).
# Проверка минимальных требований
cmake_minimum_required(VERSION 3.16.0)
# Указание имени проекта. Оно потом будет использовано для именования
# исполняемых файлов и для весьма полезного ${PROJECT_NAME}
project(ScreenPlay)
# Некоторые настройки Qt, касающиеся ресурсов и MOC
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
# Это - лишь синтаксический сахар. Тут создаётся переменная src,
# содержащая список строк. Всё это потом будет использоваться в add_executable
set(src main.cpp
app.cpp
# Тут кое-что пропустим
src/util.cpp
src/create.cpp)
set(headers app.h
src/globalvariables.h
# И тут кое-что пропустим
src/util.h
src/create.h)
# Макрос Qt для больших ресурсов наподобие шрифтов
qt5_add_big_resources(resources resources.qrc)
# Предлагаем CMake скомпилировать qml в C++ в режиме release
# ради повышения производительности!
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(qml qml.qrc)
else()
qtquick_compiler_add_resources(qml qml.qrc )
endif()
# Предлагаем CMake найти эти библиотеки. Ранее мы, рассчитывая на это, настроили CMAKE_TOOLCHAIN_FILE
# и нам больше не нужно вручную редактировать относительные пути!
find_package(
Qt5
COMPONENTS Quick
QuickCompiler
Widgets
Gui
WebEngine
REQUIRED)
# Внешние библиотеки vcpkg
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(libzippp CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
# У CMake есть две основные команды:
# add_executable для создания исполняемых файлов
# add_library для создания библиотек
add_executable(${PROJECT_NAME} ${src} ${headers} ${resources} ${qml})
# Пользовательское свойство для отключения окна консоли в Windows
# https://stackoverflow.com/questions/8249028/how-do-i-keep-my-qt-c-program-from-opening-a-console-in-windows
set_property(TARGET ${PROJECT_NAME} PROPERTY WIN32_EXECUTABLE true)
# Предлагаем компилятору найти указанные зависимости. Чаще всего имя
# библиотеки можно узнать у vcpkg. В противном случае можно поискать
# dll/lib/so/dynlib в vcpkg/installed
# Если нужны зависимости внутри структуры проекта, можно
# просто добавить project(MyLib) к target_link_libraries.
# Никаких путей и ничего другого указывать не нужно.
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt5::Quick
Qt5::Gui
Qt5::Widgets
Qt5::Core
Qt5::WebEngine
nlohmann_json::nlohmann_json
libzippp::libzippp
ScreenPlaySDK
QTBreakpadplugin)
# Предлагаем CMake скопировать этот файл в директорию build в том случае, если он изменился.
# ${CMAKE_BINARY_DIR} - это директория build!
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/assets/fonts)
configure_file(assets/fonts/NotoSansCJKkr-Regular.otf ${CMAKE_BINARY_DIR}/bin/assets/fonts COPYONLY)
Ninja ускоряет CMake
Роль CMake заключается лишь в том, чтобы генерировать инструкции для выбранной разработчиком системы сборки проектов. Это может оказаться огромным плюсом при работе с людьми, которые пользуются не Qt Creator, а Visual Studio. При использовании CMake можно (и нужно) выбрать Ninja в качестве системы сборки, используемой по умолчанию. Компиляция проектов с применением связки CMake+Ninja — это очень приятно. И то и другое можно найти в наборе инструментов Qt Maintenance. Кроме прочего, эти инструменты очень быстро обрабатывают изменения при итеративном подходе к разработке. На самом деле, всё работает так быстро, что при использовании Godot со SCons мне очень хочется и тут пользоваться CMake.
Vcpkg позволяет CMake проявить себя во всей красе
Управление зависимостями в C++-проектах — это непростая задача. Для её решения многие проекты даже размещают в своих Git-репозиториях необходимые DLL. А это плохо, так как из-за этого неоправданно увеличиваются размеры репозиториев (Git LFS мы тут не касаемся). Недостаток vcpkg заключается лишь в том, что этот диспетчер пакетов поддерживает лишь одну глобальную версию некоего пакета (то есть, приходится самостоятельно устанавливать разные версии vcpkg, но это — нечто вроде хака, да и нужно это редко). Правда, в планах развития проекта можно увидеть то, что он идёт в правильном направлении.
Для установки пакетов используется такая команда:
vcpkg install crashpad
Мы, при работе над ScreenPlay, просто создали скрипты install_dependencies_windows.bat и install_dependencies_linux_mac.sh для клонирования репозитория vcpkg, для его сборки и установки всех наших зависимостей. При работе с Qt Creator необходимо записывать в
CMAKE_TOOLCHAIN_FILE
относительный путь к vcpkg. Кроме того, vcpkg нужно сообщить о том, какую ОС и какую архитектуру мы используем. # Настройка QtCreator. Extras -> Tools -> Kits -> -> CMake Configuration. Туда надо добавить следующее:
CMAKE_TOOLCHAIN_FILE:STRING=%{CurrentProject:Path}/Common/vcpkg/scripts/buildsystems/vcpkg.CMake
VCPKG_TARGET_TRIPLET:STRING=x64-windows
Нужно установить ещё какую-нибудь библиотеку? Для этого достаточно воспользоваться командой вида
vcpkg install myLibToInstall
.Итоги
У подхода, когда пользуются всем самым новым и популярным, есть свои плюсы. Но что делать, например, когда системы сборки с большим потенциалом, вроде QBS, вдруг оказываются на обочине? В конечном счёте, разработчик сам принимает решения о том, чем ему пользоваться в его проектах. Именно поэтому я решил перевести свой проект на CMake. И, надо сказать, это было правильное решение. Сегодня, в 2020 году, CMake смотрится очень даже хорошо.
Пользуетесь ли вы CMake и vcpkg?
ss-nopol
Несколько лет назад перетаскивал большой древний с++ проект с tmake (предтеча qmake) на CMake. Если с маленькими проектами проблем не было, то большой проект вызвал вопросы, на которые не находились толковые ответы, например как лучше организовывать работу с библиотеками в большом проекте, с бинарными и заголовочными, с теми что являются частью проекта и внешними, но тоже являющимися частью разработки.
Традиционно проблемы вызвали генерируемые файлы, которые никак не удавалось помирить с параллельной компиляцией без прописывания вручную зависимостей (так и не разобрался с этим).
Смотрел, кстати, немного исходники CMake и меня неприятно удивило то, что поддержка Qt в нём прибита гвоздями, а не реализована стандартными средствами.
Tujh
ss-nopol
Ну да, так я это и сделал. Но как я себе это представляю в 2020 году? — Я каким-то образом задаю правило генерирования одних файлов из других, например *.hpp и *.cpp из файлов *.xxx и всё, на этом моя работа должна быть закончена.
Если генерируемый *.hpp включается в *.cpp файл проекта, то cmake должен сам догадаться (иначе зачем он строит дерево зависимостей), что прежде чем начать компилировать этот *.cpp файл, ему следует сначала сгенерировать соответствующий *.hpp, который он подключает. Почему я должен задавать ещё что-то вручную? По-моему это явная и очень серьёзная недоработка, из-за которой, собственно им и пришлось прибивать qt гвоздями (я знаю, что там есть и другие проблемы).
Sazonov
Если вы правильно укажете файлы, которые генерирует ваша внешняя тулза (выхлоп), а потом добавите эти файлы в проект через target_sources, то всё должно работать.
ss-nopol
У меня ситуация такая — есть заголовочный файл *.hpp, генерируемый из xxx файла. Этот заголовочный файл включают разные библиотеки и приложения проекта. Иногда непосредственно, иногда опосредовано, через заголовки других библиотек.
Естественно я не хочу и не могу добавлять вручную этот файл в каждый из этих подпроектов, а хочу чтобы cmake сам, во время построения зависимостей увидел, что используется генерируемый заголовочный файл, и сделал соответствующие манипуляции. По-моему вполне законное желание и у CMake для этого всё есть.
Сейчас мне пришлось сделать специальную цель построения, которую я просто добавляю ко всем подпроектам библиотек и приложений, независимо от того, включают они этот заголовок или нет.
Sazonov
Как вы генерируете, можно пример?