Convenience Items

As you might have noticed, we are repeating ourselves when setting the same properties in our products - we set version, install, cpp.rpaths, and so on. For a single application and a library, that is not a big deal, but what if we have a dozen libraries? Luckily, this can be achieved using item inheritance - we move the common code to the base item and in the real product we will only set what is specific to that product (for example, the list of files).

First, we need to tell Qbs where to look for our new base items. This can be achieved using the qbsSearchPaths property. We set this property to "qbs" so that Qbs will search our items in the qbs directory located in the project directory:

Project {
   name: "My Project"
   minimumQbsVersion: "2.0"
   references: [
       "app/app.qbs",
       "lib/lib.qbs"
   ]
   qbsSearchPaths: "qbs"
}

Note: This directory has a pre-defined structure: base items should be located under the imports subdirectory. See Custom Modules and Items for details.

Let's create a base item for all our applications and move common code there:

// qbs/imports/MyApplication.qbs

import qbs.FileInfo

CppApplication {
    version: "1.0.0"
    consoleApplication: true
    install: true

    cpp.rpaths: {
        if (!cpp.rpathOrigin)
            return [];
        return [
            FileInfo.joinPaths(
                cpp.rpathOrigin,
                FileInfo.relativePath(
                    FileInfo.joinPaths("/", product.installDir),
                    FileInfo.joinPaths("/", "lib")))
        ];
    }
}

As you see, we managed to extract most of the code here, and our application file now only contains what's relevant to it:

// app/app.qbs

MyApplication {
    Depends { name: "mylib" }
    name: "My Application"
    targetName: "myapp"
    files: "main.c"
}

Now let's do the same for our library:

// qbs/imports/MyLibrary.qbs

DynamicLibrary {
    version: "1.0.0"
    install: true

    Depends { name: 'cpp' }
    property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
    cpp.defines: [libraryMacro]
    cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined

    Export {
        Depends { name: "cpp" }
        cpp.includePaths: [exportingProduct.sourceDirectory]
    }

    Depends { name: 'bundle' }
    bundle.isBundle: false
}

Here, we introduce a helper property, libraryMacro, with a default value calculated based on the capitalized product name. Since the name of out library product is "mylib", this property will expand to "MYLIB_LIBRARY". We can also override the default value for the macro in products that inherit our item like this:

MyLibrary {
    libraryMacro: "SOME_OTHER_LIBRARY_MACRO"
}

Let's take a look at the refactored library file:

// lib/lib.qbs

MyLibrary {
    name: "mylib"
    files: [
        "lib.c",
        "lib.h",
        "lib_global.h",
    ]
    cpp.defines: base.concat(["CRUCIAL_DEFINE"])
}

We managed to extract the reusable parts to common base items leaving the actual products clean and simple.

Unfortunately, item inheritance comes with a price - when both parent and child items set the same property (cpp.defines in our case), the value in the child item wins. To work around this, the special base value exists - it gives access to the base item's value of the current property and makes it possible to extend its value rather than override it. Here, we concatenate the list of defines from the base item ["MYLIB_LIBRARY"] with a new list, specific to this product (namely, ['CRUCIAL_DEFINE']).

© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.