QMake and large project development
▍Code sharing in QMake is very inconvenient
When developing applications that are fairly complex, it is usually best to break them down into small, manageable chunks. For example, if you need to present your application as a main executable file to which libraries are connected, then using QMake, this can be done only using a project based on a template
subdirs
. This is a project, represented by a file, say with a name MyApp.pro
, that contains an entry for the template used and a list of project folders:
TEMPLATE = subdirs
SUBDIRS = \
src/app \ #
src/lib \
src/lib2
With this approach, we have several subprojects at our disposal in which we need to organize code sharing. In order to inform the compiler about where exactly in other projects it needs to look for header files and files with source code, you need to tell the linker information about which libraries it needs to include and where to look for compiled files. QMake does this by creating huge .pri files that are used solely to describe what to include in a project. This is similar to using regular C ++ view constructs
#include <xyz.h>
. As a result, it turns out that, for example, the file is MyProjectName.pri
included in the composition MyProjectName.pro
. And to fix the relative path problem, add the current absolute path to each line.
▍External dependencies
Working with external dependencies intended for various operating systems is mainly reduced to copying the paths to the corresponding dependencies and pasting them into a .pro file. This is a boring and tedious job, since each OS has its own peculiarities in this regard. For example, Linux does not have separate subfolders
debug
and release
.
▍CONFIG + = ordered is a compilation performance killer
Another drawback of QMake is that it has intermittent compilation problems. So, if the project has many subprojects, which are libraries used in other subprojects, then compilation periodically fails. The reason for the error may be something like this: the library
libA
depends on the libraries libB
and libC
. But by the time of assembly, the libA
library libC
is not yet ready. Usually the problem goes away when the project is recompiled. But the fact that this is happening at all indicates a serious problem with QMake. And these problems cannot be solved by using something likelibA.depends = libB
... Probably (and, perhaps, it is so), I am doing something wrong, but neither me nor my colleagues managed to cope with the problem. The only way to solve the problem with the build order of the libraries is to use a customization CONFIG += ordered
, but because of this, by eliminating the parallel build, performance suffers greatly.
QBS and CMake
▍Why is QBS losing to CMake?
The message about the end of support for QBS (Qt Build System, Qt build system) came as a real shock to me. I was even one of the initiators of an attempt to change this. QBS uses nice syntax that is familiar to anyone who has ever written QML code. I can't say the same about CMake, but after I have worked with this project build system for several months, I can confidently state that switching to it from QBS was the right decision, and that I will continue to use CMake ...
CMake, although it has some syntactic flaws, works reliably. And QBS's problems are more political than technical.
This is one of the main factors forcing programmers who are unhappy with the size of Qt (both in terms of the number of lines of code and the size of the library) to look for an alternative. In addition, many people strongly dislike MOC. It is a meta-object compiler that converts C ++ code written using Qt to regular C ++. Thanks to this compiler, you can, for example, use convenient constructs, such as those that allow you to work with signals.
▍Alternatives to QBS
In addition to QBS, we have at our disposal such project build systems as build2, CMake, Meson, SCons. They are, outside of the Qt ecosystem, used in many projects.
▍ Poor QBS support in IDE
As far as I know, the only IDE that supports QBS is QtCreator.
▍ Brilliant union of vcpkg and CMake
Remember how I resented the problems with external dependencies above? So it’s not surprising how much positive emotions the vcpkg package manager gave me. One command is enough to install the dependency! I think vcpkg can be useful for any C ++ programmer.
Seemingly unattractive CMake syntax
If you judge CMake from the top ten links found by Google, it might seem that this system uses very unattractive syntax. But the problem here is that Google is the first to display old CMake stuff from Stack Overflow, dated 2008. Links to the old documentation for the CMake 2.8 version also come across. The syntax used when working with CMake can be pretty pretty. The fact is that using CMake mainly involves using the constructs shown below (this is an abbreviated version of the CMakeList.txt file from the ScreenPlay project).
#
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 speeds up CMake
The role of CMake is only to generate instructions for the developer's chosen project build system. This can be a huge plus when working with people who use Visual Studio rather than Qt Creator. When using CMake, you can (and should) choose Ninja as your default build system. Compiling projects using the CMake + Ninja bundle is very nice. Both can be found in the Qt Maintenance toolbox. Among other things, these tools can handle changes very quickly in an iterative development approach. In fact, everything works so fast that when using Godot with SCons, I really want to use CMake here too.
Vcpkg allows CMake to shine
Managing dependencies in C ++ projects is not an easy task. To solve it, many projects even place the necessary DLLs in their Git repositories. And this is bad, since this unnecessarily increases the size of the repositories (we do not touch on Git LFS here). The only drawback of vcpkg is that this package manager only supports one global version of a package (that is, you have to install different versions of vcpkg yourself, but this is a bit of a hack, and this is rarely needed). True, in the development plans of the project, you can see that it is going in the right direction.
To install packages, use the following command:
vcpkg install crashpad
While working on ScreenPlay, we simply created the install_dependencies_windows.bat and install_dependencies_linux_mac.sh scripts to clone the vcpkg repository, build it and install all our dependencies. When working with Qt Creator, you need to write to the
CMAKE_TOOLCHAIN_FILE
relative path to vcpkg. In addition, vcpkg needs to be told what OS and what architecture we are using.
# QtCreator. Extras -> Tools -> Kits -> -> CMake Configuration. :
CMAKE_TOOLCHAIN_FILE:STRING=%{CurrentProject:Path}/Common/vcpkg/scripts/buildsystems/vcpkg.CMake
VCPKG_TARGET_TRIPLET:STRING=x64-windows
Do you need to install any other library? To do this, just use the command of the form
vcpkg install myLibToInstall
.
Outcome
The approach of using the newest and most popular has its advantages. But what to do, for example, when build systems with great potential like QBS suddenly find themselves on the sidelines? Ultimately, the developer himself decides what to use in his projects. That is why I decided to transfer my project to CMake. And, I must say, it was the right decision. Today, in 2020, CMake looks pretty good.
Are you using CMake and vcpkg?