Skip to content
Stefan Buschmann edited this page Mar 10, 2017 · 2 revisions

CMake Project Setup Guide

This guide describes how to setup cmake-init based projects, and cmake-based projects in general. It is aimed at larger projects with many sub-projects which depend on each other, and shows a way to configure such projects so that all dependencies are met at build-time as well as run-time, without the need to install projects into the local system or copy files around. As an example, the CG Internals computer graphics middleware is used, but the guide can also be used for any cmake-based projects.

Prerequisites

  • C++ compiler
  • CMake

1. Project Checkout

First, you need to checkout the projects you want to build. As an example for a large group of interdependent projects, we use the CG Internals middleware stack.

Checkout projects and dependencies

  • Choose a directory for your projects, e.g., /projects, or C:/projects.

  • Checkout git repositories there, e.g.:

/projects/cpplocate
/projects/cppassist
/projects/cppexpose
/projects/glbinding
/projects/globjects
/projects/gloperate
  • Download 3rd-party dependencies, e.g.:
/projects/glm
/projects/glfw
/projects/assimp
  • Build 3rd-party dependencies using their provided build system, e.g. ./configure or cmake. Copy the resulting libraries into subdirectory lib, as most of the find scripts assume libraries to be located there.

2. Project Setup

To configure the project, cmake needs to be called. This can be accomplished in three ways:

a) Use configure script (recommended)

All cmake-init-based projects include a shell script named configure. This script is nothing more than a wrapper around cmake, and it can be used for any cmake-based project. Its purpose is to persist all cmake options needed to successfully configure the project, so that nothing needs to be passed to cmake on the commandline or via the cmake-gui any more. This makes it possible to reconfigure the project at any time without worrying about losing important configuration values.

The script needs a UNIX shell, such as bash, to be executed. On Windows, we recommend to use git-bash, which is bundled with the installation of git.

Run the script by changing into the directory of the project you want to build, and enter ./configure on the terminal. On the first run, the configure script creates a hidden subdirectory named .localconfig, containing three files: default, debug, and pack. These files contain the configuration parameters needed to configure the project, i.e., cmake options or environment variables.

When running ./configure, the script reads the files in .localconfig, applies the options specified in the file, and then calls cmake. The file default is always processed, the other files are only processed if specified on the command line. For example, running ./configure debug will first apply default, then debug. This way, you can define several configurations and even mix configurations, e.g., have one for each compiler, one for debug instead of release, one for cross compilation, and mix them like this: ./configure gcc48 debug arm.

Inside the configuration files, you can define all options for cmake. The cmake generator, build type (release, debug, ...) and build directory can be specified via CMAKE_GENERATOR, BUILD_TYPE, and BUILD_DIR, respectively. Search paths for dependencies should be exported to the environment variable CMAKE_PREFIX_PATH. General cmake options can be specified in the variable CMAKE_OPTIONS.

b) Use cmake-gui

You can also configure the project via the cmake-gui, entering all necessary options, like search paths of dependend projects, into the GUI. This is not recommended, because it needs to be repeated every time you recreate the build directory, which makes it hard to reconfigure projects. Please consider using option a), you can then still use the cmake-gui to explore and set the available project options.

  • Start cmake-gui
  • Choose compiler and generator, e.g., "Unix Makefiles", or "Microsoft Visual Studio ..."
  • Choose source directory: /projects/cpplocate
  • Choose build directory: /projects/cpplocate/build
  • Enter all necessary build options and paths to dependend libraries
  • Configure
  • Generate

c) Execute cmake manually

You can also configure the project by running cmake manually. In that case, you have to pass all project options and search paths via the command line and environment build, and enter CMAKE_PREFIX_PATH. This is not recommend because the options are not persisted when recreating the project. Please consider using method a).

  • Open a terminal
  • Change into the project directory: cd /projects/cpplocate
  • Create build directory: mkdir build
  • Specify all necessary build options and paths to dependend libraries via -D arguments
  • Configure project: cd build
  • Configure project: cmake ..

3. Compile-Time Dependencies

Most importantly, cmake needs to find the dependencies of your project. For this to work, the project has either to be installed in a global system path, or the path to the project must be made available to cmake by adding it to the CMAKE_PREFIX_PATH. All cmake-init-based projects supply a cmake file <project>-config.cmake in their root directory, which enables cmake to use that project from its development directory, so it is not necessary to install projects into the system, only the project directory needs to be added to CMAKE_PREFIX_PATH.

a) Use configure script (recommended)

To find the project dependencies, add the following lines to .localconfig/default:

# cpplocate
export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/projects/cginternals/cpplocate"

# cppassist
export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/projects/cginternals/cppassist"

# cppexpose
export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/projects/cginternals/cppexpose"

# glm
export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/projects/glm"

# glfw
export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}:/projects/glfw"

In the case of the CG Internals middleware, lot of projects depend on one another, so the paths for the dependencies are similar for all projects. It that case, it can me more convenient to write the above lines into a single file, e.g., /projects/dependencies, and include that from ./localconfig/default:

# Import dependencies
source /projects/dependencies

b) Use environment variables

You can also persist the search paths by storing them in an environment variable. This has the disadvantage that environment variables are global to the system, so you cannot for example use different version of the same library for different projects.

Windows:

  • Open an environment variable editor
  • Add project directories to CMAKE_PREFIX_PATH variable
  • CMAKE_PREFIX_PATH="C:/projects/cpplocate;C:/projects/cppassist;..."
  • Save
  • Restart all programs and explorer windows, otherwise the environment variables are not reloaded. If nothing else helps, restart Windows.

Linux:

  • Set CMAKE_PREFIX_PATH to include all paths and persist it in the system. This can be done in several ways depending on your distribution, e.g., /etc/environment, /etc/profile, .profile, .bashrc.
  • export CMAKE_PREFIX_PATH="/projects/cpplocate:/projects/cppassist:..."

See StackExchange for details.

c) Use your own scripts

You can of course use your own scripts, or batch files, to set all necessary cmake options and environment variables before calling cmake. This option is in general equal to a), but can be preferable if you want to optimize the setup for your specific system.

4. Runtime Dependencies

In order to run the compiled applications, the system must be able to find the dynamic libraries the application depends on. For example, gloperate depends on globjects and glbinding, so the respective dynamic libraries (.so or .dll) must be available to run gloperate-viewer.

Linux, macOS: Use RPATH

The compiled binaries already contain the paths of the dependend libraries in their RPATH. Therefore, they can be executed in place and all libraries will be found automatically. Note however that the binaries are only setup to work in the build environment, they cannot be copied around and are not intended to be used directly for packaging or deployment. If you want to install a project into the local system or create an installation package, use the make pack or make install directives.

Windows: Add build directory to PATH

The DLLs of the dependend projects need to be found, therefore it is necessary to add their directories to the environment variable PATH.

  • Open an environment variable editor
  • Add project directories to PATH variable
  • PATH="C:/projects/cpplocate/build/Debug;C:/projects/cpplocate/build/Release;..."
  • Save
  • Restart all programs and explorer windows, otherwise the environment variables are not reloaded. If nothing else helps, restart Windows.

5. Runtime Data

You should now be able to build and run projects from their development location. However, the programs and libraries must also be able to find their data files at runtime. Since the projects are distributed over many directories, this is not a trivial task. Therefore, we use cpplocate to help us locate data files at runtime.

This is accomplished by providing .modinfo files for each program or library, which are searched for at runtime. The module files for the currently executed program will be found relative to the program executable. However, to find data files for dependent libraries, cpplocate must be provided with search paths. This can be accomplished by adding the build directories of those libraries to the environment variable CPPLOCATE_PATH.

Windows:

  • Open an environment variable editor
  • Add project build directories to CPPLOCATE_PATH variable
  • CPPLOCATE_PATH="C:/projects/gloperate/build/Debug;C:/projects/gloperate/build/Release;..."
  • Save
  • Restart all programs and explorer windows, otherwise the environment variables are not reloaded. If nothing else helps, restart Windows.

Linux:

  • Set CPPLOCATE_PATH to include all paths and persist it in the system. This can be done in several ways depending on your distribution, e.g., /etc/environment, /etc/profile, .profile, .bashrc.
  • export CPPLOCATE_PATH="/projects/gloperate/build:/projects/gloperate/build-debug:..."

Known bugs and limitations

macOS: Module file cannot be found when application is built as an app bundle

TODO ...

Windows: When using modinfo files to define plugin paths, debug and release configuration collide

TODO ...

Module files of dependent projects could be copied into the using project automatically, so CPPLOCATE_PATH would not be needed any more.

TODO ...