# qiBuild documentation

# 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")

Changelog CMake index Python index Index