Что такое cmake и qmake
Перейти к содержимому

Что такое cmake и qmake

  • автор:

Заводим трактор: QMake -> CMake

У нас было . Картинка для привлечения внимания

Запрещенных веществ у нас не было (хотя в конце могут возникнуть сомнения). Было кое-что поинтереснее, если вы понимаете о чем я. Да, это легаси win-only Qt проект, начатый еще до начала времен под Qt 4.

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

Со временем захотелось упростить управление зависимостями, выкинуть велосипеды, потом целевой платформой стал и Linux, и в архитектуры добавился арм. Всё это играло в пользу cmake. К тому же cmake поддерживают самые прогрессивные IDE, такие как CLion и msvs, а qmake — QtCreator (KDevelop? нет, спасибо). Конечно еще есть make, autotools и msbuild но как-то хотелось иметь один проект под все платформы, а не вот это всё.

Немного про системы сборки

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

В действительности, система сборки — это некий набор скриптов, содержащих команды компилятору о том, как собирать наши таргеты. Он снимает эту нагрузку с программиста, и тем самым нам приходится писать короткие скрипты, которые система сборки уже преобразует в полные команды компилятору. Самые известные системы сборки это — make, autotools и ninja, но есть и множество других.

Если вы вдруг думали, что make это компилятор, то нет, это своего рода враппер над компилятором.

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

  1. Сделать системы сборки платформо-независимы — сложно и тяжело (примерно, как сделать бинарник, запускаемый на *nix и windows).
  2. Добавить уровень абстракций — проще

Некоторые упорно пошли по первому пути, ну а второй путь — появление мета систем сборки.

Теперь программисты пишут скрипты уже для мета систем сборки, а они, в свою очередь, генерируют скрипты для систем сборки. Т.е. еще один враппер, но теперь у нас один фронт-енд (мета система сборки) и множество бэков (просто систем сборки). Например, мы используем cmake как фронт. Для windows у нас бэком будет msbuild, который в свою очередь будет враппером над msvc. Для *nix у нас бэк — это make, который выступит враппером над gcc.

Не секрет что The Qt Company начиная с Qt версии 6 отказывается от QMake в пользу CMake для сборки самого Qt. Параллельно с этим Qbs был объявлен deprecated. Правда, надо отдать должное сообществу, Qbs до сих пор развивается. Но при чем тут Qbs то? Дело в том, что Qbs появился как замена qmake.

Хотели как лучше, а вышло как в том анекдоте.

У нас ведь есть основная система сборки — QMake и, казалось бы, с ней всё хорошо. Но давайте посмотрим на активность в репозитарии. К сожалению, посмотреть статистику по годам нельзя, но никто не мешает сделать это локально, получаем:

В 2020 было коммитов меньше чем в любой год до этого, а в 2021 и того меньше будет. Активность 2019 года связана с появлением Qt 6, а к возможностям самого qmake имеет опосредованное отношение. Если посмотреть на сами коммиты, то это в основном фиксы, а не добавление нового функционала. Таким образом можно предположить, что QMake поддерживается на остаточной основе и бурного развития не планируется.

Qmake хорош?

Только потому что QMake мета система сборки — она намного удобнее чем make или autotools. Но есть и другие немаловажные моменты. Условный hello world на любой написать несложно. Но вот чем дальше в лес. тем сложнее. Тут нам на руку играет такой фактор как популярность — проще найти ответ на любой вопрос на SO/в гугле. Глянем на результаты 2021 Annual C++ Developer Survey «Lite». А точнее нас интересует только один момент What build tools do you use? (Check all that apply).

Answered: 1,853 Skipped: 20

Можно смело сказать, что QMake и в 2021 году входит в тройку самых популярных мета систем сборки (ninja, make не мета), а значит найти ответы на многие вопросы будет не так и сложно, хоть в документации и опущено множество моментов.

Почему многие все еще выбирают qmake?

  • Простота — кратно проще чем тот же пресловутый cmake
  • Документация — сильная сторона всех Qt проектов (хотя есть и исключения)
  • Большая база знаний — недокументированные аспекты qmake можно как минимум нагуглить.
  • Простота подключения Qt библиотек — долгие годы всё было вокруг qmake, поэтому в некоторых моментах qmake выигрывает у cmake сих пор (статическая сборка и плагины)

Идеально для небольшого Qt проекта, не так ли? Вот именно, поэтому qmake и по сей день является рабочим решением и его рано выкидывать на свалку истории.

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

А что не так?

Идеологически qmake больше подходит для проектов, где один pro файл — одна цель, т.е. TEMPLATE = lib или app . Если же вдруг нам этого недостаточно, и нам захотелось использовать TEMPLATE = subdirs , то надо быть готовым прыгать по разложенным специально для нас граблям (про грабли попозже). Конечно вы сможете заставить это всё работать, но чего это будет стоить.

Кроссплатформенность у нас есть и довольно неплохая, реализована через mkspecs (аналог cmake-toolchains). С кросс-компиляцией сильно хуже, у меня так и не получилось её завести как надо, возможно руки виноваты, но с cmake почему-то было не сильно сложно.

Добавим сюда весьма смутное будущее, или наоборот ясное, учитывая рассмотренное выше. Недостаточно для того, чтобы пойти налево? Тогда qmake вам подходит.

Согласно уже упомянутому выше Annual C++ Developer Survey — самая больная тема в C++ разработке это управление зависимостями. Так что этот момент нельзя игнорировать.

зеленый - боль, синий - проблема, желтый - не имеет значения; полный вариант можете посмотреть в оригинале

К этому мы отдельно вернемся позже. А сейчас скажем просто, что qmake не слишком в этом хорош или даже совсем ничего не умеет, если сторонняя библиотека не содержит pro файл.

Если по пунктам:

в счастливое светлое будущее?

  1. Сложность управления разделенными большими проектами
  2. Отсутствие будущего
  3. Сложности с кросс-компиляцией
  4. Управление не Qt зависимостями

Cmake лучше?

Или те же грабли, только вид сбоку? Возьму на себя смелость найти ответ на этот вопрос.

Начнем с самого первого пункта, который нам не нравится в qmake — сложность управления большими проектами, разделенными на множество отдельных модулей. CMake спроектирован иначе, это плюс для больших проектов, но повышает сложность вхождения (настолько что им хоть детей пугать). Тут нет явного разделения на app, lib и subdirs. Всегда есть один корневой проект, а все остальные могут быть его подпроектами ( add_subdirectory ), а могут и не быть. Т.е. subdirs по умолчанию включен, но может не использоваться.

Наш проект интересен и сложен тем, что у нас разные целевые ОС и архитектуры. Просто прикинем, что нам надо всё собрать под 4 разных варианта: Windows x86, Windows x86_64, Linux Debian amd64, Linux Debian armhf. По итогу имеем три архитектуры и две ОС. Ну да, по итогу еще немного отстрелянных ног и набитых шишек (бесценный опыт).

Если у вас вдруг возник вопрос, то да, мы тащим qt в embedded. Но в оправдание скажу, что это сильно сэкономило время разработки т.к. qt части не надо переписывать на чистые плюсы, а можно взять as is.

Мы не используем MinGW под Windows, только msvc; кросс-компилируем clang’ом, под amd64 на ci тоже собираем clang’ом а так можно использовать и gcc, но баг компилятора иногда заставляет перейти на другой. В случае cmake стоит сказать и про генераторы — везде используется Ninja но поддерживается и Visual Studio генератор как запасной вариант. Это важно т.к что работает для одного, иногда не работает для другого, дело даже не в том что один multi-config.

CMakeLists первоначально выглядел как-то не очень.

Примерно так оно и было сначала

Звучит совсем плохо? Но qmake не позволяет нам выбрать генератор (систему сборки), поэтому под окошками придется страдать, используя JOM, а под никсами — make. За большие возможности приходится платить — так можно описать весь cmake одной фразой.

Будущее cmake? Это де-факто стандартная система сборки в C++, вряд ли этого мало.

Кросс-компиляция в cmake работает через cmake-toolchains, достаточно собрать правильно окружение, написать toolchain файл и всё это будет полностью прозрачно для файла проекта. Т.е. в самом файле проекта не надо ставить никакие условия и флажки отдельно для кросс компиляции. Особо рукастые кросс-компилируют под embedded, используя cmake и компиляторы не из большой тройки. Здесь всё ограничивается вашей фантазией (иногда и отсутствующим генератором).

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

Попробуем сравнить детально

Сложность управления разделенными большими проектами

Возьмем простой пример. У нас есть App1, App2 и lib1, lib2. Каждый App зависит от каждой lib. Если всё немного упросить, то мы получим файлы следующего содержания:

TEMPLATE = subdirs SUBDIRS = \ lib1 \ # relative paths lib2 \ . App1 \ App2 App1.depends = lib1 lib2 . App2.depends = lib1 lib2 . 
 add_subdirectory(lib1) add_subdirectory(lib2) add_subdirectory(App1) add_subdirectory(App2)

В обоих случаях мы перечисляем поддиректории для включения. Но дальше в qmake надо явно указать, что конечный исполняемый файл зависит от собираемой библиотеки. Иначе они будут собираться одновременно, и можно получить ошибки линковки при чистой сборке (считай UB). В cmake это решили иначе и намного лаконичнее, позже обратим на это внимание.

Library

Идем дальше, опишем сначала наши библиотеки. Для qmake прикручен велосипед, который обязывает нас в директории lib1 создавать библиотеку с точно таким же именем и именем файла но упрощает жизнь потом — уменьшает количество boilerplate кода (подробнее можно почитать тут https://quantron-systems.com/ru/article/6). Вообще-то странно что нам нужны какие-то велосипеды для такого маленького проекта, не так ли? Если у вас возник такой же вопрос, то может стоит переехать на cmake?

Самое интересное, что этот хак я так и не смог заставить работать под *nix. В конце концов плюнул и выкинул qmake просто.

QT += core network xml ## укажем необходимые Qt компоненты TARGET = lib1$$ ## укажем таргет TEMPLATE = lib ## скажем, что мы собираем библиотеку DEFINES += LIB1_LIBRARY ## добавить define, возможно где-то пригодится include(lib1.pri) ## укажем .pri файл, содержащий перечисление исходников
SOURCES += \ src.cpp \ . HEADERS += \ hdr.h \ . 

Разделение на pri и pro использовано специально, чтобы один файл содержал все директивы для библиотеки, а второй — только перечислял исходники и заголовки. Не имеет никакого значения, но мне было так удобнее ориентироваться.

project(gen LANGUAGES CXX) ## укажем проект и используемые языки find_package( QT NAMES Qt6 Qt5 COMPONENTS Core Network Xml REQUIRED) ## укажем, что мы хотим найти пакет Qt6 или Qt5 find_package( Qt$ COMPONENTS Core Network Xml REQUIRED) ## укажем, что из найденного пакета нам нужны такие компоненты add_library( lib1 STATIC hdr.h . src.cpp . ) ## укажем, что мы хотим собрать статическую библиотеку target_link_libraries( lib1 PRIVATE Qt$::Core PRIVATE Qt$::Xml PRIVATE Qt$::Network) ## и слинковать ее с такими-то библиотеками target_compile_definitions($ PRIVATE $_LIBRARY) ## также добавим макрос

Тут может показаться что cmake многословный и перегруженный, но директива target_link_libraries позволяет указать какой тип связывания мы хотим, в qmake же мы получим PUBLIC по умолчанию и дальше только флаги линкера/компилятора. find_package на первых порах выглядит громоздким, но на деле оказывается очень гибким и удобным инструментом. Пока опустим lib2 и другие.

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

if (NOT QT_VERSION_MAJOR) set(QT_VERSION $) string(SUBSTRING $ 0 1 QT_VERSION_MAJOR) endif()
Application

Посмотрим, как будет выглядеть наш App1.

QT += core gui network widgets xml TARGET = App1 VERSION = 1.0.0 ## указываем версию QMAKE_TARGET_COMPANY = Company QMAKE_TARGET_COPYRIGHT = Company QMAKE_TARGET_PRODUCT = Product ## укажем информацию о нашем исполняемом файле TEMPLATE = app ## теперь мы собираем исполняемый файл RC_ICONS = ../../logo.ico ## иконку здесь указывать проще, но все равно win-only QMAKE_SUBSTITUTES += config.h.in ## шаблоны для генерируемых файлов ## готовый файл config.h будет лежать рядом с шаблоном include(App1.pri) LIBRARIES += lib1 \ . lib2 ## а это хак, перечисляющий от чего зависит наш App1

Внутренности App1.pri опустил, они нам не важны, т.к. там только перечисление исходников и заголовков.

qmake, src/App1/config.h.in — добавим немного полезной информации

#pragma once #define PROGNAME '"$$TARGET"' #define PROGVERSION '"$$VERSION"' #define PROGCAPTION '"$$TARGET v"' #define SOFTDEVELOPER '"$$QMAKE_TARGET_COMPANY"'
project(App1) set(PROJECT_VERSION_MAJOR 1) set(PROJECT_VERSION_MINOR 0) set(PROJECT_VERSION_PATCH 0) ## здесь версию можно указать разными способами ## мы укажем так configure_file( $/config.h.in ## взять такой файл за шаблон $/config.h ## сгенерировать из него новый по такому то пути @ONLY) configure_file( $/versioninfo.rc.in $/versioninfo.rc ## аналогичная генерация, но уже rc файлов @ONLY) ## генерируемые файлы find_package( QT NAMES Qt6 Qt5 COMPONENTS Core Xml Widgets Network REQUIRED) find_package( Qt$ COMPONENTS Core Xml Widgets Network REQUIRED) add_executable($ main.cpp . ../../icon.rc # это тоже иконка но windows-only $/versioninfo.rc # windows-only ) target_include_directories($ PRIVATE $) ## добавим в include directories нашу директорию, где будут лежать ## сгенерированные файлы if(CMAKE_BUILD_TYPE STREQUAL "Release") set_property(TARGET $ PROPERTY WIN32_EXECUTABLE true) endif() ## куда ж без костылей, говорим, что запускать надо гуй без консоли target_link_libraries( $ lib1 . lib2 Qt$::Core Qt$::Xml Qt$::Widgets Qt$::Network )

Почти в 2 раза больше строк для cmake, что ж это такое-то.

#define PROGNAME "@PROJECT_NAME@" #define PROGVERSION "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" #define PROGCAPTION "@PROJECT_NAME@ v" #define SOFTDEVELOPER "@SOFTDEVELOPER@"
1 TYPELIB "versioninfo.rc" 1 VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "@SOFTDEVELOPER@" VALUE "FileDescription", "@PROJECT_NAME@" VALUE "FileVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" VALUE "InternalName", "@PROJECT_NAME@" VALUE "LegalCopyright", "Copyright (c) 2021 @SOFTDEVELOPER@" VALUE "OriginalFilename", "@PROJECT_NAME@.exe" VALUE "ProductName", "@PROJECT_NAME@" VALUE "ProductVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" ## здесь мы тоже указываем информацию о нашем ## исполняемом файле END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END

Заголовок про системы сборки, а тут почему-то .rc файлы. А всё просто, cmake не предоставляет возможности указать иконку или информацию о запускаемом файле через переменные (в отличие от, внезапно, qmake), поэтому нам нужен rc файл.

Но все-таки что .rc файлы windows-only, как и QMAKE_TARGET_* RC_ICONS. Вообще-то в qmake тоже можно использовать сгенерированный rc файл, но зачем если встроенных переменных будет достаточно, и qmake всё сделает сам. Так что вся магия и rc файлы просто спрятаны от нас в qmake.

Директива configure_file аналогична QMAKE_SUBSTITUTES, но с одним крайне важным отличием. Вы можете указать путь, по которому будет сгенерирован файл, а в qmake он будет рядом с исходным. Это неважно, если вам нужно использовать его только один раз. Но что если нам нужно генерировать по одному шаблону несколько файлов? Например, вытаскивать версию через информацию текущего коммита. А ничего, тогда придется страдать. Для каждой цели в случае qmake придется иметь копию этого файла в другой директории, иначе они будут затираться. Вообще cmake предоставляет больше способов работы с путями.

Вернемся немного назад, вспомним строки в самом первом .pro файле — App1.depends = lib1 lib2 . Cmake под капотом имеет схожий инструмент, только для пользователя он выглядит намного удобнее. Всё это работает через директиву target_link_libraries( . . . ) . Здесь target зависит от item, т.е. item должен быть собран перед тем, как target будет с ним линковаться. Если вы используете рекомендуемый синтаксис, т.е. item это — A library target name (item должна быть создана директивой add_library() или быть IMPORTED библиотекой), то всё само прекрасно соберется и слинкуется а при пересборке библиотеки, она будет слинкована снова. Надо заметить, что это намного удобнее нежели реализация в qmake. Почему этого нет в qmake.

Можно сказать, что cmake предоставляет больше возможностей, но и больше приходится писать руками. CMake всё больше напоминает один известный ЯП.

Управление зависимостями

Тут у нас есть решения как общие для обеих систем сборки, так и специфичные для каждой. Начнем с общих.

Пакетные менеджеры, а конкретно conan предоставляют удобные пути интеграции с обеими системами сборки. Но есть небольшой нюанс — основной путь интеграции в qmake. Он не является прозрачным и теперь мы полностью будем зависеть от conan’a. Больше нельзя будет собрать проект без conan’a. Прекрасно? Остальные языки тоже зависят от пакетников, но там они часть языка.

А вот для cmake дела нынче обстоят иначе. Есть три основных генератора: cmake, cmake_find_package, cmake_find_package_multi. Первый аналогичен таковому для qmake — подсаживает нас на пакетник,а вот два последних дают полностью прозрачную интеграции, что несомненно является большим плюсом, например мы можем на Windows линковаться с библиотекой из conan’a а под Linux с библиотекой из пакетов без особых проблем. Тут есть много но и если, которые связаны с кривыми рецептами в conan’e отчасти, но сама возможность имеется и многие кейсы покрывает. Так что небольшая магия присутствует. Небольшой пример:

find_package(hidapi REQUIRED) ## найдет как системный dev пакет так и из conan'a if (UNIX) target_link_libraries($ PRIVATE hidapi-hidraw) # debian package endif() if (WIN32) target_link_libraries($ PRIVATE hidapi::hidapi) # conan endif()

Специально вытащил такой пример, hidapi под никсами и hidapi под окошками — разные библиотеки с одним api, т.е. под никсами это либо через libusb либо через hidraw,а вот окошками только один вариант.

Но что делать если нашей библиотеки нет в пакетном менеджере (или в репах нашего дистрибутива)? А это случается часто, надеюсь когда-нибудь в нашем ужасном С++ мире будет пакетный менеджер с библиотеками на любой чих (привет npm).

В случае qmake у нас не так чтобы много возможностей. Если желаемая библиотека предоставляет возможности интеграции (например содержит .pro файл), то всё в шоколаде, например как тут https://github.com/QtExcel/QXlsx/blob/master/HowToSetProject.md, 4 строчки и всё в ажуре. Но вот если желаемая библиотека не поддерживает qmake. ничего вы не сделаете, кроме как сначала собрать и разложить всё по папочкам.

С cmake ситуация отличается примерно целиком и полностью, он из коробки предоставляет интерфейс подхватывания и сборки сторонних либ, даже если они не поддерживают cmake — ExternalProject. Конечно если желаемая либа содержит идеальный CMakeLists, то строчек надо написать что-то в районе 4-х тоже (снова там же пример https://github.com/QtExcel/QXlsx/issues/49#issuecomment-907870633) или вообще пойти через add_subdirectory , тогда можно ограничиться и 1 строчкой, управлять версиями через git submodule. Но мир костылестроения большой, поэтому предположим ситуацию, что требуемая библиотека поддерживает только qmake и ничего более (вариант патчить и контрибьютить в Open Source пока отложим). Например LimeReport, я намеренно указал старый коммит, т.к. позже поправил CMakeLists. Можно построить весьма занятный велосипед, бессмысленный и беспощадный но интересный. А если поддерживает, но нам хочется патчить и собирать по-своему, то можно уже чуть лаконичнее, например, тот же QXlsx. CMake дает много возможностей даже здесь, главное ими научиться пользоваться.

Вывод

QMake как система сборки хороша, максимально проста для вхождения и удобна. Если вы пишете маленький Qt-only проект или строго для одной платформ с одним компилятором, то всё прекрасно в вашем мире, но как только вам понадобится выйти за рамки дозволенного.

CMake сложен, один хороший человек сказал, что его стоит рассматривать как отдельный ЯП, вынужден с ним согласиться, потому что писать приходится много. Он позволяет очень много, настолько много что порой рождается такое.

Если у вас сложный проект, вы хотите жонглировать зависмостями, использовать одну кодовую базу на самых разных ОС и архитектурах или просто быть на гребне волны и не остаться за бортом, то ваш выбор cmake.

Можно провести такие аналогии, то qmake — js/python, а cmake — C++.

P.S. В статье опущены генераторные выражение т.к. аналогов в qmake попросту нет.

Отдельное спасибо выражаю товарищам из https://t.me/qt_chat, https://t.me/probuildsystems и переводчику/автору статьи т.к. именно благодаря им всё получилось.

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

Решенные CMake vs QMake. Which is better to use for new project?

Hello all!
I’ve got curious about CMake vs QMake? Which is better to use for new application for Mobile/Desktop? What is advantage/disadvantage of both? Suggestions? Especially in case of Qt 6.

mrjj Lifetime Qt Champion отредактировано

  • Knows Qt. handles UIC and moc running automatically
  • Easy to learn.
  • Full integration. Can use Wizards to generate forms etc.
  • Harder to integrated 3rd part lib building as they rarely use QMake
  • Can do anything if you learn it well
  • Easy to integrated 3rd party things if they also use CMake. (many do)
  • Bad integration with Creator. You must manually insert new files etc.
  • Need manual handling/setup of Qt extra tools. ( last time i checked. maybe better now)
  • Complex language.

So I would say, if you know you want to use 3rd party libs, using CMake makes good sense.

sierdzio Moderators @bogong отредактировано

  • supports iOS
  • hard to support multiple libraries, 3rd party libs etc.
  • awful syntax
  • it is the de-facto industry standard for c++

qmake will still be supported in Qt 6 for user apps. But in Qt 7 it is likely to be completely dropped.
But cmake support is sure to get far more love now and int the future. So for long-term projects — better use cmake. For short-term — up to you 🙂 And for projects supporting iOS — pick qmake.

bogong отредактировано bogong

@mrjj and @sierdzio thx for reply. I’ve just tested default QtQuick application for iOS and got +2000 warnings and failed on deploy on iOS simulator. And it’s just default and empty QML application from wizard based on CMake. It’s looking awful for now.

bogong @sierdzio отредактировано bogong

@sierdzio Do you know something about implementing iOS/Android and Qt Creator integration for CMake soon? is there any plans?

sierdzio Moderators @bogong отредактировано

@sierdzio Do you know something about implementing iOS/Android and Qt Creator integration for CMake soon? is there any plans?

Android already works. iOS — I don’t know but I suspect it will be fully supported by the time Qt 6 is released (end of year).

mrjj Lifetime Qt Champion @sierdzio отредактировано
@sierdzio
So the wizards
will also work for adding classes to the project ?
sierdzio Moderators @mrjj отредактировано

@sierdzio
So the wizards
will also work for adding classes to the project ?

They already partially work (class files are added and copied into clipboard — so you can manually insert them in correct cmake file) and I expect it will get better soon. Qt Company is clearly putting a lot of effort into this area nowadays.

mrjj Lifetime Qt Champion @sierdzio отредактировано

@sierdzio
So it generates files and pastes the file name to the clipboard so you can open the project file and
paste it there ? I guess it better than not working at all but still very silly that such a trivial thing can be an issue for a build generator system. ��

bogong @sierdzio отредактировано bogong

@sierdzio Yes — Just tested. Default QML application on CMake is working for Android emulator. Issue closed.

Taupter @mrjj отредактировано Taupter

@mrjj December 2021, Qt 6.2.2, Creator 6.0. Still copies file names to clipboard so you can add them manually to Cmake.

What are CMake vs qmake pros and cons?

I would like to know reasons for use CMake for particular project over qmake and vice versa. Simply what are the pros and cons of both build systems?

10.9k 11 11 gold badges 60 60 silver badges 95 95 bronze badges
asked Dec 19, 2015 at 11:18
Hareen Laks Hareen Laks
1,442 2 2 gold badges 17 17 silver badges 33 33 bronze badges

5 Answers 5

Both are build systems, but they’re not very similar at all. If your project uses Qt, you’re probably best off using qmake. CMake is more generic, and fits pretty much any type of project.

Both qmake and CMake generate a Makefile, which is read by make to build the project. Not all build systems generate a Makefile, but many do. Even a Makefile is a type of build system; it tells the compiler and linker what to do, in order to create an executable (or a dynamic or static library).

If your project uses Qt, but you don’t want to use qmake, you’ll have to do a few more things yourself:

  • running the Meta Object Compiler (MOC)
  • include paths (tell the compiler where to look for Qt headers)
  • linking (tell the linker where to look for Qt libraries)

So, you’ll have to do a bit more work to build a Qt project without qmake, but it is possible and it will teach you a lot about how Qt and qmake do things.

On a personal note (take this only as a recommendation, do further research yourself): I’m not a big fan of qmake. It helps you with the Qt stuff, but apart from that, I’ve found it to be pretty limited.

In any case, I would recommend learning to build a small project (~10 source files) without using any type of build system. Not using CMake, not with a Makefile, just using the compiler and linker directly. You shouldn’t actually build any real project in this way, but you should learn how to do it, just to learn what build systems actually do. Knowing what they do will make them a lot easier to use.

A few months ago, we switched a project from qmake to Premake, which is also worth a look. It’s highly scriptable (with Lua), which is great when you need to customize your build process.

That said, it is a bit more «manual», so prepare yourself to learn how compiling and linking works at a more basic level, without the use of a build system. It’s also in beta (Premake 5), so there are some bits and pieces still missing.

You can also take a look at qbs, which is supposed to be a better qmake. It’s still in a beta stage, so I’d wait while it matures and becomes easier to use.

История успешного перевода ScreenPlay с QMake на CMake

ScreenPlay — это опенсорсное приложение для Windows (а скоро — ещё и для Linux и macOS), предназначенное для работы с обоями и виджетами. Оно создано с использованием современных инструментов (C++/Qt/QML), активная работа над ним ведётся с первой половины 2017 года. Код проекта хранится на платформе GitLab.

Автор статьи, перевод которой мы сегодня публикуем, занимается разработкой ScreenPlay. Он столкнулся с рядом проблем, решить которые ему помог переход с QMake на CMake.

QMake и разработка больших проектов

▍Совместное использование кода в QMake — это очень неудобно

При разработке достаточно сложных приложений обычно лучше всего разбивать их на небольшие фрагменты, которыми удобно управлять. Например, если нужно представить приложение в виде основного исполняемого файла, к которому подключаются библиотеки, то, пользуясь QMake, сделать это можно лишь с помощью проекта, основанного на шаблоне subdirs . Это — проект, представленный файлом, скажем, с именем MyApp.pro , который содержит запись об используемом шаблоне и список папок проекта:

 TEMPLATE = subdirs SUBDIRS = \ src/app \ # относительные пути src/lib \ src/lib2 

При таком подходе в нашем распоряжении оказываются несколько подпроектов, в которых нужно организовать совместное использование кода. Для того чтобы сообщить компилятору о том, где именно в других проектах ему нужно искать заголовочные файлы и файлы с исходным кодом, нужно передать линковщику сведения о том, какие именно библиотеки ему требуется подключать, и о том, где искать скомпилированные файлы. В QMake это делается через создание огромных .pri-файлов, которые используются исключительно для описания того, что нужно включить в проект. Это напоминает использование обычных C++-конструкций вида #include . В результате оказывается, что, например, файл 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(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($ $ $ $ $) # Пользовательское свойство для отключения окна консоли в Windows # https://stackoverflow.com/questions/8249028/how-do-i-keep-my-qt-c-program-from-opening-a-console-in-windows set_property(TARGET $ PROPERTY WIN32_EXECUTABLE true) # Предлагаем компилятору найти указанные зависимости. Чаще всего имя # библиотеки можно узнать у vcpkg. В противном случае можно поискать # dll/lib/so/dynlib в vcpkg/installed # Если нужны зависимости внутри структуры проекта, можно # просто добавить project(MyLib) к target_link_libraries. # Никаких путей и ничего другого указывать не нужно. target_link_libraries($ PRIVATE Qt5::Quick Qt5::Gui Qt5::Widgets Qt5::Core Qt5::WebEngine nlohmann_json::nlohmann_json libzippp::libzippp ScreenPlaySDK QTBreakpadplugin) # Предлагаем CMake скопировать этот файл в директорию build в том случае, если он изменился. # $ - это директория build! file(MAKE_DIRECTORY $/bin/assets/fonts) configure_file(assets/fonts/NotoSansCJKkr-Regular.otf $/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=%/Common/vcpkg/scripts/buildsystems/vcpkg.CMake VCPKG_TARGET_TRIPLET:STRING=x64-windows 

Нужно установить ещё какую-нибудь библиотеку? Для этого достаточно воспользоваться командой вида vcpkg install myLibToInstall .

Итоги

У подхода, когда пользуются всем самым новым и популярным, есть свои плюсы. Но что делать, например, когда системы сборки с большим потенциалом, вроде QBS, вдруг оказываются на обочине? В конечном счёте, разработчик сам принимает решения о том, чем ему пользоваться в его проектах. Именно поэтому я решил перевести свой проект на CMake. И, надо сказать, это было правильное решение. Сегодня, в 2020 году, CMake смотрится очень даже хорошо.

Пользуетесь ли вы CMake и vcpkg?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *