qiBuild and CMake¶
Introduction¶
This section is targeted to users already using CMake for their build system.
See also
qiBuild is built on top of CMake.
Ultimately, every qi_
functions call standard CMake functions, so for instance
qi_create_bin
accepts every argument the standard
add_executable
CMake function does.
After installing a qiBuild project, you can use the -config.cmake
files generated
by qiBuild even from a pure CMake project.
A very high-level point of view¶
One way to think about the qibuild
is to compare this two few samples
# Find some deps for the whole project
find_package(foo COMPONENT spam eggs)
# Add include path for every target in this file
include_directories(${FOO_INCLUDE_DIRS})
# Create a bar library
add_library(bar bar.h bar.c)
# Link the bar target with foo libraries
# (both libfoo_spam.so and libfoo_eggs.so)
target_link_libraries(bar ${FOO_LIBRARIES})
# Create a baz library
add_library(baz bar.h bar.c)
# Link the baz target with foo libraries
# (both libfoo_spam.so and libfoo_eggs.so)
target_link_libraries(bar ${FOO_LIBRARIES})
# Lots and lots of install rules
qi_create_lib(bar bar.h bar.c)
# Find the SPAM component of foo,
# add the foo/spam include directory, and only link with
# libfoo_spam.so
qi_use_lib(bar FOO_SPAM)
# Find the EGGS component of foo,
# add the foo/eggs include directory, and only link with
# libfoo_eggs.so
qi_use_lib(baz FOO_EGGS)
# Install rules for bar, baz already created,
# bar-config and baz-config generated and ready to be installed to
You can see than in fact everything in qibuild is close to a target: we tie together the include directories and the libraries, making it easier to avoid weird link errors (i.e the include directory was correct but you forget to link with the library)
Also note how easy it is to make sure that someone using bar will only depend on FOO_SPAM
,
and not the whole FOO
package.
CMake variables¶
To make sure you do not break qiBuild behavior you should manipulate the CMAKE_FIND_ROOT_PATH and CMAKE_MODULE_PATH variables carefully.
For instance:
# This will break finding packages in the toolchain:
set(CMAKE_FIND_ROOT_PATH "/path/to/something")
# This won't:
# (create an empty list if CMAKE_FIND_ROOT_PATH does not exist)
if(NOT CMAKE_FIND_ROOT_PATH)
set(CMAKE_FIND_ROOT_PATH)
endif()
list(APPEND CMAKE_FIND_ROOT_PATH "/path/to/something")
# This will break finding the qibuild framework
# include (qibuild/general) will no longer work
set (CMAKE_MODULE_PATH "/path/to/something")
# This won't
# (create an empty list if CMAKE_FIND_ROOT_PATH does not exist)
if(NOT CMAKE_MODULE_PATH)
set(CMAKE_MODULE_PATH)
endif()
list(APPEND CMAKE_MODULE_PATH "/path/to/something")
CMake functions¶
Creating executables¶
Using :cmake:function:qi_create_bin
will make sure that:
- The executable is generated in
build/sdk/bin
- An install rule is created to
<prefix>/bin
- On linux, rpath is set to
$ORIGIN/../lib
You can change this behavior using various NO_
arguments
to qi_create_bin
(for instance NO_INSTALL
, NO_RPATH
...),
or simply call set_target_properties
yourself
Creating libraries¶
Using qi_create_lib
will make sure that:
- If the library is static, it is generated in
build/sdk/lib
- If the library is shared, it is generated in
build/sdk/bin
on Windows, and inbuild/sdk/lib
on linux - The install rules are created accordingly
- On linux,
-fPIC
is used so that you can use the static library inside a shared library - On mac, the install name dir is set to
@executable_path/../lib
You can change this behavior using various NO_
arguments
to qi_create_bin
(for instance NO_FPIC
, NO_INSTALL
...), or simply call set_target_properties
yourself
The library will be:
- built as a shared library on UNIX
- built as a static library on Windows
You can can set BUILD_SHARED_LIBS=OFF
to compile everything in static by
default.
Installing¶
Using qi_install
functions will make sure that:
- You will get an error if the files you want to install do not exist at configuration time, not at install time.
Exporting targets¶
The export()
and install(EXPORT ...)
command do exist in standard CMake
but they are a bit clumsy to use.
(See qi_stage_lib versus export for details)
In qibuild
, you have a much nicer API
qi_stage_lib(world)
qi_use_lib(hello world)
Using qi_use_lib
in conjunction with qi_stage_lib
work in any of the following cases:
- world and hello are both targets in the same project
- world and hello are two targets in two different projects in the same worktree (providing a small configuration file)
- world is a package in a toolchain
- world is a library that has been found by a custom qibuild module in cmake/qibuild/modules/world-config.cmake
- world is a library installed on the system that has been found by an upstream CMake module in /usr/share/cmake/modules/FindWorld.cmake
Plus, qi_use_lib
will export sane defaults for you:
- include directories will be set to the last call to include_directories
- WORLD_DEPENDS will be set using the call to
qi_use_lib(... world)
And still, you will be able to stage different include directories or dependencies if you want.
Even better, you can still use standard CMake code:
find_package(world)
include_directories(${WORLD_INCLUDE_DIRS})
add_library(hello)
target_libraries(hello ${WORLD_LIBRARIES})
You do not need to read the world-config.cmake because you know the exported variables will always have the same name: <target>_INCLUDE_DIRS and <target>_LIBRARIES
qitest versus CTest¶
qi_stage_lib versus export¶
You may wonder why qi_stage_lib
does not use export
.
There are several reasons but the main reason is that we did not like the idea of the “global CMake package registry”.
One workflow we needed to support since the beginning was to be able to use the
same worktree to compile for two different targets (say linux64
and
cross-compiling)
Also, export
does not work that well when you want to work with several versions
of the same target (say master
and a release-1.12
branch).
You can kind of solve that using version numbers (in a
FooConfigVersion.cmake
) for instance, but that’s a bit clumsy too.