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']
).