> ## Content Index
> Fetch the complete content index at: https://www.smallstepsystems.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# A Yocto Recipe for Qt Applications Built with CMake
- URL: https://www.smallstepsystems.com/a-yocto-recipe-for-qt-applications-built-with-cmake/
- Published: 2024-03-12T13:51:22.000Z
- Updated: 2025-12-28T19:03:49.000Z
- Author: Burkhard Stubert
- Tags: Applications & Services, Embedded Linux, qt, cmake, yocto

How hard can it be to write a Yocto recipe for building a Qt application with CMake? Actually, it turns out to be pretty hard. I have seen my fair share of slow-and-dirty workarounds (nothing is ever quick with Yocto, not even the diry workarounds) how to force the Qt application into the Linux image and onto the device. Over the years, I turned my own slow-and-dirty workarounds into a hopefully quick-and-clean solution. Here it comes.

## Context

After a painful struggle and probably with some external help, you managed to build the Linux image for your chosen SoM, SBC or panel PC (a.k.a. operator terminal). You can log in to your device with a serial console or SSH terminal and execute commands at the Linux prompt. You can build your Qt application against an SDK and copy it with `scp` or via USB drive to the device. The [Weston](https://wayland.pages.freedesktop.org/weston/?ref=smallstepsystems.com) "window manager" displays the Qt application full-screen and you can interact with it via touch.

This approach of installing an application on the device is good enough for developers but not for normal users. When your hundreds or even thousands of devices reach the end of the production line, you don't want the factory workers to install the Linux image first and then the application and its auxiliary files. This would be slow and error-prone. Instead, the workers should install the Linux image with the application in one step. Later in the field, users perform an OTA update to install the full Linux image with the application.

Eventually, you'll sit down and write a Yocto recipe for your CMake-based Qt application. This is a bit tricky, as the ways how Yocto and CMake work together are mysterious. You need to know how Yocto's `cmake` class calls CMake for configuration, compilation and installation, that all you need from the `qt6-cmake` class is the `QT_HOST_PATH`, and how the files installed by CMake end up in different Yocto packages or not.

In short, you need to understand the interface between Yocto/BitBake and CMake. And that's what my post focuses on. Additionally, you'll get templates for a Yocto recipe and a `CMakeLists.txt` file

## Controlling a CMake Build from a Yocto Recipe

### The Yocto Class cmake.bbclass

The Yocto class [poky/meta/classes/cmake.bbclass](https://github.com/yoctoproject/poky/blob/kirkstone/meta/classes/cmake.bbclass?ref=smallstepsystems.com) defines three tasks to build an application or library with CMake.

- `cmake_do_configure` generates the make files for the chosen generator (e.g., Ninja, Unix Makefiles).
- `cmake_do_compile` compiles and links the application and library targets specified by the CMake commands `add_executable`, `add_library` and `add_custom_target`.
- `cmake_do_install` installs binary, configuration and other files into the `image` sub-directory of the package working directory - using the CMake command `install`.

The class also generates a [CMake toolchain file](https://cmake.org/cmake/help/v3.29/manual/cmake-toolchains.7.html?ref=smallstepsystems.com#id8) `toolchain.cmake`, which facilitates the cross-compilation from the host to the target architecture (e.g., from `x86_64` to `aarch64`). You can find the toolchain file in the package working directory (`WORKDIR` in the [builld directory diagram](https://docs.yoctoproject.org/4.0.16/overview-manual/concepts.html?ref=smallstepsystems.com#configuration-compilation-and-staging)).

An abridged version of the CMake configure command looks like this:

```
cmake \
    ${OECMAKE_GENERATOR_ARGS} \
    ${OECMAKE_SOURCEPATH} \
    -DCMAKE_INSTALL_PREFIX:PATH=${prefix} \
    -DCMAKE_INSTALL_BINDIR:PATH=${@os.path.relpath(d.getVar('bindir'), ...} \
    -DCMAKE_INSTALL_LIBDIR:PATH=${@os.path.relpath(d.getVar('libdir'), ...} \
    ...
    -DCMAKE_TOOLCHAIN_FILE=${WORKDIR}/toolchain.cmake \
    ${EXTRA_OECMAKE}
```

The variable `${OECMAKE_GENERATOR_ARGS}` is set to either

```
-G 'Ninja' -DCMAKE_MAKE_PROGRAM=ninja
```

or

```
-G 'Unix Makefiles' -DCMAKE_MAKE_PROGRAM=make
```

earlier in the class file. CMake builds only support two generators: `Ninja` and `Unix Makefiles`. `Ninja` is the default generator. You can change this with the following setting in the recipe for your software:

```
OECMAKE_GENERATOR = "Unix Makefiles"
```

The variable `${OECMAKE_SOURCEPATH}` points to the source directory (`${S}` by default) containing the top-level `CMakeLists.txt` file. If your `CMakeLists.txt` is in the top-level directory of your source tree, you don't have to do anything. Otherwise, you must change the variable similar to this:

```
OECMAKE_SOURCEPATH = "${S}/hmi/app"
```

The CMake configure command continues by setting the `CMAKE_INSTALL_<dir>` variables, which get their values from the corresponding BitBake variables like `${prefix}`, `${bindir}` and `${libdir}`. These BitBake variables are defined in [poky/meta/conf/bitbake.conf](https://github.com/yoctoproject/poky/blob/kirkstone/meta/conf/bitbake.conf?ref=smallstepsystems.com). The script `${WORKDIR}/temp/run.do_configure` for running the CMake configure task would typically have the following definitions, where executables end up in `/usr/bin` and libraries in `/usr/lib`:

```
-DCMAKE_INSTALL_PREFIX:PATH=/usr \
-DCMAKE_INSTALL_BINDIR:PATH=bin \
-DCMAKE_INSTALL_LIBDIR:PATH=lib \
```

CMake uses the toolchain file `${WORKDIR}/toolchain.cmake` generated by `cmake.bbclass`. Among other things, the toolchain file defines the CMake variables for the C/C++ compilers, linkers and their flags.

```
set( CMAKE_C_COMPILER aarch64-poky-linux-gcc )
set( CMAKE_CXX_COMPILER aarch64-poky-linux-g++ )
set( CMAKE_C_FLAGS " -march=armv8-a+crc+crypto -fstack-protector-strong  -O2 ...")
set( CMAKE_CXX_FLAGS " -march=armv8-a+crc+crypto -fstack-protector-strong  -O2 ...")
set( CMAKE_CXX_LINK_FLAGS "...   -Wl,-z,relro,-z,now" CACHE STRING "LDFLAGS" )
```

The variable `${EXTRA_OECMAKE}` at the end of the CMake configure call enables recipes and other classes to add their own CMake variable definitions. The class `qt6-cmake.bbclass` and the standard Yocto recipe below will use this extension point.

### The Yocto Class qt6-cmake.bbclass

The class `meta-qt6/classes/qt6-cmake.bbclass`, which inherits `cmake.bbclass`, adds several CMake definitions to the Yocto variable `${EXTRA_OECMAKE}`. The following definitions are relevant for all CMake configurations:

```
EXTRA_OECMAKE += "\
    -DQT_CMAKE_DEBUG_EXTEND_TARGET=ON \
    -DQT_BUILD_INTERNALS_NO_FORCE_SET_INSTALL_PREFIX=ON \
    -DCMAKE_MESSAGE_LOG_LEVEL=${QT_MESSAGE_LOG_LEVEL} \
     \
    -DQT_HOST_PATH:PATH=${RECIPE_SYSROOT_NATIVE}${prefix_native}/ \
    -DQT_FORCE_BUILD_TOOLS=ON \
    \
    -DINSTALL_BINDIR:PATH=${@os.path.relpath(d.getVar('QT6_INSTALL_BINDIR'), d.getVar('prefix') + '/')} \
    -DINSTALL_DOCDIR:PATH=${... d.getVar('QT6_INSTALL_DOCDIR'), ...)} \
    -DINSTALL_EXAMPLESDIR:PATH=${... d.getVar('QT6_INSTALL_EXAMPLESDIR'), ...)} \
    -DINSTALL_INCLUDEDIR:PATH=${... d.getVar('QT6_INSTALL_INCLUDEDIR'), ...)} \
    -DINSTALL_LIBDIR:PATH=${... d.getVar('QT6_INSTALL_LIBDIR'), ...)} \
    ...
"
```

When cross-compiling a Qt application, the Yocto build must run tools like `moc`, `rcc`, `lupdate` and `lrelease` on the host (similar to the compiler). `[QT_HOST_PATH](https://doc.qt.io/qt-6/cmake-variable-qt-host-path.html?ref=smallstepsystems.com)` points to the base directory of a host Qt installation, where the build can find the host tools. Yocto builds the host tools before it builds the target Qt libraries.

The definition `[QT_FORCE_BUILD_TOOLS=ON](https://doc.qt.io/qt-6/configure-linux-device.html?ref=smallstepsystems.com#configuring-qt)` makes sure that the host tools are also built for the target. This option doesn't apply to building an application but to building Qt itself. The option implies that you build Qt on the target. 1, 2 or even 4 GB RAM are not enough to build Qt on the device. Besides, on-target builds are excruciatingly slow. Just don't do it! Cross-building on the host is the way to go.

The first two `QT_*` variables are undocumented. Similar to QT\_FORCE\_BUILD\_TOOLS, they seem to be relevant for building Qt itself.

Then, the class `qt6-cmake` defines `INSTALL_<dir>` variables to install executables (`BINDIR`), documentation (`DOCDIR`), examples (`EXAMPLESDIR`), headers (`INCLUDEDIR`), libraries (`LIBDIR`), etc. They are duplicating the `CMAKE_INSTALL_<dir>` variables from `cmake.bbclass`. They also add some new installation directories like `INSTALL_EXAMPLESDIR`, `INSTALL_SYSCONFDIR` and `INSTALL_TRANSLATIONSDIR`, which are Qt-specific. The `INSTALL_<dir>` variables are needed for building the Qt libraries and the SDK but not for Qt applications.

The following lines append three more CMake definitions, if a target build - specified by the [class override class-target](https://docs.yoctoproject.org/ref-manual/variables.html?ref=smallstepsystems.com#term-CLASSOVERRIDE) \- is performed.

```
EXTRA_OECMAKE:append:class-target = "\
    -DQT_HOST_PATH:PATH=${RECIPE_SYSROOT_NATIVE}${prefix_native}/ \
    -DQT_FORCE_BUILD_TOOLS=ON \
    -D__harfbuzz_broken_config_file=TRUE \
"
```

The first two definitions are known. The third definition fixes a problem with [HarfBuzz](https://en.wikipedia.org/wiki/HarfBuzz?ref=smallstepsystems.com), which the Qt libraries use for converting Unicode text into glyphs. Hence, it is only relevant for building the Qt libraries but not for Qt applications.

In short, only one variable definition from `qt6-cmake.bbclas`s is relevant for cross-building Qt applications: `QT_HOST_PATH`. All other definitions are only relevant for building the Qt libraries. So, your application recipe could inherit `qt6-cmake` and pull in a lot of irrelevant definitions, which hopefully don't do any harm. Or, it could inherit the base class `cmake` and add `QT_HOST_PATH` manually. I'll show you the second option in the next section, as it is a lot clearer what the recipe is doing.

## A Typical Yocto Recipe

Let me walk you through a typical Yocto recipe `my-app.bb` that builds a Qt application with CMake. You can adapt the recipe to your needs. The Yocto documentation with its detailed description how to [write a new recipe](https://docs.yoctoproject.org/4.0.16/dev-manual/new-recipe.html?ref=smallstepsystems.com) may be helpful.

```
SUMMARY = "The super-duper Qt application"
AUTHOR = "Paul Smith (paul.smith@hardworking.com)"
HOMEPAGE = "https://www.hardworking.com/"
```

[SUMMARY](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-SUMMARY) describes what the software provided by the recipde does. If `[DESCRIPTION](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-DESCRIPTION)` is not given, `SUMMARY` is used instead. `[AUTHOR](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-AUTHOR)` gives the name and email address of the person who wrote the recipe. `[HOMEPAGE](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-HOMEPAGE)` is the web page, where you can find more information about the software built by the recipe.

```
LICENSE = "Proprietary"
LICENSE_FLAGS = "commercial"
LIC_FILES_CHKSUM="file://LICENSE.md;md5=43f86fe61a366a458bba8a2523987918"
```

For commercial applications, you set `[LICENSE](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-LICENSE)` to `Proprietary` and `[LICENSE_FLAGS](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-LICENSE%5FFLAGS)` to `commercial` (see also [Enabling Commercially Licensed Recipes](https://docs.yoctoproject.org/4.0.16/dev-manual/licenses.html?ref=smallstepsystems.com#enabling-commercially-licensed-recipes)). You must add the line

```
    LICENSE_FLAGS_ACCEPTED:append = " commercial"
```

to your `local.conf` file. If you forget it, the package `my-app` is *not* included in the image.

You assign your license file and its checksum to `[LIC_FILES_CHKSUM](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-LIC%5FFILES%5FCHKSUM)`. If you run BitBake with an empty checksum `md5=`, it will tell you the correct checksum in an error message. Just copy this checksum into the recipe.

```
LICENSE = "MIT"
LIC_FILES_CHKSUM="file://MY-MIT-LICENSE;md5=8c99826849261ee2ff74870f2dae6c4b"
```

For packages licensed under FOSS licenses, you can skip `LICENSE_FLAGS`.

```
PV = "1.3.2"
```

`[PV](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-PV)` is the *"version of the recipe"*. No, it is not! `PV` is *"the version of the software being packaged"*, that is, it is the version of the software being built and installed by the recipe (see [Incrementing a Package Version](https://docs.yoctoproject.org/4.0.16/dev-manual/packages.html?ref=smallstepsystems.com#incrementing-a-package-version) for more details). That's quite different to the recipe version. For example, `PV` for all Qt 6.5.1 packages like `qtbase`, `qtserialbus` and `qtdeclarative` is `6.5.1`.

When you change the version in the source code of the package, you must not forget to change `PV` in the recipe - and vice versa. You must keep the two versions in sync.

```
inherit cmake
EXTRA_OECMAKE += " \
    -DQT_HOST_PATH:PATH=${RECIPE_SYSROOT_NATIVE}${prefix_native}/ \
    -DCMAKE_MODULE_PATH=${STAGING_DIR_TARGET}/${libdir}/cmake/B4OtaUpdate \
"
```

The recipe inherits the base `cmake` class - and not `qt6-cmake`. It adds two CMake definitions to `EXTRA_OECMAKE`. The `do_configure` task passes these two definitions to the CMake command for configuring the Qt application - as you would do when calling CMake on the command line.

The first definition sets `QT_HOST_PATH` to something like `/path/to/sdk/sysroots/x86_64-pokysdk-linux/usr` so that the cross-build knows where to find `moc`, `rcc` and other Qt host tools. `CMAKE_MODULE_PATH` defines the search paths for CMake modules exported by other recipes so that `my-app`'s `CMakeLists.txt` file can import the modules. For example, `my-app` could link against the library `libUpdateAdapter.so` from the module `B4OtaUpdate` with the following line in its `CMakeLists.txt` file:

```
target_link_libraries(${PROJECT_NAME} PRIVATE B4OtaUpdate::UpdateAdapter ...)
```

Of course, you'll add other CMake definitions to `EXTRA_OECMAKE` in the Yocto recipe of your Qt application - in addition to the definition for `QT_HOST_PATH`. I try to minimise the definitions added to `EXTRA_OECMAKE` by setting the variables to proper default values for a production build in `my-app`'s `CMakeLists.txt` file. You might want to do the same.

```
SRC_URI = "git://git@github.com:/psmith/my-app.git;name=myapp;branch=main;protocol=ssh"
SRCREV_myapp = "ac078eb65da118354c6c21911b35ec49444e18af"
S = "${WORKDIR}/git"

DEPENDS += "b4-ota-update qtbase qtdeclarative qttools-native"
```

`[SRC_URI](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-SRC%5FURI)` tells the `fetch` task to clone the `my-app.git` repository using the `ssh` protocol and switch to the branch `main`. `[SRCREV_myapp](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?ref=smallstepsystems.com#term-SRCREV)` makes `fetch` check out the given commit SHA. Note that `SRC_URI` differs from the URL that you would use in `git clone`:

```
git clone git@github.com:psmith/my-app.git
```

`[DEPENDS](https://docs.yoctoproject.org/4.0.16/ref-manual/variables.html?highlight=bb%5Fnumber%5Fthreads&ref=smallstepsystems.com#term-DEPENDS)` lists the recipes that must be built before the recipe `my-app.bb`. By including `qtbase`, for example, the build installs all libraries, headers, tools and other files from `qtbase` into the sub-directory `recipe-sysroot` of `my-app`'s working directory - before the `configure` task of `my-app` runs. This lets CMake find the library `libQt6Core.so.6.5.1`, if you add `Qt6::Core` to the `target_link_libraries` command of the application's `CMakeLists.txt`.

The dependency `qttools-native` looks a bit odd, as its artifacts are for the host computer. It provides host tools like `linguist`, `lrelease` and `lupdate`, which are needed during development and during the build but not on the device.

## A Typcial CMakeLists.txt File

### Configuring and Building an Application

After all this preparation, it is high time to show you a typical top-level `CMakeLists.txt` file that the recipe uses to build your Qt application. Here is a step-by-step walk-through.

```
cmake_minimum_required(VERSION 3.22)
project(MyApp)
```

At the time of this writing, the current patch version of Yocto 4.0 (kirkstone) uses CMake 3.22\. Older patch versions may use older CMake versions, newer patch versions newer CMake versions. Using newer CMake versions for building Qt applications has never been a problem for me. For Qt 6, you are on the safe side with CMake 3.19 or newer.

The `[project](https://cmake.org/cmake/help/latest/command/project.html?ref=smallstepsystems.com#project)` command sets the variable `PROJECT_NAME` to `MyApp`. It also sets a couple of less used `PROJECT_*` variables.

```
include(GNUInstallDirs)
```

The CMake module `[GNUInstallDirs](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html?ref=smallstepsystems.com)` assigns the standard Linux installation paths to the variables `CMAKE_INSTALL_<dir>`. For example:

```
CMAKE_INSTALL_BINDIR = bin
CMAKE_INSTALL_INCLUDEDIR = include
CMAKE_INSTALL_LIBDIR = lib
CMAKE_INSTALL_DATAROOTDIR = share
...
```

All `CMAKE_INSTALL_<dir>` directories are relative to `CMAKE_INSTALL_PREFIX`, which has the default value `/usr`. The values of the `CMAKE_INSTALL_<dir>` directories are used for the `DESTINATION` argument of the `install` commands (see below).

```
# Global settings for this project and its sub-projects
set(B4_UPDATE_ADAPTER_CLIENT "SwUpdate")
set(B4_UPDATE_ADAPTER_SERVER "Memfault")

# Libraries and tools built by this project
add_subdirectory(adapters/update)
```

The two `set` commands specify that the `UpdateAdapter` library, which is added to the project by the `[add_subdirectory](https://cmake.org/cmake/help/latest/command/add%5Fsubdirectory.html?ref=smallstepsystems.com#add-subdirectory)` command, shall use `SwUpdate` as the update client on the device and `Memfault` as the update server for managing the device fleet. The `add_subdirectory` command adds the `UpdateAdapter` library to this project. The Yocto recipe builds both the Qt application `MyApp` and the library `UpdateAdapter` in one go.

In general, the CMake variables from this section configure which parts of the project are built and how they are build. They apply to this project and all its sub-projects included through `add_subdirectory` commands.

```
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
```

This project mandates the use of the C++17 standard. You cannot use C++20 features, but you can use C++11 or C++98 features. Qt 6 supports C++17 by default.

```
find_package(Qt6 6.4 COMPONENTS Gui Quick REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
```

The `[find_package](https://cmake.org/cmake/help/latest/command/find%5Fpackage.html?ref=smallstepsystems.com#find-package)` command sets up the search paths where to find the Qt modules `QtGui` and `QtQuick` from Qt 6.4\. The two Qt modules are mandatory for building the project. After the `find_package` command, other commands like `target_link_libraries` can refer to the Qt modules as `Qt6::Gui` and `Qt6::Quick`. You don't have to provide link paths for the g++ compiler option `-L` or include paths for the option `-I`. CMake takes care of this.

The last two settings make CMake automatically apply `moc` to C++ files containing the `Q_OBJECT` macro and `rcc` to `.qrc` files.

```
add_executable(${PROJECT_NAME} main.cpp sub1/a.cpp sub1/a.h ...)

qt_add_qml_module(${PROJECT_NAME} URI Main VERSION 1.0 RESOURCE_PREFIX /
    QML_FILES main.qml A.qml ...)

target_include_directories(${PROJECT_NAME} PRIVATE sub1)

target_link_libraries(${PROJECT_NAME}
    PRIVATE B4OtaUpdate::UpdateAdapter Qt6::Gui Qt6::Quick)
```

You pass a list of C++ source and header files to `[add_executable](https://cmake.org/cmake/help/latest/command/add%5Fexecutable.html?ref=smallstepsystems.com#add-executable)`. The file paths are relative to the directory containing the `CMakeLists.txt` file. The `[qt_add_qml_module](https://doc.qt.io/qt-6/qt-add-qml-module.html?ref=smallstepsystems.com)` command creates a QML module `Main` from the QML source files given after `QML_FILES`. You can load `main.qml` from the `main()` function with the following commands:

```
    QQmlApplicationEngine engine;
    engine.load(QUrl(u"qrc:/Main/main.qml"_qs));
```

As usual, you can instantiate other QML components (e.g., `A`) from the same module without explicitly importing `Main`. As your application is growing, you'll introduce more QML modules in sub-projects - included with `add_subdirectory` commands.

The command `[target_include_directories](https://cmake.org/cmake/help/latest/command/target%5Finclude%5Fdirectories.html?ref=smallstepsystems.com#target-include-directories)` adds include directories (here: `sub1`) to the target object `${PROJECT_NAME}` (here: `MyApp`) defined by `add_executable` (`add_library` for libraries). During the build, CMake passes the include directories to the C++ compiler with the option `-I` so that the compiler knows where to search for the header files.

The second argument of `target_include_directories` controls the access to the target object. `PRIVATE` include directories are only visible to the current target but not to other targets using the current target. PUBLIC include directories are also visible to other targets. The difference is irrelevant for top-level targets like `MyApp`. However, it is relevant for a library or executable target `A` linking against a library target `B`. Target `B` specifies which headers should be visible to `A` through the `PUBLIC` include directories and which headers should not be visible to `A` through the `PRIVATE` include directories.

The command `[target_link_libraries](https://cmake.org/cmake/help/latest/command/target%5Flink%5Flibraries.html?ref=smallstepsystems.com#target-link-libraries)` adds library target objects to the current target. A library target object like `Qt6::Gui` provides the include search paths, library search paths and library names through its public properties to the current project. CMake translates these properties into the arguments for the compiler options `-I`, `-L` and `-l`, respectively.

Again, the second option of `target_link_libraries` controls the access for other targets using the current target. If `MyApp` were a library target aptly called `MyLib` with the above `target_link_libraries` definition, another target would have to pass `Qt6::Gui` in addition to `MyLib` to its own `target_link_libraries` command. The other library doesn't know that `MyLib` uses `Qt6::Gui`, as `Qt6::Gui` is `PRIVATE`. If `Qt6::Gui` were `PUBLIC` in `MyLib`, the other target could skip `Qt6::Gui` and would get away with listing only `MyLib` in its `target_link_libraries`.

### Installing Executables

The commands in the `CMakeLists.txt` file so far contribute to the Yocto tasks `do_configure` and `do_compile`. The CMake `install` commands tell the Yocto task `do_install` which files to install in the rootfs image. The `do_install` task fails, if your project's `CMakeLists.txt` files don't contain any `install` commands.

```
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
```

This `[install](https://cmake.org/cmake/help/latest/command/install.html?ref=smallstepsystems.com#targets)` command installs the executable (the `RUNTIME`) to the `DESTINATION` directory `${CMAKE_INSTALL_BINDIR}`, which is set to `bin` by the CMake module `GNUInstallDirs` at the beginning of this `CMakeLists.txt` file. The destination directory is prefixed by `${CMAKE_INSTALL_PREFIX}`, which defaults to `/usr`. You can find the executable in the file `/usr/bin/MyApp` on the device.

```
build/tmp/work/armv8a-mx8mp-poky-linux/my-app/0.1.0-r0/      # package working dir
    image/usr/bin/                                           # staging dir
        MyApp                                                # executable
```

The Yocto build stores the executable in the staging directory `image/usr/bin`, which is a sub-directory of the package working directory. The Yocto task `do_rootfs` copies the contents of the `image` sub-directory of all the packages to the `rootfs` sub-directory of the working directory of the image package. The executable could, for example, end up in the directory

```
build/tmp/work/my-machine-poky-linux/my-image/1.0.3-r0/rootfs/usr/bin
```

The Yocto task `do_image_ext4` creates an image for an ext4 file system, which you could burn on an SD card or into the internal eMMC storage of your device.

There are special `install` commands for installing dynamic and static libraries, files, directory trees and other output artifacts. Let me walk you through the most common variants (see the [documentation for install](https://cmake.org/cmake/help/latest/command/install.html?ref=smallstepsystems.com) for other variants).

### Installing Shared Libraries

```
project(UpdateAdapter)
add_library(${PROJECT_NAME} SHARED <cpp-files> <h-files>)
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
```

The Yocto task `do_install` dutifully installs the shared library `libUpdateAdapter.so` in the staging directory `image/usr/lib` with the CMake `install(TARGETS)` command. However, the task `do_package_qa` fails with this cryptic error message:

```
ERROR: b4-ota-updater-0.3.1-r2 do_package_qa: QA Issue: -dev package b4-ota-updater-dev
    contains non-symlink .so '/usr/lib/libUpdateAdapter.so' [dev-elf]
ERROR: b4-ota-updater-0.3.1-r2 do_package_qa: Fatal QA errors were found, failing task.
```

The `b4-ota-updater-dev` package contains the file `libUpdateAdapter.so` itself but should contain a link to it or its versioned cousins (e.g., `libUpdateAdapter.so.1`). You find the RPM packages in the sub-directory `deploy-rpms/armv8a` of the package working directory. You can check its contents with this command:

```
$ rpm -qlp b4-ota-updater-dev-0.3.1-r2.armv8a.rpm
...
/usr/include/UpdateController.h
/usr/lib/libUpdateAdapter.so
```

The real problem is that there is a `dbg`, `dev`, `lic` and `src` package but no main package. The main package should contain the library file, to which the library in the `dev` package can link. As the main package doesn't even exist, an application depending on the main package `b4-ota-update` would fail to build.

The configuration file `bitbake.conf` defines, which build artifacts (libraries, headers, sources, licenses, etc.) go into which package. Here are the definitions relevant for libraries with the fully evaluated path patterns in the comments:

```
SOLIBS = ".so.*"
SOLIBSDEV = ".so"
FILES:${PN} = "... ${libdir}/lib*${SOLIBS} ..."             # /usr/lib/lib*.so.*
FILES_SOLIBSDEV ?= "... ${libdir}/lib*${SOLIBSDEV} ..."     # /usr/lib/*.so
FILES:${PN}-dev = "... ${FILES_SOLIBSDEV} ..."              # /usr/lib/*.so
```

`FILES:${PN}` holds the path patterns for the main package and `FILES:${PN}-dev` for the `dev` package. So, library files like `/usr/lib/libUpdateAdapter.so.1.0.0` and `/usr/lib/libUpdateAdapter.so.1` end up in the main package and library files like `/usr/lib/libUpdateAdapter.so` in the `dev` package. The CMake build only produces unversioned `*.so` files but not versioned `*.so.1` or `*.so.1.0.0` files. You change this by setting the properties `VERSION` and the `SOVERSION` for the library target.

```
project(UpdateAdapter)
add_library(${PROJECT_NAME} SHARED <cpp-files> <h-files>)
set_target_properties(${PROJECT_NAME} PROPERTIES
    VERSION 1.0.0
    SOVERSION 1)
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
```

Adding the `[set_target_properties](https://cmake.org/cmake/help/latest/command/set%5Ftarget%5Fproperties.html?ref=smallstepsystems.com#set-target-properties)` command eliminates the build error, creates a main package and puts the library file and its links into the right packages. By the way, the base name of the package changes from `b4-ota-updater` to `libupdateadapter`. The following checks corroborate this.

```
$ cd deploy-rpms/armv8a
$ rpm -qlp libupdateadapter1-0.3.1-r2.armv8a.rpm
...
/usr/lib/libUpdateAdapter.so.1
/usr/lib/libUpdateAdapter.so.1.0.0
$ rpm -qlp libupdateadapter-dev-0.3.1-r2.armv8a.rpm
...
/usr/include/UpdateController.h
/usr/lib/libUpdateAdapter.so
```

The contents of the RPM packages look OK.

```
$ cd image/usr/lib
$ ls -l
libUpdateAdapter.so -> libUpdateAdapter.so.1
libUpdateAdapter.so.1 -> libUpdateAdapter.so.1.0.0
libUpdateAdapter.so.1.0.0
```

The `dev` package now contains a link from `libUpdateAdapter.so` to `libUpdateAdapter.so.1` from the main package. So, the links are OK now. The last two entries are installed on the device. They also end up in the SDK together with the first entry.

### Installing Files and Directories

```
install(FILES a.h b.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
```

The `[install(FILES)](https://cmake.org/cmake/help/latest/command/install.html?ref=smallstepsystems.com#files)` command puts the two header files into the staging directory `image/usr/include`. Yocto's packaging task then puts them into the `dev` package. The `populate_sdk` task installs the headers in the SDK but not in the rootfs. The `install` command should only list public header files and not private ones, as other software should not depend on the private headers of a library. The `install(FILES)` command can deploy any files - e.g., image, calibration, configuration and database files - into the rootfs.

```
install(DIRECTORY data/profiles/ DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/profiles"
```

The `[install(DIRECTORY)](https://cmake.org/cmake/help/latest/command/install.html?ref=smallstepsystems.com#directory)` command recursively copies the contents of the directory `data/profiles/` to the staging directory `image/usr/share/profiles`. Whether the source directory ends with a slash or not makes a difference. With a trailing slash, the source file `data/profiles/x/y.c` is installed as `shares/profiles/x/y.c`. Without a trailing slash, it it is installed as `shares/profiles/profiles/x/y.c`. The latter is rarely what you want.