2023-02-24

Qbs tutorial part 3/5: Libraries

Building libraries

In the last part we took a look at test/tests.qbs and how it related to the main project file. In this part we will go into the sub-project file src/libs.qbs in detail. But first our file tree as a reference:

.
├── OctoMY.qbs
├── src
│   ├── agent
│   │   ├── AgentMain.cpp
│   │   ├── AgentMain.hpp
│   │   ├── app.qbs
│   │   └── README.md
│   ├── apps.qbs
│   ├── combined
│   │   └── lib.qbs
│   ├── libs
│   │   ├── libagent
│   │   │   ├── agent
│   │   │   │   ├── Agent.cpp
│   │   │   │   ├── Agent.hpp
│   │   │   │   └── AgentWindow.ui
│   │   │   ├── lib.qbs
│   │   │   └── README.md
│   ├── libs.qbs
├── test
│   ├── common_test
│   │   ├── Common_test.hpp
│   │   ├── CommsTester.cpp
│   │   ├── CommsTester.hpp
│   │   ├── mock
│   │   │   ├── MockCourier.cpp
│   │   │   └── MockCourier.hpp
│   │   ├── resources
│   │   │   ├── icons
│   │   │   │   ├── profile.svg
│   │   │   │   ├── stress.svg
│   │   │   │   └── test.svg
│   │   │   └── test_resources.qrc
│   │   ├── Utility_test.cpp
│   │   └── Utility_test.hpp
│   ├── testLogHandler
│   │   ├── TestLogHandler.cpp
│   │   ├── TestLogHandler.hpp
│   │   └── test.qbs
│   ├── testTryToggle
│   │   ├── TestTryToggle.cpp
│   │   ├── TestTryToggle.hpp
│   │   └── test.qbs
│   └── tests.qbs
└── integration
   └── qbs
     └── imports
            ├── OctoMYApp.qbs
            ├── OctoMYLibProbe.qbs
            ├── OctoMYAutoLib.qbs
            ├── OctoMYTestProbe.qbs
            ├── OctoMYAutoTest.qbs
            ├── OctoMYFiles.qbs
            ├── OctoMYQtDepends.qbs
            └── utils.js


Libraries work much the same way as tests, so if you look inside src/libs.qbs you will find a similar reference to OctoMyLibsProbe as you did inside libs/libs.qbs. This probe will expectedly enumerate all folders that start with "lib" and that contain a file called lib.qbs under libs/

So now that all our libs and tests have been enumerated, we can look at the anatomy of a library. Let's use libs/libagent/lib.qbs as an example:


OctoMYAutoLib {
    property string path2:path
}


Again, you might have put two together and realized that OctoMYAutoLib refers to integration/qbs/imports/OctoMYAutoLib.qbs which again contains some sort of magic. This time it is not a Probe, however. It looks like this:


import qbs.FileInfo

StaticLibrary {
    Depends { 
        name: "cpp"
    }
    OctoMYQtDepends {}

    property string libdir: FileInfo.relativePath(project.sourceDirectory, path2)+FileInfo.pathSeparator()
    property string fulldir: FileInfo.cleanPath(path2)+FileInfo.pathSeparator()
    
    name:{
        var n=FileInfo.fileName(path2);
        console.info("Autolib    libdir=" + libdir+", n="+n+", path2="+path2+", fulldir="+fulldir);
        return n;
    }
    
    Export {
        Depends {
            name: "cpp"
        }
        OctoMYQtDepends {}
        property string e:{
            console.info("Auto Lib '"+exportingProduct.name+"' exportingProduct.sourceDirectory="+exportingProduct.sourceDirectory)
            return exportingProduct.sourceDirectory + FileInfo.pathSeparator();
        }
        cpp.includePaths: [ e ]
        cpp.defines: [ "USE_LIB_" + exportingProduct.name.toUpperCase() ]
    }
    
    cpp.includePaths: [ fulldir ]
    cpp.cxxLanguageVersion: "c++20"
    cpp.cxxStandardLibrary: "libc++"
    
    // Enumerate source files for this library
    Group{
        name: FileInfo.fileName(path2)+"_sources"
        prefix: fulldir
        files: [
            "**/*.cpp",
            "**/*.hpp",
            "**/*.c",
            "**/*.h",
            "**/*.ui",
            "**/*.qrc"
        ]
    }
}

This time you can see that we are defining an actual artifact called StaticLibrary. And this introduces a whole slew of new concepts. Let's go thorugh them one at the time from the top.

First we see a definition Depends{ name: "cpp"}

This is powerful feature in Qbs where you can depend on certain features by name to bring them into your project like magic. For example the cpp module contains all the properties and rules for building C++ applications. It is quite extensive. Qbs has a bunch of these modules that you can depen on for all sorts of languages and platforms and toolchains.

Just with that simple line we now have C++ toolcahin at our disposal.

Next comes a friend of ours from the ./integrations/qbs/ folder called OctoMYQtDepends. This time it is a reusable depend statement to specify what Qt modules we want to include in every build of OctoMY. It looks like this:


Depends {
    name: "Qt"
    submodules: [
          "bluetooth"
        , "concurrent"
        , "core"
        , "gui"
        , "multimedia"
        , "multimediawidgets"
        , "openglwidgets"
        , "positioning"
        , "printsupport"
        , "sensors"
        , "serialport"
        , "svgwidgets"
        , "widgets"
        , "xml"
    ]
}

As you can see, it uses the optional submodules syntax of Qbs Depends to specify a list of sub-modules. OctoMY™ consumes pretty much all of Qt so the list of modules is long. Here is the list of Qt modules available.

Next we will look at the Export entry. This is how we tell anyone wishing to link against our StaticLibrary how to do that. We export our dependency on C++ though the Depends {name: "cpp"}, our dependence on Qt through OctoMYQtDepends {}. We also specify where consumers of our library can find the include files through cpp.includePaths and we can specify which settings to use for the C++ compiler and linker with cpp.*.

Ideally these should match the ones used when building the library, and in this case they do, as you can clearly see it is duplicated.

The Group defines the collection of files from which this StaticLibrary will be built, and it deserves a whole part for itself, so we will go through this concept in the next part of this series.

So to summarize our StaticLibrary definition, what does the full statement mean?

In short we put the declaration for each library from one template that will recursively build all source files into a static library. This will ensure that maintaining our 40+ librarie's build instructions which should remain identical at all times is easily done in one place. We don't have to scurry around maintaining 40+ project files which would be a hazzle.

We could also just make one big library that found all the source files in each library's folder. So why did we not go this route?

There are some reasons. For one, we might want to have special commands for some of the libraries while keeping the rest identical. This is now easily carried out by reokacing or extending the project file for that single project.

Another reason is that we might gain some build speed by keeping libraries instead of re-building from source every time.

Now our libraries are built, what comes next?

This concludes the third part in a series on using Qbs to our benefit and joy in a non-trivial project, namely OctoMY™

In the next part we will look at the Group type in Qbs and how we can use it to enumerate the source fiels of our builds.

Illustrations borrowed from the amazing collection at https://ericjoyner.com/

No comments:

Post a Comment