- Qbs Manual
- Version Header
- Qbs 3.3.1

Configurable Library ◦ C++20 Modules

# 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 `config.myproject` base item:

     // qbs/imports/ConfigMyProject.qbs
     Module {
         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: "config.myproject" }

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 `config.myproject` module to use the new `config.myproject.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.config.myproject.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.config.myproject.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: "config.myproject" }

         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.config.myproject.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

Configurable Library ◦ C++20 Modules