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. |
Install — apt (recommended)
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-devlibraries 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-devA convenient shortcut is to
apt installthe apt Qt-devpackages anyway (above) purely to pull in the transitive system libraries — you can still pointqt.qmakeat your~/Qtqmake.
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.1becomes0.1.4~rc.1. dpkg reads a hyphen as the start of the Debian revision, so0.1.4-rc.1would sort above the final0.1.4and the release would count as a downgrade. The file name keeps the version as given. -
Depends.
dpkg-shlibdepsworks 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_dependsis 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/constructThe unambiguous command /usr/bin/example-constructLauncher, icon, AppStream id, Wayland app id com.example.constructSettings and data Where your data lives, with the edition's organization The bare
constructcommand is anupdate-alternativeslink. The edition installed first owns it, andsudo update-alternatives --config constructswitches it. Hub findsagentandremotenext 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, whichupdate-alternativeswill not replace. New packages therefore declareBreaks: <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 exampleorg.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
~/Qtinstaller tree, the project's own probe may pick one while this profile names the other. The failure is loud but misleading: hundreds of lines ofproduct '<lib>' is disabledandDependency 'Qt.bluetooth' not found, because the apt Qt carries fewer optional modules.qbs resolveprints the Qt it chose on its third line — check it against this profile, and re-runsetup-qtif they disagree.
baseProfileis not optional either:setup-qtcreates 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-debcallskiln package debsince 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
- Build for Android — Cross-compile to Android targets
- Build for Windows (MSVC) — Native MSVC build
- Qbs Build System — Build system internals