# Building CMake projects ¶

For this overview, we will assume we have:

• A  foo-sdk  toolchain containing a packages , named  bar 
• A worktree containing two projects , the  world  project, and a  hello  project, which depends on  world  and  bar  .
• A build profile named  my-profile  configured in the worktree.

This overview guides you through all what happens from the moment you run:



\$ qibuild configure --worktree=/path/to/worktree \
--config foo-sdk \
--profile my-profile \
--release \
-DWITH_SPAM=ON \
hello



to every cmake code that is generated, and what CMake flags are passed.

(We chose a complex example here on purpose to show all the various cases that need to be handled)

## Command line parsing - first pass ¶

This is done by  qisys.script.root_command_main  from  bin/qibuild  script.

We look for every module in  qibuild.actions  , and find the  configure.py  module.

Then, we create a  argparse.ArgumentParser  parser, and run  qibuild.configure.configure_parser  on it.

We parse the command line arguments using this parser, and we now have a  argparse.NameSpace  object we can pass to  qibuild.configure.do  .



# in qibuild.actions.configure

def configure_parser(parser):
# Calls functions in qibuild.parsers to add all the options
# this command recognize

def do(args):
# In this point we have a argparse.Namespace object with the "raw"
# result of the arguments parsing
args.worktree = "/path/to/worktree"
args.build_type = "Release"
args.config = "foo-sdk"
args.projects = ["hello"]
args.profiles = ["my-profile"]



This “raw” command parsing already took care of simple tasks, like making sure the  --debug  or  --release  arguments are converted to a proper  CMAKE_BUILD_TYPE  .

## Command line parsing - second pass ¶

The goal is to get a correctly initialized  CMakeBuilder  object

This is done in just a single line:



# in qibuild.actions.configure

def do(args):
cmake_builder = qibuild.parsers.get_cmake_builder(args)



The  get_  functions in  qibuild.parsers  are here to factorize code that must be called in every action that uses a BuildWorkTree.

The  get_cmake_builder  action looks like



# in qibuild.parsers

def get_cmake_builder(args):
""" Get a CMakeBuilder object from the command line

"""
build_worktree = get_build_worktree(args)
# dep solving will be made later by the CMakeBuilder
build_projects = get_build_projects(build_worktree, args, solve_deps=False)
cmake_builder = qibuild.cmake_builder.CMakeBuilder(build_worktree, build_projects)
cmake_builder.dep_types = get_dep_types(args)
return cmake_builder



Here’s what those functions do:

### get_build_worktree ¶

• A new WorkTree object is initialized using the path given in args.worktree, or by exploring parent directories until a  .qi  directory is found if  --worktree  is not given At this point, every path registered in the worktree can be found in  worktree.projects 

• A new BuildWorkTree is initialized. A list of  BuildProject  objects is built from every project in  worktree.projects  , by inspecting the various  qiproject.xml  and looking for  <qibuild>  tags. Note that at this moment  build_project.build_depends  ,  build_project.run_depends  , and  build_project.test_depends  are sets of names because no dependency resolution has been done yet.

• A new CMakeBuildConfig object is initialized, using the  .qi/qibuild.xml  file to read the default config that should be used. If the user has an incorrect default config specified in the  .qi/qibuild.xml  file, an error is raised immediately.

• Then, the  build_config  object is configured using the  args  object and the  qibuild.xml  configuration files.

First, the  -c  argument is checked to see if it matches a known toolchain. If not, an error is raised.

Then, the configuration specific settings and the default settings in  ~/.config/qi/qibuild.xml  are read.

For instance, if the user specified  -c foo-sdk  on the command line there is a  <cmake gererator="Ninja">  tag in the  <config name="foo-sdk">  section of  ~/.config/qi/qibuild.xml  ,  build_config.cmake_generator  is set to  Ninja  and  build_config.toolchain_name  to  foo-sdk 

Lastly, the options coming from the command line are applied to the  build_config  object.

This is done after reading the config files, so that settings can be overwritten. Thus the user can for instance specify  --cmake-generator="Unix Makefiles"  to overwrite the default CMake generator configured in  ~/.config/qi/qibuild.xml 

• Lastly, the  build_config  is applied to the  BuildWorkTree  :  worktree.build_config = build_config  .

Note: the code later looks like:



# in BuildProject

def configure(self, **kwargs)
cmake_args = self.cmake_args
build_directory = self.build_directory



But actually,  cmake_args  and  build_directory  are both properties.

This means that the build dir will always match the latest build settings, and that the list of CMake args in the BuildProject will always be up to date.



# in CMakeBuildConfig

@property
def cmake_args(self):
# Transform all the "high level" settings into a list of
# CMake arguments

>>> build_config.cmake_generator == "Ninja"
>>> build_config.cmake_args
["-G", "Ninja"]
>>> build_config.toolchain_name = "foo-sdk"
>>> build_config.cmake_args
["-DCMAKE_TOOLCHAIN_FILE=/path/to/foo-sdk/toolchain.cmake"]



The build config also manages the environment variables, so that you can for instance set a suitable  PATH  when using mingw on windows without to mess with the registry base.



# in BuildProject
@property
def build_directory(self):
#  Create a sensible build dir, using
# self.build_worktree.build_config

>>> build_config.build_type = "Release"
>>> hello_project.build_directory = "/path/to/hello/build-release"
>>> build_config.profiles = ["my-profile"]
>>> hello_project.build_directory = "/path/to/hello/build-my-profile"



### get_build_projects ¶

The goal here is to get a list of  BuildProject  objects to build.

• If no build project named is specified, the parent directories are explored until a  qiproject.xml  containing a  <qibuild>  tag is found.

If no such project is registered in the  BuildWorkTree  yet, it will be automatically added to the worktree cache.

• If the user specified some projects in the command line, a matching  build_project  is searched in the  build_worktree  for every project name specified on the command line. If no build project is found, an error is raised.

Note that at this point, no dependency solving has been done yet. Meaning that the  projects  list only contains the  hello project 

### get_dep_types ¶

Here,  get_dep_types  is used to converting the  --runtime  ,  --build-deps-only  ,  --single  arguments into a list of build types:

• default:  ["build", "runtime"] 
•  --runtime  :  ["build", "runtime"] 
•  -s, --single  :  [] 
•  --build-deps-only  :  ["build"] 

Finally,  CMakeBuilder.dep_types  is set In our examples, no argument was specified at all, so the build and the runtime dependencies are going to be used.

## Configuring the project and its dependencies ¶

Here’s what the code looks like:



# in qibuild.cmake_builder

class CMakeBuilder:
def __init__(self, build_worktree, projects):
self.build_worktree = build_worktree
self.projects = projects
self.deps_solver = BuildDepsSolver(self)

def configure(self, **kwargs):
self.bootstrap_projects()
projects = self.deps_solver.get_dep_projects(self.projects, self.dep_types)
for project in projects:
project.configure(**kwargs)



Note that the  CMakeBuilder  contains a  BuildDepsSolver  to delegates all the dependencies solving.

For instance, configuring  hello  , by default should call  configure()  on the  world  project, unless  -s  was specified.

Also, since  hello  has a runtime dependency on the  bar  package,  qibuild install --runtime hello /tmp/hl  should install both  hello  and  bar  to  /tmp/hl 

Also note that  CMakeBuilder  delegates the actual call to  cmake  to the build project itself

### Generating the dependencies.cmake ¶

For the  CMake  call to work, a  dependencies.cmake  must be written in the build directory

This is done by  cmake_builder.bootstrap_projects 

Here it is important that the  dependencies.cmake  always contains the list of every build dependencies, even if  -s  is used.

### Calling CMake ¶

Here  deps_solver  uses  self.dep_types  , so that when  qibuild configure -s hello  , is used,  world.configure()  is not called.

### Installing ¶

When installing a project, the  deps_solver  is again used to get a list of packages to install.

Then either:
• the whole contents of the packages are installed (the “-config.cmake” files, the headers, the static and shared libraries, etc.)
• if  solving_type  was set to  runtime  , only the runtime parts of the packages (shared libraries) will be installed.

## Building projects outside a qiBuild action ¶

This could be part of a continuous integration script, for instance:



worktree = qisys.worktree.WorkTree(worktree_root)
build_worktree = BuildWorkTree(worktree)
build_config = build_worktree.build_config



Note

Here the build_config has already been initialized from the various config files, and default values, but you can still use:



build_config.set_active_config("mytoolchain")
build_config.build_type = "Release"




project = build_worktree.get_build_project(name)

cmake_builder = CMakeBuilder(build_worktree, [projet])

# Configure and build the build and runtime deps of the
# project:
cmake_builder.configure()
cmake_builder.build()



If you then need to install the runtime parts only (to make a redistributable package for instance)



cmake_builder.dep_types = ["runtime"]
cmake_builder.install(destdir="package")