Version Header

To create new files, such as version headers, during the build, use Rules. Every command Qbs executes, such as compile or linker commands, is described by a corresponding Rule in a Module or a Product.

In this section, we will create a simple header with a string constant based on the variable in our project.

First, we add this variable to our mybuildconfig module:

// qbs/modules/mybuildconfig/mybuildconfig.qbs
Module {
    Depends { name: "cpp" }

    property string productVersion: "1.0.0"
    // ...

Next, we create a new file, version.h.in, located in the version-header directory. This file contains the template for our header:

// version-header/version.h.in
#ifndef VERSION_H
#define VERSION_H

const char kProductVersion[] = "${PRODUCT_VERSION}";

#endif // VERSION_H

Now we create a file called version-header.qbs in the same directory. This file contains a Product with a Rule that turns the version.h.in into a real header. Let's go through the contents of the file:

import qbs.TextFile

Product {
    name: "version_header"
    type: "hpp"

    Depends { name: "mybuildconfig" }

First, we import TextFile. We will need this class to read the template and write the resulting header. Second, we declare a new Product named "version_header" and with the "hpp" type. This is the type of the artifact we will create in the Rule. Third, we add the dependency on the mybuildconfig module to use the new mybuildconfig.productVersion variable.

We also add a Group with the template file and assign the version_h_in tag to the file:

    Group {
        files: ["version.h.in"]
        fileTags: ["version_h_in"]
    }

Qbs Rules work with file tags, instead of working with files directly, which makes it easy to reuse rules. The name of the tag is chosen arbitrarily here. We could use the name of the file as a tag, but to avoid confusion between file name and file tag, we use underscores in the tag instead of dots.

Now we can create a Rule itself:

    Rule {
        inputs: ["version_h_in"]
        Artifact {
            filePath: "version.h"
            fileTags: "hpp"
        }

Here, we specify that our Rule takes files tagged as "version_h_in" and produces an Artifact with the name "version.h" and tagged "hpp". By default, files are created in the Product.destinationDirectory folder. We add the "hpp" tag for the header as this is the tag the cpp module uses for headers. That way, Qbs can track changes and process our generated file the same way it treats all other headers. Note that earlier we set the product type to "hpp" as well. Qbs requires that artifact type should match the product type directly or be accessible via the chain of Rules. Otherwise, the Rule won't be executed. For details, see the Rules and Product Types section.

The actual code generation happens in the Rule.prepare script:

        prepare: {
            var cmd = new JavaScriptCommand();
            cmd.description = "generating " + output.fileName;
            cmd.highlight = "codegen";
            cmd.sourceCode = function() {
                var file = new TextFile(input.filePath, TextFile.ReadOnly);
                var content = file.readAll();

                content = content.replace(
                    "${PRODUCT_VERSION}",
                    product.mybuildconfig.productVersion);

                file = new TextFile(output.filePath, TextFile.WriteOnly);
                file.write(content);
                file.close();
            }
            return cmd;
        }

In this script, we create a JavaScriptCommand object and set some meta properties, such as the description and highlight. For details about Commands, see Command and JavaScriptCommand. In the sourceCode variable, we create a JavaScript function that opens the input file, reads its content using the TextFile object, replaces the "${PRODUCT_VERSION}" placeholder with the actual value in the product.mybuildconfig.productVersion variable, and writes the resulting content into the output file.

Finally, we export the exportingProduct.buildDirectory so that products that depend on this product can include our generated header:

    Export {
        Depends { name: "cpp" }
        cpp.includePaths: exportingProduct.buildDirectory
    }

The full content of the file should look like this:

// version-header/version-header.qbs
import qbs.TextFile

Product {
    name: "version_header"
    type: "hpp"

    Depends { name: "mybuildconfig" }

    Group {
        files: ["version.h.in"]
        fileTags: ["version_h_in"]
    }

    Rule {
        inputs: ["version_h_in"]
        Artifact {
            filePath: "version.h"
            fileTags: "hpp"
        }
        prepare: {
            var cmd = new JavaScriptCommand();
            cmd.description = "generating " + output.fileName;
            cmd.highlight = "codegen";
            cmd.sourceCode = function() {
                var file = new TextFile(input.filePath, TextFile.ReadOnly);
                var content = file.readAll();

                content = content.replace(
                    "${PRODUCT_VERSION}",
                    product.mybuildconfig.productVersion);

                file = new TextFile(output.filePath, TextFile.WriteOnly);
                file.write(content);
                file.close();
            }
            return cmd;
        }
    }

    Export {
        Depends { name: "cpp" }
        cpp.includePaths: exportingProduct.buildDirectory
    }
}

Let's now add our Product into the root project so Qbs will be aware of it:

// myproject.qbs
references: [
    "app/app.qbs",
    "lib/lib.qbs",
    "version-header/version-header.qbs",
]

We also need to add the dependency on the "version_header" to our application:

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

Now we can include the header in the main.c file and print the contents of the string constant:

#include <lib.h>
#include <version.h>

#include <stdio.h>

int main()
{
    printf("Hello, world\n");
    printf("%s\n", get_string());
    printf("ProductVersion = %s\n", kProductVersion);
    return 0;
}

Let's try and run our application. You should see something like this:

$ qbs run -p "My Application"
Starting target. Full command line: .../default/install-root/usr/local/bin/myapp
Hello, world
Hello from library
ProductVersion = 1.0.0

© 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.