Overview

This document describes how you can build libcinder and your application using cmake. This is Cinder's officially supported build system on most POSIX platforms, though in general you can use cinder's cmake system on any platform. See the section on platform-specific notes for details.

Table of Contents

Building libcinder with CMake

To build libcinder from the command line, first make sure you have CMake version 3.0 or later installed. Then the process is similar to most other cmake projects you may have used, for example you can do the following from within the main cinder repo path:

mkdir build
cd build
cmake ..
make -j4

Upon completion, this will generate a static libcinder binary within the $(CINDER_PATH)/lib folder for your application to link against. By default you'll be building for Debug configuration, to build release you change the above cmake command to:

cmake -DCMAKE_BUILD_TYPE=Release ..
make -j4

The runtime output directory for different configurations will automatically end up within different folders (e.g. lib/macosx/Debug/libcinder.a and lib/macosx/Release/libcinder.a), so multiple configurations can live side by side.

Command-line Options

There are a few project configuration settings you can change by either editing the CMakeCache.txt file (or using some GUI to do this), or from the command line with the -D option. All settings related to cinder are prefixed with CINDER_. Some of the more common settings are listed below, take a look in the CMakeCache.txt file for a full and updated list.

CINDER_VERBOSE: prints out verbose information from within Cinder's CMake scripts during configuration.

CINDER_TARGET: Sets the target to compile to. This defaults to one appropriate for the current operating system, but in some cases, for example Android, you must set it manually (i.e. cmake -DCINDER_TARGET=android ..).

CINDER_TARGET_GL: Sets the target OpenGL version. Usually defaults to ogl (desktop modern OpenGL), but other valid options are es2, es3, and es31. These are useful when you are building for something like the Raspberry Pi.

CINDER_BUILD_TESTS: Builds the unit tests (You can then run the tests from the command line with make test / make check).

CINDER_BUILD_SAMPLE: Specify the name of a single sample to build, after libcinder successfully builds. It will end up in the current build folder. For example, adding the option -DCINDER_BUILD_SAMPLE=BasicApp will also build the BasicApp sample and place it at build/Debug/BasicApp/BasicApp.app.

CINDER_BUILD_ALL_SAMPLES: Specifying True here will tell CMake to try to build all of the samples within cinder's samples directory. Be prepared to wait a while.

Building your application with CMake

Often the best thing to do when adding CMake support to your own project is to look at one of the samples as a starting point. You'll find the CMakeLists.txt file will be at proj/cmake/CMakeListst.txt. You can still build it from the base directory of your project if you choose, with something like:

cd my_project
mkdir build
cd build
cmake ../proj/cmake
make -j4

To simplify configuring your project and the many platform-specific particulars, we use a utility function called ci_make_app(), which takes a number of arguments to build your application. For example, here is the CMakeLists.txt file used for building the _opengl/ObjLoader sample:

cmake_minimum_required( VERSION 3.0 FATAL_ERROR )
set( CMAKE_VERBOSE_MAKEFILE ON )

project( opengl-ObjLoader )

get_filename_component( CINDER_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../../.." ABSOLUTE )
get_filename_component( APP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../" ABSOLUTE )

include( "${CINDER_PATH}/proj/cmake/modules/cinderMakeApp.cmake" )

ci_make_app(
    APP_NAME    "ObjLoader"
    CINDER_PATH ${CINDER_PATH}
    SOURCES     ${APP_PATH}/src/ObjLoaderApp.cpp
    INCLUDES    ${APP_PATH}/include
    RESOURCES   ${APP_PATH}/../../data/models/8lbs.obj
)

It is quite brief, but you can see that we've specified a number of common things necessary when building a custom application, such as source files and include paths.

Arguments to ci_make_app() include:

APP_NAME: Defines the name of your application. Defaults to the current ${PROJECT_NAME}.

CINDER_PATH: Relative path to cinder's root directory, used to find the cinder module via CMake's find_package() system. Required.

SOURCES: list of source files. Required.

INCLUDES: list of include directories

ASSETS_PATH: path to your assets folder (see explanation of assets here. This will automatically be found if your project folder structure follows the standard pattern. Note that by default, this gets symlinked next to your built application, however specifying -DCINDER_COPY_ASSETS=On at the command line will cause the assets to be copied over instead.

RESOURCES: list of resources, which are handled in a platform specific manner (see explanation of resources here.

BLOCKS: list of CinderBlocks that your application depends on. These can be specified by either a relative or absolute path, or by providing the name of one of the blocks that ships with cinder (e.g. lives in the $(CINDER_PATH)/blocks folder).

LIBRARIES: list of lib files to be linked (via target_link_libraries()).

Printing verbose information when building your application

ci_make_app() also respects the CINDER_VERBOSE variable, so if that is on then you will see much more configuration information.

CinderBlocks and CMake

Using CinderBlocks

As mentioned above, you can provide a list of CinderBlocks to ci_make_app() BLOCKS argument, which will then attempt to first find the block by either relative or absolute path, and then within Cinder's blocks folder (by its name alone).

Writing CMake support for a CinderBlock

The basic pattern we use for CinderBlock support is CMake's packages system, specific the Package Configuration File variant. For a concrete example, take a look at the OSC block's OSCConfig.cmake file.

First and foremost, ci_make_app() finds a file by looking for it at a specific location, within the block's proj/cmake/ folder, and you must use the block's name as the prefix to *Config.cmake. Then, we define that it is a library with the add_library( OSC ${source_files} ) command, along with any other dependencies on the block.

In the case of a header-only CinderBlock, there won't be a target, but you can still specify the include and library dependencies with BLOCK_NAME_INCLUDE_DIRS and BLOCK_NAME_LIBRARIES. Also note that, as in the case of the TUIO block, a block configuration can specify that it depends on another block with CMake's add_dependencies().

Using CLion to build libcinder and your application

Aside from the command line, you can also use JetBrains CLion to build and work on both libcinder and your application. CLion is a sophisticated C++ IDE with many nice syntax features around context-aware searching and refactoring, so it makes for a nice environment to code on your Cinder tasks.

To build libcinder with CLion, open the main Cinder path within the IDE, wait for the initial symbol indexing to finish, and select Run -> Build. This will by default build the Debug configuration; to change that to Release, you can modify the CMakeCache.txt file (along with any other Cinder cache options) by navigating to the CMake panel at the bottom and finding the button on the left that says 'Open CMakeCache File', and changing the CMAKE_BUILD_TYPE setting to Release there.

Note that by default, CLion will output build files into a folder called cmake-build-debug. If you want to change this to the usual build, which will automatically get ignored by Cinder's .gitignore file, then navigate to Preferences -> Build, Execution, Deployment, and change the field called 'Generation path:' to 'build'.

Running a Cinder sample directly from Cinder's main project.

There are a couple CMakeCache.txt options that will allow you to conveniently build and run a sample that ships with Cinder alongside building the main library. First, you can set the option CINDER_BUILD_SAMPLE to any of the sample names (ex. CINDER_BUILD_SAMPLE=_opengl/Cube), and then you will be able to run that sample by selecting it in the the Select/Run Configuration drop-down in the top right. Alternatively, you could set CINDER_BUILD_ALL_SAMPLES=On, which will load the configurations for all Cinder samples, and you can then run any one of them from within the IDE.

Structure of Cinder's CMake Files

This section provides an overview of how CMake build support is structured within the Cinder repo.

Cinder's CMake scripts live in the common folder proj/cmake. The root CMakeLists.txt file is largely there so that it is easy to find, and so that our build configuration plays nicely with CMake-based IDEs like CLion.

configure.cmake

One of the first things that we do is to include() the common configure.cmake file, which is shared by Cinder, libraries that depend on Cinder, and applications. It defines some global variables such as:

  • CINDER_TARGET, if it hasn't been defined by the user. See above section on Command-line Options for details.
  • A platform-specific variable: one of CINDER_LINUX, CINDER_MAC, CINDER_MSW, CINDER_COCOA_TOUCH or CINDER_ANDROID.
  • CINDER_TARGET_GL, if it hasn't been defined by the user. See above section on Command-line Options for details.
  • Defines some useful OpenGL variables such as CINDER_GL_CORE, CINDER_GL_ES, etc. (see configure.cmake for the full list).
  • Sets the CMAKE_BUILD_TYPE to Debug if the user hasn't specified one.
  • Provides some shared cache options like CINDER_VERBOSE.
  • Sets a couple variables, CINDER_TARGET_SUBFOLDER and CINDER_LIB_DIRECTORY, which define where the output binaries will be generated for each target.

In short, configure.cmake is meant to provide a a place where common variables can be declared based on the same commands for any cinder-based project, including cinder itself.

Configuring libcinder

The next thing we do is configure the platform independent aspects of the cinder target. These steps live in libcinder_configure.cmake and libcinder_source_files.cmake. The platform-specific configuration is broken out into separate sources (ex. platform_linux.cmake to ease extending the build system to new platforms. This way you can see source files that only pertain to a specific platform by viewing its specific platform_*.cmake file.

Finally, we create the actual cinder target within libcinder_target.cmake, which among other things contains the commands to add_libary( cinder ... ). Also importantly, this file is responsible for exporting dependency information and creating a cinderConfig.cmake file that will later be used with find_package() by libraries and applications that depend on Cinder.

CMake Best Practices

Below are some guidelines for how we've been working on the CMake build setup within Cinder, which are useful to read over if you plan on making contributions and want to streamline your pull requests.

Assign compiler configurations to targets

We try to 'leak' as little compiler configuration as possible. This means using things methods like, for example, target_include_directories( target ... ) instead of the older include_directories( ... ) and then relying on CMake's package system to apply the settings based on dependencies. This pertains to both developing on CMake for libcinder and CinderBlocks.

Keep the code style consistent

As with all community projects, it makes things easier to keep a consistent style. CMake might be a funky language, but we've more or less developed our own readable way to write in it, which is based on the spacing described in Cinder's styleguide as much as possible.

Platform Specific Notes

Microsoft Windows

As of this writing, we do not support building from CMake-generated Makefiles on Windows, simply because the GCC toolchain is too difficult to set up there with a compiler that supports the features we need (C++11). The best approach there is to use the provided Visual Studio project files, as explained here. You can however generate new Visual Studio project files from CMake, if that suits your needs.