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:
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:
MyApplication {
Depends { name: "mylib" }
name: "My Application"
targetName: "myapp"
files: "main.c"
}
Now let's do the same for our library:
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:
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']
).