Shtille's blog A development blog

CMake quick guide

Quick overview

CMake commands are not case sensitive, so you may use any version you like the most. Translating commands to another line doesn’t require additional characters like ‘' in Makefile. Commentaries are made with ‘#’ starting symbol (like in Makefile).

Variables

Variables are being set with following command:

set(value 10)

To clear the value one should use:

unset(value)

To access variable use its name in parentheses with dollar prefix:

${value}

Variables only visible on the scope they’re being set and below. The variable with the same name on any scope below will hide upper variable (like on any programmic language, C/C++/JavaScript as example). If you need to make variable be visible in the upper scope use:

set(value 10 PARENT_SCOPE)

Lists

Lists are made with space divided values:

set(my_sources first.cpp second.cpp third.cpp)

To append value to list there are two options:

list(APPEND my_sources fourth.cpp)

or

set(my_sources ${my_sources} fourth.cpp)

Prebuilt variables

List of useful prebuilt variables:

  • CMAKE_CURRENT_SOURCE_DIR
  • CMAKE_CURRENT_BINARY_DIR

Setting C++ standard version

set(CMAKE_CXX_STANDARD 11)

Project structure

You need to create CMakeLists.txt file in each directory you have target and intermediate files for easy build, including root file. Subdirectories can be included via command add_subdirectory.

So the root file should have following look:

cmake_minimum_required(VERSION 3.19)

project(my_project)

add_subdirectory(my_static_library_dir)
add_subdirectory(my_shared_library_dir)
add_subdirectory(my_executable_dir)
add_subdirectory(thirdparty_dir)

Subdirectories can have many layers:

project(thirdparty)

add_subdirectory(zlib)
add_subdirectory(jpeg)
add_subdirectory(png)

Types of targets

  • Executable
  • Static library
  • Shared library
  • Object library
  • Custom target

Executable

project(my_project)
...
add_executable(${PROJECT_NAME} ${source_files})

Static library

project(my_lib)
...
add_library(${PROJECT_NAME} STATIC ${source_files})

Shared library

Shared library requires to link dependencies.

project(my_lib)
...
add_library(${PROJECT_NAME} SHARED ${source_files})
target_link_libraries(${PROJECT_NAME} PUBLIC ${libraries})

Object library

Object library is used only to compile object files and not generate static/dynamic libraries output.

project(my_lib)
...
add_library(${PROJECT_NAME} OBJECT ${source_files})

Custom target

Example of usage of custom target:

set(OPENGL_OLD_SHADERS
	${CMAKE_CURRENT_SOURCE_DIR}/AppUtils/RenderingContexts/OpenGL2/OpenGLContextShader_fsh.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/AppUtils/RenderingContexts/OpenGL2/OpenGLContextShader_vsh.cpp
)

if (WIN32)
	message(FATAL_ERROR "Provide target for building shaders for Windows")
else()
	add_custom_command(
		OUTPUT ${OPENGL_OLD_SHADERS}
		COMMAND ${CMAKE_COMMAND} -E env ${EML_ROOT}/build/buildShaders.sh ${EML_ROOT}/AppUtils/RenderingContexts/OpenGL2/ ${EML_ROOT}/AppUtils/RenderingContexts/OpenGL2/ OpenGLContextShader.fsh
		COMMAND ${CMAKE_COMMAND} -E env ${EML_ROOT}/build/buildShaders.sh ${EML_ROOT}/AppUtils/RenderingContexts/OpenGL2/ ${EML_ROOT}/AppUtils/RenderingContexts/OpenGL2/ OpenGLContextShader.vsh
		DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/AppUtils/RenderingContexts/OpenGL2/OpenGLContextShader.fsh
		        ${CMAKE_CURRENT_SOURCE_DIR}/AppUtils/RenderingContexts/OpenGL2/OpenGLContextShader.vsh
		COMMENT "Building shaders"
		VERBATIM
	)
endif()

add_custom_target(build_shaders DEPENDS ${OPENGL_OLD_SHADERS})

Linking libraries

So as you’ve seen before to link libraries simply use:

project(my_lib)
...
target_link_libraries(${PROJECT_NAME} PUBLIC ${libraries})

after target (executable, library) was specified. In new versions of CMake you may not set specific order of libraries to correct the linkage, also CMake automatically collects all the required libraries between subprojects. The more information about link function read at its manual.

project(my_lib)
...
target_link_libraries(${PROJECT_NAME} PUBLIC "-framework Cocoa")

Include directories

Similar way handled include directories:

project(my_lib)
...
target_include_directories(${PROJECT_NAME} PRIVATE ${directories})

Read the manual for more information.

Compile definitions

project(my_lib)
...
target_compile_definitions(${PROJECT_NAME} PRIVATE ${defines})

Read the manual for more information.

Extended programming

Message logging

Use message command to log information:

message(STATUS "My defines: " ${defines})

Read the manual for more information.

If case

Useful when choose the platform target:

if (WIN32)
	message(STATUS "WIN32")
elseif(ANDROID)
	message(STATUS "ANDROID")
else()
	message(STATUS "OTHER")
endif()

For cycle

Following code parses directories for .c and .cpp files and fills MODULE_SOURCES variable:

foreach(DIR ${MODULE_DIRS})
  file(GLOB DIR_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${DIR}/*.c ${CMAKE_CURRENT_SOURCE_DIR}/${DIR}/*.cpp)
  set(MODULE_SOURCES ${MODULE_SOURCES} ${DIR_SOURCE})
endforeach(DIR)

Building the code

cd <project directory>
cmake -S . -B build
cmake --build build

To specify the build configuration use:

cmake --build build --config Release

Read the manual for more information about configurations.

Cleaning up

cmake --build build --target clean

Installing

Use following command in your CMakeLists.txt file:

install(TARGETS AtmosphericScattering Shadows
        CONFIGURATIONS Release
        RUNTIME DESTINATION ${BINARY_PATH})

To install call following cmake command:

cmake --install build --config Release