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 “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 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 commandsadd_executable
,add_library
andadd_custom_target
.cmake_do_install
installs binary, configuration and other files into theimage
sub-directory of the package working directory – using the CMake commandinstall
.
The class also generates a CMake toolchain file 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).
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. 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
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
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
– 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, 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 may be helpful.
SUMMARY = "The super-duper Qt application"
AUTHOR = "Paul Smith (paul.smith@hardworking.com)"
HOMEPAGE = "https://www.hardworking.com/"
SUMMARY
describes what the software provided by the recipde does. If DESCRIPTION
is not given, SUMMARY
is used instead. AUTHOR
gives the name and email address of the person who wrote the recipe. 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
to Proprietary
and LICENSE_FLAGS
to commercial
(see also 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
. 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
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 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(${ProjectId} 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
tells the fetch
task to clone the my-app.git
repository using the ssh
protocol and switch to the branch main
. SRCREV_myapp
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
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
command sets the variable PROJECT_NAME
to MyApp
. It also sets a couple of less used PROJECT_*
variables.
include(GNUInstallDirs)
The CMake module GNUInstallDirs
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
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
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
. The file paths are relative to the directory containing the CMakeLists.txt
file. The qt_add_qml_module
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
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
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
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 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
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)
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)
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.