Techminded

Modular C++ projects with CMake

Introduction

Cmake if fantastic make tool for C++. It allows you to generate makefiles for every platform you need, itsteand of writing them manually. As and addition it alllows you to generate project files from variety of IDE's while the most of C centric alredy have Cmake support.

Project Structure

We will organize project tree as set of subprojects. Each of subproject will have own catalog with own CMake configuration (CMakeLists.txt) in it. Project set folder will have own global CMake configuration that will allow us to build all projects into one set of binaries and libraries.

project1
    include
    src
    CmakeLists.txt
proejct2
    include
    src
    CmakeLists.txt
....
include
CMakeLists.txt

Subprojects in example

Each of project will also have own headers and sources catalogs as far as common for all subprojects headers will be located in headers root. So the main CMake config will look as follows:

cmake_minimum_required(VERSION 2.6)
project (rootproject)

set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build)

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR})

set(PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)

include_directories("${PROJECT_INCLUDE_DIR}")
include_directories("${PROJECT_SOURCE_DIR}")

add_subdirectory(project1)
add_subdirectory(project2)

While each of project will have own configurations like the following:

cmake_minimum_required(VERSION 2.6)
project (project)

set (PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)

set(PROJECT1_SRCS 
${PROJECT_SOURCE_DIR}/some.cpp
${PROJECT_SOURCE_DIR}/someother.cpp
)

include_directories("${PROJECT_BINARY_DIR}")
include_directories("${PROJECT_INCLUDE_DIR}")

add_library(${PROJECT_NAME} SHARED ${PROJECT1_SRCS})

In the example below we are building a shared library (.so or .dll or .dynlib). Subroject with binray executable will have very small changes:

cmake_minimum_required(VERSION 2.6)
project (project)

set (PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)

set(PROJECT2_SRCS
${PROJECT_SOURCE_DIR}/some.cpp 
${PROJECT_SOURCE_DIR}/someother.cpp 
)

include_directories("${PROJECT_BINARY_DIR}")
add_executable(execute ${PROJECT2_SRCS})
include_directories("${PROJECT_INCLUDE_DIR}")

Now we can build our projects. If you already have what to build ofcource:) CMake allows you to make out-of-source build in any folder and theoretically you can create build catalog elsewhere else and build sources to it.

$mkdir build
$cmake .
$make

Build subprojects separately

We also wanted to build subprojects seaprately. In this case only one subproject is builded at once time. For this case we will have to add options for CMakeLists.txt that will identify that build is separeted from other subprojects. Even if subproject is independent very othen usage of common headers is required to avoide code duplications. For this case wil will add root and parent relatively to subproject include catalog with common headers if option is set to "ON" and include and use any path in other cases to use headers from elsewhere.

option (SEPARATE_BUILD OFF)

if (SEPARATE_BUILD)
  if (SEPARATE_BUILD STREQUAL "ON")
  include_directories("${CMAKE_SOURCE_DIR}/../include")
  else() 
  include_directories("${SEPARATE_BUILD}")
  endif()
endif (SEPARATE_BUILD)

So the final configurations for project1 will look like:

cmake_minimum_required(VERSION 2.6)
project (project)

set (PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)

option (SEPARATE_BUILD OFF)

set(PROJECT2_SRCS
${PROJECT_SOURCE_DIR}/some.cpp 
${PROJECT_SOURCE_DIR}/someother.cpp 
)

if (SEPARATE_BUILD)
  if (SEPARATE_BUILD STREQUAL "ON")
  include_directories("${CMAKE_SOURCE_DIR}/../include")
  else() 
  include_directories("${SEPARATE_BUILD}")
  endif()
endif (SEPARATE_BUILD)

include_directories("${PROJECT_BINARY_DIR}")
add_executable(execute ${PROJECT2_SRCS})
include_directories("${PROJECT_INCLUDE_DIR}")

Now you can to to suboroject, create build catalog and build it separetely from other proejcts:

$cd project1
$mkdir build
$cd build
$cmake -DSEPARATE_BUILD=ON ../
$make

So if you still reading this go follow CMake tutorial and start using it. It can find libraries and dependencies and even generate code for you or build linux package in automated way. And all things are much much simplier then diving into writing of native makefiles or automake scripts

Links

http://www.cmake.org/cmake/help/cmake_tutorial.html

http://www.cmake.org/Wiki/CMake_FAQ#Isn.27t_the_.22Expression.22_in_the_.22ELSE_.28Expression.29.22_confusing.3F

 

Comments

29-08-2012 19:55: Andreas Mohr
Thank you for this nice demo! Some short comments: It has a somewhat weird feel to it to actively define a custom CMAKE_BINARY_DIR setting. Usually this is a pre-configured value resulting from executing CMake configure run within a build dir root, pointing at the source root: mkdir build cd build cmake -DCMAKE_BUILD_TYPE=... ../source_root Dito for PROJECT_SOURCE_DIR, this one is automatically provided after each new project() command. Also, it's quite a bit more effort to have manual relative references to source files when creating a target (lib/executable/...): it's probably a much better idea to have a new CMakeLists.txt within each sub dir, then have this dir included via add_subdirectory() lines in parent configs, and then simply do add_executable() directly within the source dir's own scope. Plus, always doing all actions within the very same directory that holds the source files means that all configure_file() etc. arguments always implicitly properly go from CMAKE_CURRENT_SOURCE_DIR to CMAKE_CURRENT_BINARY_DIR. There's no magic manual annoying directory cross referencing required, none, zilch, nada. Provided there is a new CMakeLists.txt per each sub directory. Which there probably should be ;) Doing it like that means that you gain *full* eternal relocatability of all CMake configs (simply move the entire sub directory away to an entirely differently named place - the *only* thing that then needs to be changed is the add_subdirectory() line thingy). That's the magic of having everything define their stuff within their own local scope... HTH!