CMake for Multi-Platform Builds

I want to develop an engine that will run on Windows, OS X, and Linux. This means having a build system that will build binaries for all those platforms also.

Cross-platform development of this sort means dealing with platform-specific libraries and toolchain. It requires branching within build scripts to deal with things like linking libraries with different names and extensions, paths for toolchain, and different compiler options.

Variables

I mentioned briefly in the previous post how you can set variables using set:

set(NAME value)

But these variables have scopes. You cannot use them until after the first set command. Normally they also cannot be modified by CMakeLists.txt in a sub-directory. This is the first obstacle we must overcome in order to have newly loaded CMakeLists.txt change behavior of our build script. This default scoping behavior isn't obvious unless you are looking for it, but is easily accomplished:

set(NAME "old value")
add_subdirectory(A)
message("VALUE: ${NAME}")

in CMakeLists.txt that lives in A:

set(NAME "new value" PARENT_SCOPE)

Passing In Variable Values

To pass in values when you run cmake from command line:

cmake ../src/ -DMY_VARIABLE="Value to pass in"

This is useful for setting values for variables that controls branching within your build scripts.

Environment Variables

Access environment variables using $ENV. For example, the PATH variable that determines the search order for binaries to execute:

$ENV{PATH}

Built-In Variables

Often there are other built-in variables specific to your operating system and compiler, some of which are listed here. Examples include APPLE, UNIX, WIN32, and CMAKE_COMPILER_IS_GNUCXX.

The variable CMAKE_BUILD_TYPE deserves some note, it determines the type of build and compiler flags. For example, a release build with no debugging symbols:

set(CMAKE_BUILD_TYPE "Release")

With clang or gcc this often means -O3 optimization. To build binaries for use with gdb or other debugger:

set(CMAKE_BUILD_TYPE "Debug")

By setting the build type, it affects the variables that store the flags used for compiling and linking. For release builds, they are: CMAKE_C_FLAGS_RELEASE and CMAKE_CXX_FLAGS_RELEASE. For debug builds they are: CMAKE_C_FLAGS_DEBUG and CMAKE_CXX_FLAGS_DEBUG. It may be surprising to find out that the compiler flags you set have no effect, and this is where you should investigate.

Branching

All these variables allows you to evaluate conditions and branch:

if( ... AND ... )
 
elseif( ... OR ... )
 
elseif( EXISTS ... )
 
elseif( DEFINED ... )
 
else()
 
endif()

Often you will be comparing strings:

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

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

The ability to conditionally execute different parts of your build script means we can now use a single set of CMakeLists.txt, maybe in different sub-directories, to build and package binaries for different platforms.

Next up, let's talk about CMake support for macros, linking, and other useful techniques.