Qbs

Blog Documentation Get Qbs
  • Qbs Manual
  • Convenience Items
  • Qbs 2.3.1
  • 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']).