# Managing dependencies with qiBuild: an overview ¶

## Introduction ¶

Here we will use the same project layout we used in the Managing dependencies between projects tutorial.

Make sure you have followed it before going further.

A quick reminder of what we want.

We have a project called  world  , which contains a dynamic library also called  world  . So we have a  libworld.so  on linux, and  libworld.dylib  on mac, and a  world.dll  on windows.

Then we have a project called  hello  , which contains a executable also called  hello  , which uses code from the  world  library.

Using the following two lines of cmake



qi_stage_lib(world)
qi_use_lib(hello world)



we want to:

• add the correct include directories when building  hello 
• link  hello  with the  world  library, without mixing a  world  library compiled in release with an  hello  executable compiled in debug (on windows at least)
• make sure that the  hello  executable will find the  world  library, without messing up with  PATH  ,  LD_LIBRARY_PATH  or  DYLD_LIBRARY_PATH 
• generate a  world-config.cmake  so that the  world  library is usable by standard CMake project when installed.

## Overview of the process: the power of the SDK layout ¶

If you have a look at  hello/qibuild.manifest,  you will see the following lines



[project "hello"]
depends = world



Each time you run qibuild, it looks for a .qi to guess your current worktree.

After this, the worktree is parsed to find  qibuild.manifest  files.

Here, there are two  qibuild.manifest  files, so qibuild can find the two projects:  hello  and  world  .

The relevant lines of the  CMakeLists.txt  are:.

In  world/CMakeLists 



qi_create_lib(world SRC world/world.h world/world.cpp)
qi_stage_lib(world)



In  hello/CMakeLists.txt 



qi_create_bin(hello "main.cpp")
qi_use_lib(hello world)



Note

For those already familiar with CMake:
We use  qi_create_lib  and  qi_create_bin  instead of  add_executable  and  add_library 

We never have to call  find_package  or  include_directories  , or  target_link_libraries  .

This first part is the job is done by the  qi_create_bin  and  qi_create_lib  functions.

Those are just wrappers for  add_executable  and  add_library  .

They just set a few properties (like the  RUNTIME_OUTPUT_LOCATION  for instance).

There are other properties that are used so that the executable can find the dynamic libraries it depends on at runtime, more on this later.

This way, we always generate binaries and libraries in the SDK directory. The  build/sdk  contains only the results of the compilation that are necessary to be used by other projects.

Also, the executables are created in  build/sdk/bin  , and the libraries in  build/sdk/lib  , so that we stick to the FHS convention inside the  build/sdk  directory.

On Windows, the binaries compiled in debug contain  _d  in their names, so you can share the same build directory, and the same Visual Studio solution for several build configurations, without the risk of a mix of binaries compiled in release and binaries compiled in debug.

This is done by something like



# in qibuild/general

set(QI_SDK_DIR ${CMAKE_BINARY_DIR}/sdk) # in internal/layout: qi_persistent_set(QI_SDK_BIN "bin") qi_persistent_set(QI_SDK_LIB "lib") # then, in target.cmake set_target_properties(${name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${QI_SDK_DIR}/${QI_SDK_BIN}
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${QI_SDK_DIR}/${QI_SDK_BIN}
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${QI_SDK_DIR}/${QI_SDK_BIN}
ARCHIVE_OUTPUT_DIRECTORY ${QI_SDK_DIR}/${QI_SDK_LIB}
LIBRARY_OUTPUT_DIRECTORY ${QI_SDK_DIR}/${QI_SDK_LIB}
)

if(WIN32)
set_target_properties("${name}" PROPERTIES DEBUG_POSTFIX "_d") endif()  The call to  qi_stage_lib  causes a  world-config.cmake  to be generated in  world/build/sdk/cmake/  When using  qibuild configure hello  , a  dependencies.cmake  files is generated in  hello/build/dependencies.cmake  (this file is automatically included by the  qibuild.cmake  file at the root of the  hello  project) This file contains a call to  list(INSERT CMAKE_FIND_ROOT_PATH 0 "QI_WORK_TREE/world/build/sdk")  So when  qi_use_lib(hello world)  is called, we only have run  find_package(world)  Since the variable  CMAKE_FIND_ROOT_PATH  is correctly set, CMake can find the  world-config.cmake  file in the build dir of world. Since everything under  build/sdk  follows the standard FHS conventions, finding the library in  sdk/lib  is also works. ## SDK and redistributable config files ¶ Note you can see qibuild as a way to automatically follow the cmake conventions See the CMake wiki for more information In fact we have two different  world-config  files. The first one is installed. It is supposed to be used with a  world  pre-compiled package, from an other machine than the one used to compile world. We call it the redistributable config file. The second one is generated in  build/sdk/share/cmake/world/world-config.cmake  so that CMake will find it if  CMAKE_FIND_ROOT_PATH  is set to  build/sdk.  We call it the SDK config file. There are several differences between the redistributable config file and the SDK config file. • The SDK file never has to call find_* functions: since we have just built the library, we know where it is. The redistributable file however must call  find_library  , and  find_path  . • The SDK file uses absolute paths : we don’t care because we will never share this file with anyone. The redistributable file must only use relative paths to the root dir of the package. This is how we can set  ROOT_DIR  to world-prefix from  world-config.cmake  We now we have a layout looking like:  world-prefix |__ share | |__ cmake | |__ world | |__ world-config.cmake |__ include | |__ world | |__ world.h |__ lib |__ libworld.so  So we generate the following code to set ROOT_DIR  get_filename_component(_cur_dir${CMAKE_CURRENT_LIST_FILE} PATH)
set(_root_dir "${_cur_dir}/../../../") get_filename_component(ROOT_DIR${_root_dir} ABSOLUTE)



## Calling qi_stage_lib ¶

The complete signature to  qi_stage_lib  is in fact:



qi_stage_lib(prefix
INCLUDE_DIRS  ...
PATH_SUFFIXES ...
DEFINITIONS   ...
DEPENDS ...
)



When flags are missing, we will guess them.

Note that prefix is always the name of a cmake target, i.e the first argument of something like  qi_create_lib  . There is an error message if you try to use  qi_stage_lib  on something that is not a target.

Let’s go through the variables one by one:

<PREFIX>_INCLUDE_DIRS
only used in the sdk file. During the configuration of hello, we will simply call  include_directories(WORLD_INCLUDE_DIRS) 

If not given, this can be guessed using the “directory properties”, like so:



get_directory_property(_inc_dirs INCLUDE_DIRECTORIES)


<PREFIX>_PATH_SUFFIXES
only used in the redistributable file. The file will contain something like:


set(WORLD_INCLUDE_DIRS
"${ROOT_DIR}/include" "${ROOT_DIR}/include/${WORLD_PATH_SUFFIXES}")  A few words about what this variable is for. Let’s assume a client of the world library wants to use  #include<world.h>  , but  world.h  is installed in  world-prefix/include/world/world.h  Other people, on the other hand, want to use  #include<world/world.h>  . The standard CMake way to deal with this is to call  find_path(WORLD_INCLUDE_DIR world.h PATH_SUFFIXES world) find_path(WORLD_INCLUDE_DIR world/world.h)  (hence the name of the variable) This will never be guessed, because it’s too specific. <PREFIX>_DEFINITIONS used by both config files. During the configuration of hello, we will simply call  set_target_properties(hello PROPERTIES COMPILE_DEFINITIONS "${WORLD_DEFINITIONS}"
)



This will never been guessed. We could have done something like:



get_target_property(_world_defs world COMPILE_DEFINITIONS)



But most of the time you don’t have to propagate the compile flags everywhere.

<PREFIX>_DEPENDS
used by both config files. If world depends on an thirdparty library (boost for instance), we want to make sure that whenever we use  qi_use_lib(hello world)  , we also add the boost include directories.

Unless the  world  headers have been very carefully written, (using private pointer implementations, forward declarations and the like), there’s a great chance we will also need the boost headers when compiling  hello,  that’s why we always propagate the dependencies by default.

This is guessed using the previous call to  qi_use_lib  . In our example, after using  qi_use_lib(world boost)  ,  WORLD_DEPENDS  contains “boost”.

<PREFIX>_LIBRARIES
used by both config files. In this case the SDK and the redistributable config file do not use the same code.

In the SDK file, we use something like:



get_target_property(_world_location world LOCATION)
set(WORLD_LIBRARIES_world_location})



In the redistributable file, we use:



find_library(world ...)
set(WORLD_LIBRARIES ...)



## Calling qi_use_lib ¶

So what happens when using a  qi_use_lib  ?

When using  qi_use_lib(foo bar)  , we will always call



find_package(bar)



But we have several cases here:

• We are using a  bar-config.cmake  that was generated by qibuild.
• We are using the custom  bar-config.cmake  in  qibuild/cmake/modules  . This can happen because the upstream  FindBar.cmake  does not exist or is not usable. (For instance, the upstream  FindGTest.cmake  sets  GTEST_BOTH_LIBRARIES,  instead of  GTEST_LIBRARIES  ...)
• We are using upstream CMake  FindBar.cmake  .

To do this, we have to search for the  -config.cmake  files generated by qiBuild, then look for upstream  Find-\*.cmake 

The relevant lines of code are:



find_package(${_pkg} NO_MODULE QUIET) find_package(${_pkg} REQUIRED)



Note

You can NOT specify optional dependencies when using qi_use_lib.

That’s because it’s hard to know from CMake whether the  foo-config.cmake  file was not found or the  foo-config.cmake  was found, the  FOO_INCLUDE_DIRS  was found, but not the  FOO_LIBRARIES  ). If you really want to have optional dependencies, you can do this this way:



find_package(FOO QUIET)

if(FOO_FOUND)