Build for Linux desktop

Compile the applications natively on Debian/Ubuntu

Compile the applications natively on a Debian/Ubuntu workstation. This is the primary development target — toolchain support is most mature here.

Target: Debian 13 (trixie) amd64. Ubuntu 24.04 LTS also works without changes; older releases need a newer Qt from the Qt installer or aqtinstall.


Status

Item State Notes
Build infrastructure ✅ Mature kiln build + Qt6 from the Qt installer or apt; qbs is the second path
Source portability ✅ Full This is the development reference platform
Packaging (.deb) ✅ Working kiln package deb, all apps, plus a -dbgsym package

Prerequisites

This list is derived from building the project on a vanilla Debian 13 (trixie) amd64 install — everything below is something a clean machine was missing. Items are split into mandatory (the build fails without them) and optional (feature- or variant-specific).

Mandatory — build toolchain

Component Version Debian package(s) Notes
Qt 6.10.0+ see Qt module set below Declared in integration/toolchain-requirements.json; kiln check holds qt.qmake to it, and project.qbs reads it as minimumQtVersion. Required for xdg_toplevel_icon_v1 (window icons on KDE Wayland), QRhiTexture::R32UI (construct GBuffer) and Q_STATIC_LOGGING_CATEGORY. Debian 13 ships 6.8.2, which is below the floor — use the Qt online installer.
kiln the checkout's commit or newer none The build system. It carries clang, lld and the part of git it needs. Find the machine's kiln, or get one, as Start here says.
C++ headers GCC 12+ build-essential kiln's first compile reads the host's C and C++ standard library headers. A stage-0 kit also builds the first kiln with this compiler.

Mandatory — system libraries

These are linked unconditionally on Linux and are the ones most often missing when Qt itself was installed from the Qt online installer (the apt Qt -dev packages pull most of them in transitively; a hand-installed ~/Qt does not — see the tip below).

Library Debian package Why it's needed
libudev libudev-dev libserial includes <libudev.h> and links udev for serial-port enumeration on Linux (non-Android). Build fails at Udev_p.hpp without it.
OpenGL libgl1-mesa-dev Qt's OpenGL module and several tools link -lGL; the runtime libGL.so.1 is not enough — the -dev package provides the libGL.so linker symlink.
OpenSSL libssl-dev libanon takes OpenSSL from the system on Linux: the vendored i2pd sources include <openssl/sha.h> and the library links ssl and crypto. Without it libanon stops with <openssl/sha.h> was not found. The apt qt6-base-dev pulls it in; a ~/Qt does not.
GLU libglu1-mesa-dev -lGLU for legacy GL utility entry points pulled in by the GL stack.

Optional — feature specific

Component Debian package When you need it
Vulkan libvulkan-dev Qt RHI Vulkan backend.

Installing the apt Qt -dev packages is the simplest way to get the system libraries, because they drag in the mandatory ones (libgl1-mesa-dev, libudev-dev, etc.) as transitive dependencies:

sudo apt install \
    qt6-base-dev qt6-base-dev-tools \
    qt6-svg-dev qt6-tools-dev qt6-tools-dev-tools \
    qt6-shadertools-dev qt6-declarative-dev \
    libqt6opengl6-dev libqt6openglwidgets6 \
    libqt6sql6-sqlite libqt6printsupport6 libqt6dbus6 \
    build-essential libvulkan-dev \
    libudev-dev libgl1-mesa-dev libglu1-mesa-dev libssl-dev

Tip — using Qt from the Qt online installer (~/Qt)? The installer ships Qt itself but not the system -dev libraries the link step needs. Install at least the mandatory system libraries with apt even when you build against ~/Qt:

sudo apt install build-essential libudev-dev libgl1-mesa-dev libglu1-mesa-dev libvulkan-dev libssl-dev

A convenient shortcut is to apt install the apt Qt -dev packages anyway (above) purely to pull in the transitive system libraries — you can still point qt.qmake at your ~/Qt qmake.


Build and run

In the checkout, check it first, then build. kiln check gives each command that is still missing, qt.qmake in .kiln.local among them:

kiln check                     # until the last line is: next: kiln build

# Build everything, in debug
kiln build

# Build a single product, and what it links
kiln build src/hub
kiln build src/agent
kiln build src/construct

# Lay out the install tree
kiln build --install

The programs land under debug/, and after --install under debug/install-root/usr/local/bin/ (The build convention). A first build of the whole tree takes long: detach it and follow it, as steps 4 and 5 of Start here say.

Release build

kiln build --variant release

Build options

The options of the build are option.<name> keys. The root .kiln sets the defaults, and .kiln.local changes them for this machine, for example:

option.enableLto = off
option.treatWarningsAsErrors = on
Option Default Description
option.enableLto on Link-time optimization in a release build
option.securityHardening on Stack protector, FORTIFY_SOURCE, PIE
option.treatWarningsAsErrors off -Werror

The kiln reference lists every key.


Packaging (.deb)

kiln package deb builds and installs what the install tree holds, in release, and turns it into two Debian packages (kiln reference). They go in <build root>/package, and kiln prints each path on stdout. The build root is release under the checkout, or under output.dir when .kiln.local sets it:

File Contents
<base>_<version>_<arch>.deb Stripped executables in /usr/lib/<base>/bin, <base>-<app> commands, one launcher, icon and AppStream metainfo per GUI app, copyright, changelog.gz, md5sums
<base>-dbgsym_<version>_<arch>.deb Split debug info under /usr/lib/debug/.build-id/, which gdb finds with no configuration
# .kiln.local names the Qt, and a release sets its version:
#   qt.qmake = /home/you/Qt/6.10.1/gcc_64/bin/qmake
#   package.version = 0.1.4-rc.1
kiln package deb
sudo apt install ./release/package/octomy_0.1.4-rc.1_amd64.deb

--ship construct,hub packages only those applications of package.ship. kiln package archive makes the portable .tar.gz in the same folder.

The qbs products octomy-deb and octomy-portable make the same packages with bundle_qt.sh and make_deb.sh. They stay only while the build farm uses them. Use kiln package for your own packages.

Install the -dbgsym package too when you want symbols for an installed build. Install the main package with apt install ./file.deb rather than dpkg -i, because apt resolves the dependencies and the conflicts described below.

What the package declares

  • Version. A SemVer pre-release is written with a tilde: 0.1.4-rc.1 becomes 0.1.4~rc.1. dpkg reads a hyphen as the start of the Debian revision, so 0.1.4-rc.1 would sort above the final 0.1.4 and the release would count as a downgrade. The file name keeps the version as given.

  • Depends. dpkg-shlibdeps works this out from the shipped binaries. Each Debian package that owns a system copy of a library the package carries is dropped, because the package uses its own copy. package.fallback_depends is used only when shlibdeps is not installed, and its Debian Qt packages are left out. kiln refuses a package whose Depends still names a Debian Qt package.

  • Editions install side by side. OctoMY™ and Example Organization builds of the same app can be installed at once. On Windows, each edition gets its own Program Files\<Organization> folder; on Linux, each package keeps to its own paths:

    Path Example
    Binaries, under their own names /usr/lib/example/bin/construct
    The unambiguous command /usr/bin/example-construct
    Launcher, icon, AppStream id, Wayland app id com.example.construct
    Settings and data Where your data lives, with the edition's organization

    The bare construct command is an update-alternatives link. The edition installed first owns it, and sudo update-alternatives --config construct switches it. Hub finds agent and remote next to its own binary, which is why the binaries keep their unprefixed names.

    Edition packages from before this layout owned /usr/bin/<app> as real files, which update-alternatives will not replace. New packages therefore declare Breaks: <other edition> (<< 0.1.4~), and apt upgrades or removes the old package first.

  • Icons come from the edition's branding qrc (logo.svg, window/<app>.svg), not from a fixed file.

  • AppStream ids are the edition's androidPackagePrefix + app name, for example org.octomy.construct.

  • Maintainer scripts only manage the alternatives. The desktop-file-utils and hicolor-icon-theme triggers refresh the menu and icon caches.

The build validates the metainfo with appstreamcli and the launchers with desktop-file-validate, and fails on an error. If either tool is missing, it prints a warning. lintian runs when installed; it reports problems but never fails the build.

What the package carries

The package carries every library its programs load that no installed Debian package owns, and only those. With a Qt from the Qt installer, that is the Qt the programs were built with, its plugins and a qt.conf, below /usr/lib/<base>. kiln asks dpkg -S which libraries a Debian package owns, and libdeploy reads the rest from the programs themselves (kiln-file §KN-K23e). Each program gets the run path $ORIGIN/../lib, so no path of the build machine reaches the package.

Debian 13's Qt is 6.8.2, older than the 6.10 the build needs, so a package without its own Qt would load Debian's and stop at the first symbol 6.8.2 lacks. That is how one edition's 0.1.4-rc.6 package failed. kiln refuses the package when a symbol of a carried file is defined nowhere, or when a program keeps a run path outside the package.

What a software centre shows

Discover and GNOME Software get an app's long description, license and screenshots from AppStream metadata. Where they find it depends on how the package reaches the user:

Situation Source What is shown
A downloaded .deb opened in Discover PackageKit's GetDetailsLocal The control Description, the homepage and the size. License shows as unknown, because apt packages carry no license field. The package's metainfo is not read at this stage.
The app after installation The installed metainfo file Name, summary, description, license, screenshots, releases
An apt repository DEP-11 data from appstream-generator Everything, before installation

The installed metainfo file is /usr/share/metainfo/*.metainfo.xml.

The complete page before installation therefore needs an apt repository published with DEP-11 metadata. Screenshots need public, stable URLs, and kiln has no key for them yet.

Headless verification

The packaged binary must work without a display server:

construct --version -platform offscreen

Troubleshooting

kiln check gives a fix line

Run the command it gives, then kiln check again, until the last line is next: kiln build.

Module 'Qt.<name>' not found

A Qt component is missing. Match the module name to its -dev package — e.g. Qt.svgwidgets → qt6-svg-dev.

glsl shader compilation failed

qt6-shadertools-dev is missing or the wrong version. The project requires QShaderTools matching the runtime Qt version.


Build with qbs (the second path)

qbs builds the same tree until it retires. It needs more than kiln: a qbs newer than any distribution ships, a compiler and linkers of the machine, and a qbs profile. The facts of the qbs path are in Qbs reference → The qbs build convention.

What qbs needs besides the above

Component Version Debian package(s) Notes
Qbs 3.3.1+ none — build from source The floor is declared in integration/toolchain-requirements.json and enforced by project.qbs. No distro ships it, and Debian 13's 2.6.1 is on the knownBad list. See Qbs from source.
Compiler GCC 12+ build-essential C++20
mold any mold Required for the default (debug) qbs build — project.useMoldLinker defaults to true, so debug links pass -fuse-ld=mold. Either install mold or build with project.useMoldLinker:false.
lld any lld Release builds only — config:release with project.enableLto:true (the default) links with -fuse-ld=lld for LTO. Not used by debug builds.
clang any clang Only if you switch the qbs toolchain to clang. GCC is the configured/default toolchain of the qbs path.
sudo apt install build-essential mold lld

Qbs from source

qbs resolve on a stock Debian box stops immediately with qbs 2.6.1 is below the minimum 3.3.1 this build definition requires. That is the check working: 2.6.1's parallel scheduler SIGSEGVs on the android multi-arch graph, and the knownBad entry records which jobs it killed.

Two build dependencies fail configure in ways that do not obviously name themselves:

sudo apt install cmake ninja-build qt6-base-private-dev qt6-5compat-dev
# without qt6-base-private-dev:  Imported target "Qt6::CorePrivate" includes non-existent path
# without qt6-5compat-dev:       Could NOT find Qt6Core5Compat
curl -sLO https://download.qt.io/official_releases/qbs/3.3.1/qbs-src-3.3.1.tar.gz
tar xf qbs-src-3.3.1.tar.gz
cmake -S qbs-src-3.3.1 -B b -GNinja -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_PREFIX_PATH=/usr -DCMAKE_INSTALL_PREFIX=/usr/local -DWITH_TESTS=OFF
cmake --build b -j"$(nproc)" && sudo cmake --install b
which -a qbs && qbs --version      # /usr/local/bin/qbs must come first

Building against the distro Qt (-DCMAKE_PREFIX_PATH=/usr, as above) keeps qbs independent of what you install or remove under ~/Qt later, and needs no rpath flags. If you build it against a ~/Qt tree instead, add -DCMAKE_INSTALL_RPATH=<QtDir>/lib -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON or the installed binary picks up the distro Qt at runtime and dies with version 'Qt_6.10' not found.

One-time profile setup

qbs setup-toolchains --detect
qbs setup-qt /usr/lib/qt6/bin/qmake qt6      # or <QtDir>/bin/qmake6 for a ~/Qt tree
qbs config profiles.qt6.baseProfile x86_64-linux-gnu-gcc-14
qbs config defaultProfile qt6

Point the profile at the Qt the project actually resolves against. If you have both an apt Qt and a ~/Qt installer tree, the project's own probe may pick one while this profile names the other. The failure is loud but misleading: hundreds of lines of product '<lib>' is disabled and Dependency 'Qt.bluetooth' not found, because the apt Qt carries fewer optional modules. qbs resolve prints the Qt it chose on its third line — check it against this profile, and re-run setup-qt if they disagree.

baseProfile is not optional either: setup-qt creates a Qt-only profile and says so in a message that is easy to scroll past. Without a toolchain base profile the build has no compiler.

Verify:

qbs config --list profiles.qt6 | head -5

Build with qbs

# Build everything in the default profile
qbs build -f project.qbs

# Build a single product
qbs build -p hub

# Release build
qbs build -f project.qbs config:release

Output lands under default/install-root/usr/local/bin/.

qbs build parameter overrides

Parameter Default Description
project.enableLto true Link-time optimization (GCC/Clang/MSVC)
project.useMoldLinker true Use mold instead of ld (Linux only)
project.securityHardening true Stack protector, FORTIFY_SOURCE, PIE
project.treatWarningsAsErrors true -Werror

Feature gating via oc_use.json

Drop an oc_use.json file in the project root to disable expensive features of a qbs build. Kiln does not read it. Templates live in integration/oc_use_examples/. Useful when iterating on unrelated code:

cp integration/oc_use_examples/oc_use_disable_heavy.json oc_use.json
qbs build -p hub

qbs troubleshooting

qbs: command not found, or a qbs below the floor. Build qbs from source, as above. The qbs of Debian 13 and the one bundled with Qt Creator are older than the floor.

Slow link. Confirm mold is in use:

qbs build -p hub --command-echo-mode command-line 2>&1 | grep fuse-ld

If not, install mold and set project.useMoldLinker:true.


Remaining work

Tracked tasks for a production-quality Linux build:

  • [ ] apt repository with DEP-11: needed for Discover to show the description, license and screenshots before installation.
  • [ ] AppImage / Flatpak: No portable distribution format is supported.
  • [ ] Reproducible builds: the package stamps file times, the changelog and the AppStream release with the commit date, but the binaries themselves are not yet checked for bit-for-bit reproducibility.
  • [ ] The linux-deb image has no kiln: integration/cicd/targets/linux-deb calls kiln package deb since 2026-10-11, so it makes the same package as a workstation. The image installs neither kiln nor a Qt 6.10 or newer; its README says what to mount.

Builds run at low priority

Every process this build starts — compiler, linker, shader baker, help generator — is launched through nice -n 19, so compiling does not make the machine unusable while you work. Priority is inherited, so the whole process tree is covered.

A contended build therefore takes longer; on an idle machine there is no difference. It covers the processor only — a heavy link can still make the disk busy, and ionice does nothing on an NVMe device using the none scheduler.

To turn it off for an unattended batch build, or to make it stronger, see Build priority. The mechanism is one line.

A build through the kiln queue gets the same effect without the prefix: the broker lowers the priority of every job it starts, and of everything that job starts.

See also

Topics
howto linux debian build packaging
See also