CMake share library with multiple executables

My project contains several executables that share some common code. I would like to put the common code in a static library that the executables can link to. (The common code is pretty small and I prefer not to deal with shared libraries).

The source tree looks something like this:

  • project
    • CMakeLists.txt
    • common
      • CMakeLists.txt
      • src
      • include
    • app1
      • src
      • CMakeLists.txt
    • app2
      • src
      • CMakeLists.txt

app1 and app2 both depend on the code in common.

This common code is very application specific and will never need to be used by another project outside this directory tree. For that reason I would prefer not to install the library in any sort of global location.

The top-level CMakeLists.txt file just adds the subdirectories:

project(toplevel)
cmake_minimum_required(VERSION 3.1)

add_subdirectory(common)
add_subdirectory(app1)
add_subdirectory(app2)

The common library's CMakeLists.txt file creates the static library and sets include directories:

add_library(common STATIC common.cpp) 
target_include_directories(common PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")

And the file for the executables looks like this:

project(app1)
cmake_minimum_required(VERSION 3.1)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} common)

Now for my question. If I run CMake from the top level project directory, I can build app1 and app2 and they build successfully. However, if I want to build a single one of these projects (by running CMake from app1, for example) instead of building from the top level directory, I get an error because common/include is not added to the header search path.

I can see why this happens. There is nothing in the CMakeLists.txt file for app1 or app2 that "pulls in" common. This is only done at the top level.

Is there a way around this, or is this behavior generally considered acceptable? Is something about my setup sub-optimal? I'm just thinking it would be nice to be able to build the projects individually instead of from the top level in the event that we start to develop more and more executables that use this common library, but perhaps this is something I shouldn't be concerned about.


When you setup your build environment, you should put some thought into the following three topics (beside others, but for this discussion/answer I reduced it to the three I feel are relevant here):

  1. Dependecies / Coupling
  2. Deployment
  3. Teams

"Strong Coupling"

I came to think of the add_subdirectory() command as supporting "strong coupling" and your current setup implicitly supports:

  • A frequently changing common library
  • A single deployment (process and timing) for all your apps
  • A single team working on the complete source base
    • An IDE would show it all in one solution
    • You generate one build environment for everything

"Loose Coupling"

If you want a more "loose coupling" you could make use of external scripts in other languages or the use of CMake's ExternalProject_Add() macro. So if you setup the common library (maybe even including "binary delivery") and each app as a separate project you do support:

  • A less often changing common library
    • Probably with its own release cycles
  • An independent development/deployment cycle for each app
  • A team of different developers working on each app

A Mixture of Both

So as you can see there a lot of things to consider and CMake can give you support for all kind of approaches. Considering that your project might be in the early stages, you probably go by a mixed approach (not right away de-coupling the common library):

CMakeLists.txt

project(toplevel)
cmake_minimum_required(VERSION 3.1)

include(ExternalProject)

ExternalProject_Add(
    app1 
    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app1"
    PREFIX app1
    INSTALL_COMMAND ""
)
ExternalProject_Add(
    app2 
    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app2"
    PREFIX app2
    INSTALL_COMMAND ""
)

app1/CMakeLists.txt

project(app1)
cmake_minimum_required(VERSION 3.1)

add_subdirectory(../common common)

add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} common)

This will actually generate three build environments. One directly in your binary output directory and one each in app1 and app2 sub-directories.

And in such approaches you may want to think about common CMake toolchain files.

References

  • Use CMake-enabled libraries in your CMake project (II)
  • CMake: How to setup Source, Library and CMakeLists.txt dependencies?

You should use project() command in subdirectories only if this subproject is intended to be built both as standalone and as a part of toplevel project. This is the case for LLVM and Clang, for example: Clang can be compiled separately, but when LLVM build system detects Clang source, it includes its targets too.

In your case you don't need subprojects. To compile only app1 or app2 target issue make app1/make app2 in projects build dir.