Success story of ScreenPlay transfer from QMake to CMake

ScreenPlay is an open source application for Windows (and soon also for Linux and macOS) designed to work with wallpapers and widgets. It was created using modern tools (C ++ / Qt / QML), and has been actively working on it since the first half of 2017. The project code is stored on the GitLab platform . The author of the article, the translation of which we publish today, is developing ScreenPlay. He faced a number of problems, which were helped by the transition from QMake to CMake.











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.priincluded 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 debugand 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 libAdepends on the libraries libBand libC. But by the time of assembly, the libAlibrary libCis 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_FILErelative 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?










All Articles