CMake Macros, Linking, and Other Notes

Previously I described using CMake for multi-platform builds; developing this kind of support makes CMake scripting more complex. To help manage this we can use macros and other techniques, described below.

Macros

You can write macros in CMake, store them in a directory with other .cmake macro files and access them like this:

# Specify directory to find macro files
set(CMAKE_MODULE_PATH ...)

# Include macro file, .cmake extension not required
include(...)

Create macros like so, save the code in a file with .cmake extension:

macro(name PARAM1 ...)
 
  # Parameters can be accessed by name
  message("${PARAM1}")
 
  # Or find them in ${ARGV}, which is read-only.
  # To access and modify them, we first have to make a copy,
  # then we can operate on the copy like any other list.
  set(PARAMS ${ARGV})
 
  # For example, pop the first value off.
  list(REMOVE_AT PARAMS 0)
 
  # Then you can add any other commands
  ...
 
endmacro(name)

Variable String Matching

The following block is executed if x86 appears anywhere in the value stored in $ENV{MACHINE}:

if( $ENV{MACHINE} MATCHES "x86" )
 
endif()

File Operations

To create directories:

file(MAKE_DIRECTORY name)

Copy directory structure, using a custom target:

add_custom_target(... ALL
  COMMAND $(CMAKE_COMMAND) -E copy_directory source destination
)

Where ... is a target name, so you can set dependencies (other targets that your copy target depends on will need to be built first), source and destination are the source and destination directories for the copy operation. Using cmake to perform the copy so that you won't have to deal with different copy commands under different operating systems.

Linking

To link libraries A, B, and C (for example, libA.so, libB.so, and libC.so on Linux) to your build_target:

add_executable(build_target ...)
target_link_libraries(build_target A B C)

CMake will take care of proper switches required to link. Library names can be build targets from previous add_library() entries also.

Imported Static Library

From time to time you need to statically link a library or archive that is built by someone else. You can rename it so that it may get picked up as part of library search. Alternatively, you can define it as a library:

add_library(... STATIC IMPORTED)
set_target_properties(... PROPERTIES
  IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/file.a
)

Where ... is the target name that you can use to link against, and ${CMAKE_CURRENT_SOURCE_DIR}/file.a is path to the archive (in above example it lives within the source tree).

Output Target Properties

Define library version and output directory:

add_library(... SHARED ${SOURCES})
set_target_properties(... PROPERTIES
  LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY}
  VERSION ${VERSION_STRING}
  SOVERSION ${VERSION_MAJOR_NUMBER}
)

Define executable output directory and specific link options:

add_executable(... ${SOURCES})
set_target_properties(... PROPERTIES
  RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY}
  LINK_FLAGS ${LINK_OPTIONS}
)

Temporary Objects

You can group several source files together as a single unit and reference them later. This is useful if you want to apply different set of compile flags to just a subset of your source tree.

add_library(build_target OBJECT ...)

Once this is done, you can reference build_target just like another source with $<TARGET_OBJECTS: ...>:

set(SOURCES
  A.cpp
  B.cpp
  $<TARGET_OBJECTS:build_target>
)
add_executable(build_output ${SOURCES})