Qt Build System: Build Lifebuoy


    Note (06/17/2013): the syntax of the examples in the article does not correspond to the latest versions. Check the documentation. Also in March, Qt Creator 2.7 with QBS support was released.
    Not so long ago, the Qt development team introduced a new build system - QBS. Of course, the main reaction was “Why didn’t QMAKE suit you?”, “Just adapt CMAKE”, “Another build system [xkcd, standards]”. The promised advantages of the new system: flexibility, clear syntax for all developers (QML - a javascript-like declarative language), build speed (pure and incremental), as well as easy extensibility.
    We have already heard all this somewhere, so in this article we will try to figure out how the developers came to this system, consider the simplest examples, examine the basic designs and see what support at the IDE level is present at the moment.

    History


    Together, we will try to follow the path of Jörg Bornemann, QBS developer.

    Criticism of make (and derivatives)

    A 1999 article by Peter Miller addresses the issues of recursive make and generally the convenience of makefiles as such.
    • The UNIX build system, traditional for UNIX, produces an unacceptably long build time for large projects (especially for incremental builds);
    • Using a recursive call to make from the root directory leads to breaking dependencies between the modules, to eliminate which you have to run the assembly two or three times (and increase the build time, of course);
    • Using a single makefile fixes the flaws, but the support becomes unbearable. There is no question of any sufficient support for makefile changes using the IDE.


    A 2005 Adrian Neagu article discusses more general make problems:
    • Portability of make is greatly inflated, since make does not work on its own, dozens of other utilities that are platform-specific can be specified in the makefile;
    • Scalability is terrible when using recursive make (the same opinion as in the previous article), and not enough when using include or other extensions. In particular, assembly performance drops very quickly.


    In the article “ What's wrong with GNU Make
    , the minuses of the language and the same problems are described in more detail as before (I will not repeat)
    • Non-obvious and frankly flawed elements of the language (which are only ifeq, ifneq, ifdef, and ifndef)
    • Type problems (work with strings only)
    • Poor dependency resolution when changing command line options
    • Problems with simultaneous launch and parallelization
    • It is easy to “kill” the assembly tree by suddenly stopping the program.
    • And there are many other small jambs that indicate inappropriate architecture for modern needs (according to the author).


    In a short note by Ian Lance Taylor , 2007, Cmake's main drawback as a replacement for Make is He. Too. Folded up. This is a hellish mixture of several languages, only the development and debugging gurus can support it (for really complex systems) (it is also problematic to debug it). Another drawback is that for flexibility (even FLEXIBILITY), you have to pay for the loss of performance in the generated scripts.

    What is wrong with QMake?

    Qt's infrastructure today uses the Qmake build system, which is officially supported and is still being developed. Why did the need for “just another one” build system?

    The Marius Storm-Olsen article analyzes the following qmake flaws:
    • Own, original language syntax.

      Try looking at QTDIR / mkspecs / features / exclusive_builds.prf:
      DEFINES += QT_NO_CAST_TO_ASCII
      !macx:DEFINES += QT_USE_FAST_OPERATOR_PLUS QT_USE_FAST_CONCATENATION
      unix {
          CONFIG(debug, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/debug-shared
          CONFIG(release, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/release-shared
          CONFIG(debug, debug|release):MOC_DIR = $${OUT_PWD}/.moc/debug-shared
          CONFIG(release, debug|release):MOC_DIR = $${OUT_PWD}/.moc/release-shared
          RCC_DIR = $${OUT_PWD}/.rcc
          UI_DIR = $${OUT_PWD}/.uic
      }
      


      To understand this right away, even “half a liter” is not enough. Although CMake can still compete with this.
      Let the programmer know C ++, Pascal, Javascript, win & unix shell and SQL - all this knowledge will be useless in mastering the build system.
      From myself, I can add that the “#” symbol in the paths and the fuss with $$ LITERAL_HASH caused me a lot of trouble (why think of a way to escape special language characters? Let's introduce our macros better).
    • IDE support.
      Qt creator supports adding new files to a project, but if you move a little away from its “usual” syntax, it can simply add a new HEADERS or FILES construct to the end.
    • Lack of assembly per se in qmake.
      Qmake does not build your project. The name is misleading. It only generates a Makefile, and the assembly is done by one of the Make utilities (* nix - gnu make, win-nmake, jom or mingw-make).
    • Lack of transparent support for cross-compilation, package building, and deployment.

    Finally, we come to the point where the new build system and requirements for it were discussed in the Qt mailing list :
    • It should be VERY fast (inconspicuous time to generate the stat () of all files);
      Changing a node somewhere deep in the assembly tree should not lead to a change in the configuration of the entire tree;
    • It should have a simple and understandable language that can be easily understood by people and served by scripts;
      The language must be well specialized;
      It should be declarative in its structure, with small imperative fragments, rigidly isolated from the rest;
      The language should be based on properties, not on commands. No CFLAGS. (cmake halfway to this);
    • There should be full support for modularity. Also, any subproject should be able to easily separate from the parent (and join another). That is, roughly speaking, they took the folder with the configuration of the assembly, inserted it into another project - there everything is also going well (without taking into account the dependencies of course);
    • Clear and clear support for the IDE;
    • It should be possible to assemble different configurations for different platforms in one pass.


    Introducing Lifebuoy - Qt Build System


    On February 15, 2012, Jörg Bornemann presented the QBS project to the public at Qt labs. Now he is not even in alpha state, but somewhere in the middle between the prototype and the alpha version. In justification, we can say that Qt Creator is already going with its help.
    The basic principles of designing a new system:
    • Declarative - uses QML syntax;
    • Extensibility - it is possible to write your own modules for assembly, or code generators, etc. (below we will try to write our own module for assembling Delphi projects);
    • Speed;
    • Direct assembly is not qmake, it is not a mediator. Qbs itself calls the compilers, the linker, and what else is needed there.

    What is not there yet: checking the system configuration (using the qmake request to configure toolchains), building packages, deploying, running tests, and most importantly, supporting any ide, except for the special QtCreator branch.

    Let's start to try!

    The author advises building qbs from the source , but you can keep in mind that for win and linux users there are binary builds of
    win - compiled under MSVC2010. I would also advise building a version from git (I had problems with plugins and MOC).
    In binary form, qbs depends on the QtCore and QtScript libraries (+ Concurrent for Qt5).
    Create a Hello World C ++ project:
    main.cpp
    #include 
    using namespace std;
    int main()
    {
    	cout << "Hello world!" << endl;
    	return 0;
    }
    


    We also create a minimal qbs project:
    hello.qbp
    import qbs.base 1.0
    CppApplication {
         name: "HelloWorld"
         files: "main.cpp"
    }
    

    LINUX:
    1. Open the shell in the current folder.
    2. Add the path where you unpacked / collected qbs to PATH (or rather, bin folder)
    PATH = ~ / qbs / bin /: $ PATH
    3. qbs:
    qbs starts
    Found project file /home/mapron/статья/example-3/hello.qbp
    loading project took:  69 ms 
    build graph took:  18 ms 
    for debug:
      - [hpp, application] HelloWorld as debug
    compiling main.cpp
    linking HelloWorld
    Build done.
    

    After that, the build program will start the project analysis and collect the executable file in the build / debug folder (well, haven't we specified the destination folder yet?)
    WINDOWS:
    4. Since under Windows, as a rule, the compiler and qt are not in PATH, qbs may ask you to run " Qbs platform probe " what needs to be done.
    5. then it may be necessary to run qbs config update or the like (if it asks, it may have been fixed already).
    6. after that, when qbs [build] is launched , you will receive the assembled binary “HelloWorld.exe”

    We continue to study

    Now let's try to master the assembly of something more complex, and at the same time, deal with the language.
    First, create a new folder and copy the “ 2dpainting ” and “ collidingmice ” folders from the examples folder with Qt there.
    Why two at once? We will create a configuration for assembling two products at once.
    The product is the target "exit" of the build system, similar to the ".pro" file when building Qmake. There can be several products in one qbs project.
    First, create the “ examples.qbp ” project

    //Подключаем стандартные библиотеки в стиле QML
    import qbs.base 1.0 
    import qbs.fileinfo 1.0 as FileInfo
    Project {   // основной элемент файла - проект. 
        moduleSearchPaths: "qbs" // Папка для поиска дополнительных модулей, таких как cpp и qt
        // Один проект может состоять из нескольких продуктов - конечных целей сборки.
        Product {
            name: "2dpainting"       // Название выходного файла (без суффикса, он зависит от цели)
            type: "application"     // Тип - приложение, т.е. исполняемый файл.
            Depends { name: "cpp" } // этот продукт зависит от компилятора C++
            Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }   // И Qt модулей QtCore, QtGui, QtOpengl
            files: [ // список файлов в данном проекте. пока они в поддиректории, организуем позже.
                "2dpainting/glwidget.h",
                "2dpainting/helper.h",
                "2dpainting/widget.h",
                "2dpainting/window.h",
                "2dpainting/glwidget.cpp",
                "2dpainting/helper.cpp",
                "2dpainting/main.cpp",
                "2dpainting/widget.cpp",
                "2dpainting/window.cpp",
            ]
        }
    }
    


    Open the console and try to build, the error “ERROR: Error while setting up build environment: qt.core.incPath not set. Set qt.core.incPath or qt.core.path in your profile. "
    For now, we close our eyes to the not-so-user-friendly way of configuration, and create the file" qbs.config "with the following contents (WINDOWS):
    modules.qbs.platform: MSVC2010
    profile: default
    profiles.default.qt.core.path: C:/QtSDK/Desktop/Qt/4.8.1/msvc2010/
    


    either under LINUX (ubuntu):
    modules.qbs.platform: gcc
    profile: default
    profiles.default.qt.core.binPath: /usr/bin/
    profiles.default.qt.core.libPath: /usr/lib/qt4
    profiles.default.qt.core.incPath: /usr/include/qt4 
    profiles.default.qt.core.mkspecsPath: /usr/share/qt4/mkspecs
    

    and run qbs config --import qbs.config
    After that, qbs will normally be able to build the project and place the output file in the build / debug folder.
    To build a project for release, do the qbs build release .
    To clean all build files (that is, the build folders), run qbs clean .
    Now let's try to organize an extensible structure for two projects. In the “ 2dpainting ” and “ collidingmice ” subdirectories, create the namesake files of our directories with the extension “.qbs” with the following contents:

    import qbs.base 1.0 
    Product {
        name: "2dpainting"
        type: "application"
        Depends { name: "cpp" }
        Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }
        files: [ // чтоб не раздувать по вертикали, напишу в две колонки.
            "glwidget.h",     "helper.h",
            "widget.h",       "window.h",
            "glwidget.cpp",   "helper.cpp",
            "main.cpp",       "widget.cpp",
            "window.cpp",
        ]
    }
    


    import qbs.base 1.0 
    Product {
            name: "collidingmice"
            type: "application"
            Depends { name: "cpp" }
            Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }
            files: [ "mouse.h","main.cpp", "mouse.cpp" ,"mice.qrc"]
        }
    


    Those. we break the code into independent products that can be assembled in parallel. Make changes to examples.qbp:
    //Подключаем стандартные библиотеки в стиле QML
    import qbs.base 1.0 
    import qbs.fileinfo 1.0 as FileInfo
    Project {   // основной элемент файла - проект. 
        moduleSearchPaths: "qbs"
        // Один проект может состоять из нескольких продуктов - конечных целей сборки.
        // указываем связанные файлы с помощью references. Внимание: это не жестко заданный порядок!
        // Порядок задается с помощью зависимостей, о них позже
        references: [
               "2dpainting/2dpainting.qbs",
               "collidingmice/collidingmice.qbs",
           ]
    }
    


    You can run qbs again. Please note that for the “.qrc” file, “rcc” will be automatically called and all this will be linked together. In this case, all files are indicated by one list, without separation into HEADERS, SOURCES, etc., as was the case in qmake.

    How does it all work?

    To begin, I recommend that you quickly familiarize yourself with the help.
    The main concepts of the language: Project, Product, Product, Artifact, Module, Rule, Group, Depends, Tag .
    A product is an analogue of pro or vcproj, i.e., one goal for assembly.
    A project is a collection of your products along with dependencies, perceived by the build system as a whole. One project - one assembly graph.
    Tag is a file classification system. For example, “* .cpp” => “cpp”
    Rule - Convert project files marked with specific tags. Generates other files called Artifacts. Typically, these are compilers or other build systems.
    An artifact is a file over which is the output for the rule (and possibly the input for other rules). These are usually “obj”, “exe” files.
    Many QML objects have a condition property that controls whether it will be assembled or not. And if we need to split the files like that? To do this, they can be combined into a group (Group)
    Group { 
                condition: qbs.targetOS == "windows" 
                files: [ "file1", ...]
    }
    

    something like this.

    What next?


    A natural question, examples are great, but how can you go free swimming with this system? Can:
    • Read the documentation;
    • Download the Qt Creator sources and see how QBS is used there;
    • Study the source code of QBS itself (in case of emergency).


    Conclusion


    QBS provides us with the following advantages:
    • Convenient and clear assembly config format
    • High speed
    • Clear modular architecture

    But, unfortunately, it has the following (temporary disadvantages)
    • The presence of a large number of bugs (this is not alpha);
    • Lack of IDE support (wait in Qt Creator);
    • Lack of package assembly system, installers (in plans).


    Unfortunately, the format of the article does not allow to reveal all the details, in the future plans to reveal the topic:
    1. Creating your own module (it is included in the archive with examples);
    2. Writing rules for the assembly;
    3. Creation of js-modules and their inclusion;
    4. Creating your own types of products;
    5. Work with the global config and module configs;
    6. Writing your own plugin (for parsing dependencies of a .drpoj file).

    All this can be in this article, and something that will be written in the comments.

    Link to the archive with all examples to the article:
    narod.ru/disk/49759080001.18d6748f8ef86e26c6dea2e2c5ed7d13/examples.zip.html
    In the BONUS archive! an example of a module for assembling a DELPHI 2007 project not mentioned in the article (it is planned to parse in the next article).

    References


    labs.qt.nokia.com/2012/02/15/introducing-qbs - view
    doc-snapshot.qt-project.org/qbs - documentation
    qt.gitorious.org/qt-labs/qbs - git
    bugreports.qt-project .org / browse / QBS - bug tracker.

    Note: Qt Build Salvation is the internal name of QBS in the README source tree.

    Also popular now: