Qbs is a modern build tool for software projects which is most notably used for Qt-based projects. It is also directly supported by Qt Creator.

Documentation is here: http://doc.qt.io/qbs/

It is not currently well-supported in debian. Qbs is packaged, but tools like debhelper know nothing about it, so packaging packages that build using Qbs requires some work.

Qbs defaults to 'build and install' in a build directory. The build and install steps can be separated, to fit better to Debian build and binary rules, (with qbs build --no-install modules.qbs.installRoot:$(PWD)/debian/tmp/, followed by qbs install --no-build --install-root $(PWD)/debian/tmp/).

Profiles

Qbs has a concept of a 'profile' in which toolchain/dependency info is stored, possibly along with other settings. And builds can be done for each desired profile. Builds and installs are done in a specified directory. How it is specified changed twice over versions 1.8-1.11. For Qbs 1.8 and earlier the build and install were done in a dir named after the profile (so 'default' for profile:default). From Qbs 1.9 there is a 'configuration name' which is used as the build and install dir. given as a bare name for 1.9 and 1.10, and as config:name from 1.11. i.e. qbs build qbs-build or qbs build config:qbs-build.

Setup

Qbs requires a 'toolchain setup' phase where needed build tools and dependencies are found, and profiles setup to describe them. The design assumes that you will do this once and the toochain info is stored under $HOME/.confin/QtProject/qbs by default. This is incompatible with debian buildds which do not have $HOME available. It is possible to specify a different location for the profiles using the --settings-dir option. Having changed the default you have to use this option for _every_ Qbs command otherwise they will be ignoring each other. There does not seem to be an environment variable that we can use to set this property, which is a pity as that would be neater. Perhaps we should ask for one.

It is possible to treat the Qbs toolchain setup/config commands as a 'configure' stage and do it for every build. This works well where $HOME is not present, but take care if using Qbs in your normal environment, where it will use your default $HOME/.config/QtProject/qbs profiles if you don't tell it not to.

For C/C++ packages there is a qbs-setup-toolchains --detect command which finds debian binutils/gcc/clang tools correctly, and creates profiles 'gcc' and 'clang' if it finds them. It's not clear whether debian should use this, or more explicitly specify one or the other into a 'debian' profile.

Qbs is often used for Qt projects in which case the path to qmake must be specified, and this will determine which version of Qt is in use. Because debian allows the installation of both Qt4 and Qt5 at the same time this is not sufficient and the QT_SELECT=5 environment variable must be used to set the desired Qt variant to use for the build.

Namespaces

Note that in commands you have to distinguish between modules, projects and products (because the same names might exist in any). In the profile namespace this is not necessary because only module properties are set. This means that (slightly confusingly) you refer to the same thing differently when setting it in a profile with config, than when passing it on a command line. So these two commands are referring to the same property:

  qbs config profiles.default.cpp.linkerFlags "-z,relro"                                    
  qbs build modules.cpp.linkerFlags:"-z,relro"

(This confused me for some time!)

Multiarch paths

Qbs has no built-in 'libdir' concept. It just has installDir which is then set to suitable values for each type of file. File types are set with a 'tags' concept. This is all quite sensible, but it means you can't just pass in --libDir from the rules file (passing in qbs.installDir would cause all files to be installed into the library dir).

However, it's straightforward to define a library path variable, and pass in a multiarch setting for it:

Project {
  name: package
  property string LibDir: "lib"

A multiarch setting for it can then be passed in in the rules file (along with a /usr prefix):   qbs install --settings-dir /tmp --install-root $(PWD)/debian/tmp/ profile:default modules.qbs.installPrefix usr/ project.libDir:lib/$(DEB_HOST_MULTIARCH)

And can be used as required, e.g in the ?DynamicLibrary section:

  Group {
    fileTagsFilter: [ "dynamiclibrary", "dynamiclibrary_symlink" ]
    qbs.installDir: libDir
    qbs.install: true
  }

Note that libDir does not include the '/usr' part, which is more correctly set as qbs.installPrefix.

Also note that you cannot set project.libDir in a profile as it will always be overwritten by the project property string LibDir: "lib" line. You can set qbs.installPrefix in the profile (so long as it is not set again in the project file: qbs config --settings-dir /tmp profiles.default.qbs.installPrefix usr/

buildflags/DEB_BUILD_OPTIONS

Making Qbs take note of dpkg-buildflags or DEB_BUILD_OPTIONS is fiddly. Qbs will not just take the $(CFLAGS), $(LDFLAGS) etc variables 'as is'. Some options translate to Qbs properties (like -g (cpp.debugInformation) and -O2 (cpp.optimization:fast)). Others map to cpp.*Flags properties, but syntax changes are required, because it's not shell or make, it's javascript.

The precedence for module properties works like this: In decreasing order:

Regarding list semantics: Command-line overrides wipe out everything else, but in all other contexts lists are merged. In particular, profile contents simply replace the default values from the module prototype and thus get merged with what's in the project files.

So these dpkg-buildoptions:

CFLAGS=-g -O2 -fdebug-prefix-map=/home/wookey/packages/cavewhere/dewalls/debian/dewalls-1.0.0=. -fstack-protector-strong -Wformat -Werror=format-security
CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2
CXXFLAGS=-g -O2 -fdebug-prefix-map=/home/wookey/packages/cavewhere/dewalls/debian/dewalls-1.0.0=. -fstack-protector-strong -Wformat -Werror=format-security
LDFLAGS=-Wl,-z,relro

map as follows:

qbs config --settings-dir /tmp profiles.debian.cpp.debugInformation true
qbs config --settings-dir /tmp profiles.debian.cpp.optimization fast
qbs config --settings-dir /tmp profiles.debian.cpp.commonCompilerFlags -Wdate-time
qbs config --settings-dir /tmp profiles.debian.cpp.defines '"FORTIFY_SOURCE=2"'
qbs config --settings-dir /tmp profiles.debian.cpp.cFlags '[ "-fdebug-prefix-map=$(PWD)=.", "-fstack-protector-strong", "-Wformat", "-Werror=format-security" ]'
qbs config --settings-dir /tmp profiles.debian.cpp.cxxFlags '[ "-fdebug-prefix-map=$(PWD)=.", "-fstack-protector-strong", "-Wformat", "-Werror=format-security" ]'
qbs config --settings-dir /tmp profiles.debian.cpp.linkerFlags "-z,relro"

As you can see the -Wl escape must be removed from cpp.Linkerflags, and values with an '=' in need to be double-quoted to avoid the shell messing with them. Things with multiple flag setting need to be entered as lists.

There is no module to support fortran or gcj settings yet, but one could be made (so FCFLAGS, FFLAGS, GCJFLAGS are not supported).

Verbose logs

use --command-echo-mode command-line on the Qbs build line to get full commands shown (without too much noise). This lets the

Clean rule

{qbs clean doesn't work very well for our purposes. Particularly because it always leaves/adds a file (!). It always creates a binary file <build-dir>/<build-dir>.bg so then you have to clean that up yourself by hand otherwise dpkg-source will complain about the unexpected file. In the examples below the build-dir is set ot 'qbs-build'. It could also be the name of the profile (on older Qbs versions) or 'default'.

Tests

Test programs would normally be built as a separate Qbs 'product'. In which case they can be run with qbs run controlled by DEB_BUILD_OPTIONS in the usual way:

ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
        qbs run --settings-dir /tmp --no-build -p test-product-name \
           profile:deb qbs-build
endif

Example debhelper rules

Here is a simplified dh rules file (for a package which installs a library) using Qbs:

override_dh_auto_configure:
        qbs-setup-toolchains --settings-dir /tmp --detect
        QT_SELECT=5 qbs-setup-qt --settings-dir /tmp /usr/bin/qmake deb
        # choose gcc as if clang is installed too you have to pick one
        qbs config --settings-dir /tmp profiles.deb.baseProfile gcc
        qbs config --settings-dir /tmp profiles.deb.qbs.installPrefix usr/
        qbs config --settings-dir /tmp profiles.deb.cpp.debugInformation true
ifneq (,$(filter noopt,$(DEB_BUILD_OPTIONS)))
        qbs config --settings-dir /tmp profiles.deb.cpp.optimization none
else
        qbs config --settings-dir /tmp profiles.deb.cpp.optimization fast
endif
        qbs config --settings-dir /tmp profiles.deb.cpp.commonCompilerFlags -Wdate-time
        qbs config --settings-dir /tmp profiles.deb.cpp.defines '"FORTIFY_SOURCE=2"'
        qbs config --settings-dir /tmp profiles.deb.cpp.cFlags '[ "-fdebug-prefix-map=$(PWD)=.", "-fstack-protector-strong", "-Wformat", "-Werror=format-security" ]'
        qbs config --settings-dir /tmp profiles.deb.cpp.cxxFlags '[ "-fdebug-prefix-map=$(PWD)=.", "-fstack-protector-strong", "-Wformat", "-Werror=format-security" ]'
        qbs config --settings-dir /tmp profiles.deb.cpp.linkerFlags "-z,relro"

override_dh_auto_build:
        qbs build --settings-dir /tmp -v --no-install \
           profile:deb \
           modules.qbs.installRoot:$(PWD)/debian/tmp \
           project.libDir:lib/$(DEB_HOST_MULTIARCH) \
           qbs-build
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
        qbs run --settings-dir /tmp --no-build -p dewalls-test \
           profile:deb qbs-build
endif

override_dh_auto_install:
        qbs install --settings-dir /tmp --no-build \
           --install-root $(PWD)/debian/tmp \
           profile:deb \
           project.libDir:lib/$(DEB_HOST_MULTIARCH) \
           qbs-build
        dh_install

override_dh_clean:
        # tidy up qbs profile builddirs
        - qbs clean --settings-dir /tmp profile:deb qbs-build
        - rm -r qbs-build
        dh_clean