Porting a CMake project to qiBuild¶
Requirements¶
This tutorial assumes that you already have a CMake-based project.
We will see how qiBuild can help you writing less code, while staying
close to the “official” CMake recommendations when dealing with the
Find<>.cmake
or <>-config.cmake
files.
In this tutorial, we will use a simple project called foobar
.
It is pure CMake code, there is a foo
library, and a bar
executable linking
with the foo
library.
The sources of the pure CMake foobar
project can be found here:
foobar_pure_cmake.zip
Extract the archive in you qiBuild worktree, you should end up with something like:
.qi
|__ qibuild.xml
|__ foobar
|__ CMakeLists.txt
|__ libbar
|__ CMakeLists.txt
|__ bar
|__ bar.h
|__ bar.cpp
|__ foo
|__ CMakeLists.txt
|__ main.cpp
A standard CMake project¶
The standard CMakeLists.txt
files for such a project look like this:
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(foobar)
add_subdirectory(libbar)
add_subdirectory(foo)
libbar/CMakeLists.txt
include_directories(".")
add_library(bar
bar/bar.hpp
bar/bar.cpp)
install(TARGETS bar
RUNTIME DESTINATION "lib"
ARCHIVE DESTINATION "lib"
LIBRARY DESTINATION "lib")
install(FILES bar/bar.h
DESTINATION "include/bar")
foo/CMakeLists.txt
include_directories("${CMAKE_SOURCE_DIR}/libbar")
add_executable(foo main.cpp)
target_link_libraries(foo bar)
install(TARGETS foo DESTINATION "bin")
A few CMake limitations¶
You have to specify install rules for every target
If you move the
bar
library to an other directory, you will have to fixfoo/CMakeLists.txt
You cannot use
foobar
as a subdirectory of a new project (because of the use ofCMAKE_SOURCE_DIR
You have a standard layout when you install your targets:
<prefix> |__ lib |__ libbar.a |__ bin |__ foo |__ include |__ bar |__ bar.hpp
But it has nothing to do with where targets are in your build directory. (foo
is somewhere in build/foo/
and libbar.a
in build/bar
).
- If you want to give a
foobar
SDK for someone working with Visual Studio, you will have to make surelibbar
andfoo
contain a_d
when there are build on debug (unless you are very careful, you cannot mix debug and release libraries on Visual Studio, so the_d
is the safest way to do it) - If you want other people to use the
bar
library from an other project, you will have to configure abar-config.cmake
looking like:
find_path(BAR_INCLUDE_DIR bar/bar.hpp)
find_library(BAR_LIBRARY bar)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(bar
DEFAULT_MSG
BAR_INCLUDE_DIR
BAR_LIBRARY)
mark_as_advanced(${BAR_INCLUDE_DIR} ${BAR_LIBRARY})
(and of course create the install rule for the bar-config.cmake)
- Then, someone willing to use the
bar
library from an other project can do:
find_package(bar)
include_directories(${BAR_INCLUDE_DIRS})
add_executable(myexe ...)
target_link_libraries(myexe ${BAR_LIBRARIES})
This assumes that the person has installed the bar
packaged somewhere CMake can
find it. (For instance in /usr/local/share/cmake/bar-config.cmake
), or that
he sets -DBAR_DIR
.
It the person also happens to have the foboar
sources built somewhere, it
cannot use them...
Neither libbar
or bar.hpp
can be found by CMake: bar.hpp
is hidden somewhere in the
sources of foobar,
and libbar.a
somewhere in the build directory of foobar,
so
it is impossible to use the carefully home-made bar-config.cmake
, unless you
install libbar
to /usr/local/lib/libbar.a
for instance.
qiBuild to the rescue!¶
The motivation for qiBuild is to help solve this CMake limitations with a clean, easy way, while staying the more compatible possible with other CMake projects.
Preparation¶
Add a call to find_package(qibuild)
file at the root of the project and have it included right
after the project()
line.
cmake_minimum_required(VERSION 2.8)
project(foobar)
find_package(qibuild)
Note that you somehow have to find the qibuild-config.cmake
find from you qibuild sources,
if qibuild
is not installed on your system.
You can do that by:
- Using
cmake -Dqibuild_DIR=/path/to/qibuild/cmake/qibuild
,
or:
- Create the
qiproject.xml
and useqibuild configure
which will set theqibuild_DIR
CMake variable for you.
Install rules¶
Replace the add_library
by qi_create_lib
, and remove
the install rules to use qi_install_header
instead:
include_directories(".")
qi_create_lib(bar
bar/bar.hpp
bar/bar.cpp)
qi_stage_lib(bar)
qi_install_header(bar/bar.hpp
SUBFOLDER bar)
Using qi_create_lib
and qi_install_header
will have
the following effects:
- The install rules will been properly generated for the library
- For the headers, you must choose a subfolder in which to put your headers.
(otherwise, it is too easy to have conflicts, especially when you are
generating a big SDK.) Unless you have a very good reason not to, please
choose the same folder name to put you headers inside your source tree, and
once your header is installed. (here, the
bar
argument ofqi_install_header
matches the location ofbar.hpp
:bar/bar.hpp
). - A
sdk
directory will be created, withlibbar
inskd/lib
See also
Using the bar library¶
Add the following line in libbar/CMakeLists.txt
:
qi_stage_lib(bar)
And replace code in foo/CMakeLists.txt
to have
qi_use_lib(foo bar)
(no need to call include_directories
or target_link_libraries
anymore)
You should end up with
qi_create_bin(foo main.cpp)
qi_use_lib(foo bar)
So what happened?
Two versions of the foo-config.cmake
file have been generated:
- The first one is in
build/cmake/sdk/bar-config.cmake
: this one is supposed to be installed. You can see it is only using relative paths to find the library. - The second one is in
build/sdk/cmake/bar-config.cmake
: this one is supposed to be used inside your project: it contains absolute paths only.
So, since the layout in build/sdk
is the same as the layout when the library is
installed, and since the foo-config
file has been automatically generated
(along with the install rules), it makes no difference whether you want to find
the bar
library you have just built in the foobar
project, using the bar
library you have just built in a other project, or using the installed bar
library.
Finding the bar-config.cmake
in foobar/build/skd
from an other project is as
easy as:
list(APPEND CMAKE_FIND_ROOT_PATH "/path/to/foobar/build/sdk")
Finding the bar-config.cmake
once bar has been installed in as easy as:
# No qiBuild required: the installed bar-config.cmake contains
# no qibuild-specific code:
find_package(bar)
include_directories(${BAR_INCLUDE_DIRS})
add_library(foo)
target_link_libraries(${BAR_LIBRARIES})
# Or, still using qibuild:
qi_use_lib(... bar)
Note
We always generate variables in the form <PREFIX>_INCLUDE_DIRS and <PREFIX>_LIBRARIES (all upper case, no version number, plural form)
Conclusion¶
This is what the final code looks like when you are done:
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(foobar)
find_package(qibuild)
add_subdirectory(libbar)
add_subdirectory(foo)
libbar/CMakeLists.txt
include_directories(".")
qi_create_lib(bar
bar/bar.hpp
bar/bar.cpp)
qi_stage_lib(bar)
qi_install_header(bar/bar.hpp
SUBFOLDER bar)
foo/CMakeLists.txt
qi_create_bin(foo main.cpp)
qi_use_lib(foo bar)
Less code, so many features !
- You have a nice layout in
build/sdk
- You can use the newly compiled bar library inside the
foobar
project, outside thefoobar
project, or using an installedfoobar
package with always the same line:
qi_use_lib(foo bar)
- You did not have to write any install rule.
- You did not have to write any
bar-config.cmake.
- You can build SDK packages for other people to use, even on Visual Studio, without handling all the annoying cross-platform stuff (for instance, on windows, the .dll must be generated next to the .exe otherwise the use has to set %PATH%, and so on...)
- It is still pure, standard CMake code: you did not have to use the qibuild script.
- Absolutely nothing has been generated in the source directory,
build/sdk
only contains the useful, re-distributable binaries (no .o here)
The final project can be found here:
foobar_qibuild.zip