Module Providers
There are use cases for which a pre-defined module is not flexible enough. For instance, the overall set of modules related to a certain task might depend on some information present on the local platform.
Use Module Providers to create new modules during the resolve
stage, for example when locating external dependencies such as libraries.
Consider the following example:
CppApplication { files: "main.cpp" Depends { name: "zlib" } qbsModuleProviders: ["conan", "qbspkgconfig"] }
Here, we want to use the zlib compression library in our application. Since there is no pre-defined module called "zlib"
, Qbs will try to create it using Module Providers. Qbs will invoke all providers specified in the qbsModuleProviders property and those providers will create the requested module if possible. Providers contribute to the qbsSearchPaths in the order specified by this property, so modules generated by providers specified earlier are prioritised. In the example above, modules created by the conan provider have higher priority than modules created by qbspkgconfig
.
Lookup Based on Module Name
Originally, the Qt module provider was a the only provider in Qbs and we wanted to make its usage transparent to users. Thus, it was implemented by automatically kicking in when Qbs saw the dependency on the Qt
module such as "Qt.core". It is also possible to specify the order of providers explicitly:
CppApplication { files: "main.cpp" Depends { name: "Qt.core" } qbsModuleProviders: ["Qt", "qbspkgconfig"] }
That way, users have more control over the module priorities.
Note that setting the qbsModuleProviders
property disables the lookup based on the module name entirely.
Provider Eagerness
Historically, providers were implemented in an eager way meaning that once a provider is called, it creates as many modules as it can. For example, when called, the qbspkgconfig provider created a module for each .pc file found in the system. Even though providers are quite fast, this violates the zero-overhead principle (you don't pay for what you don't use). Thus, we introduced non-eager providers which only create one module at a time when that module is requested by the Depends item. This behavior is controlled by the isEager property. We advise against using eager providers in new code.
Selecting Module Providers
As described above, you can select which providers to run using the qbsModuleProviders property. This property can be set on the Product as well as the Project level:
$ qbs resolve project.qbsModuleProviders:providerA \ # sets property globally for the Project projects.SomeProject.qbsModuleProviders:providerB \ # overrides property for the specific Project products.SomeProduct.qbsModuleProviders:providerC \ # overrides property for the specific Product
Parameterizing Module Providers
You can pass information to module providers from the command line, via profiles or from within a product, in a similar way as you would do for modules. For instance, the following invocation of Qbs passes information to two module providers a
and b
:
$ qbs moduleProviders.a.p1:true moduleProviders.a.p2:true moduleProviders.b.p:false
Qbs will set the properties of the respective module providers accordingly. In the above example, module provider a
needs to declare two boolean properties p1
and p2
, and they will be set to true
and false
, respectively.
Note: The following section contains some implementation details. Reading this section is not required for most people's everyday work.
How Qbs Uses Module Providers
If Qbs encounters a Depends item whose name does not match a known module, it checks whether such a module can be generated. This procedure works as follows:
- If the qbsModuleProviders property is not
undefined
, for each provider name in the list, all search paths are scanned for a file calledmodule-providers/<name>.qbs
ormodule-providers/<name>/provider.qbs
. - If the qbsModuleProviders property is
undefined
, search paths are scanned for a file calledmodule-providers/<name>/provider.qbs
, where<name>
is the name of the dependency as specified in theDepends
item. Multi-component names such as "a.b" are turned into nested directories, and each of them is scanned, starting with the deepest path. For instance, if the dependency's name isa.b
, then Qbs will look fora/b/provider.qbs
and thena/provider.qbs
. - If such a file is found, it needs to contain a ModuleProvider item. This item is instantiated, which potentially leads to the creation of one or more modules, and Qbs retrieves the search paths to find these modules from the item. The details are described in the ModuleProvider documentation.
- If a matching module provider was found and provided new search paths, a second attempt will be made to locate the dependency using the new paths. The search for a matching module provider ends as soon as one was found, regardless of whether it created any modules or not.