CMake for Building and Linking

CMake is a powerful cross-platform build system that simplifies building C/C++ projects, especially when it comes to managing libraries and linking.

Here's a comprehensive look at using CMake for building and linking, complete with examples:

Project Setup

  • Create a directory for your project.

  • Inside the project directory, create a file named CMakeLists.txt. This is the heart of your CMake configuration.

  • Create a build directory

  • Write your CMakeLists.txt

  • Move to build directory

  • Run cmake ..

  • Run make

Building a Simple Static Library

# Minimum required CMake version
cmake_minimum_required(VERSION 3.0)

# Project name
project(MyStaticLib)

# Create a static library
add_library(MyStaticLib STATIC source1.cpp source2.cpp)

Explanation:

  • cmake_minimum_required(VERSION 3.0): This line specifies the minimum required version of CMake for this project.

  • project(MyStaticLib): This line defines the project name, which is used for generating build files.

  • add_library(MyStaticLib STATIC source1.cpp source2.cpp): This line creates a static library named MyStaticLib from the source files source1.cpp and source2.cpp.

Building an Executable and Linking with the Static Library

# Include the library directory
target_include_directories(MyProgram PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")

# Create an executable
add_executable(MyProgram main.cpp)

# Link the executable with the static library
target_link_libraries(MyProgram PRIVATE MyStaticLib)

Explanation:

  • target_include_directories(MyProgram PRIVATE "${CMAKE_CURRENT_BINARY_DIR}"): This line tells the compiler where to find header files. Since the static library might have headers, we include the current binary directory (where the library is built) in the search path.

  • add_executable(MyProgram main.cpp): This line creates an executable named MyProgram from the source file main.cpp.

  • target_link_libraries(MyProgram PRIVATE MyStaticLib): This line links the executable MyProgram with the previously created static library MyStaticLib. The PRIVATE keyword indicates that the library is private to this project and shouldn't be exposed to other projects linking against this executable.

Building a Shared Library

# Create a shared library
add_library(MySharedLib SHARED source1.cpp source2.cpp)

Explanation:

This line is similar to creating a static library, but instead of STATIC, we use SHARED to create a shared library named MySharedLib.

Linking with External Libraries using find_package

# Find an external library (replace with the actual library)
find_package(Threads REQUIRED)

# Include the library directory
target_include_directories(MyProgram PRIVATE "${CMAKE_THREAD_INCLUDE_DIRS}")

# Link the executable with the external library
target_link_libraries(MyProgram PRIVATE Threads::Threads)

Explanation:

  • find_package(Threads REQUIRED): This line attempts to find a package named Threads (replace with the actual library name, like OpenSSL or SDL2). The REQUIRED keyword ensures CMake throws an error if the library is not found.

  • target_include_directories(MyProgram PRIVATE "${CMAKE_THREAD_INCLUDE_DIRS}"): This line includes the library's header directory (CMAKE_THREAD_INCLUDE_DIRS) in the search path for MyProgram.

  • target_link_libraries(MyProgram PRIVATE Threads::Threads): This line links the executable with the external library's target (Threads::Threads).

Advanced Linking Options and Flags

Visibility Control

You can control which symbols from a library are visible to linked programs using compiler flags like -fvisibility=hidden (GCC) or /DEF:{symbol} (MSVC).

CMake can help manage these flags through target properties:

if(CMAKE_C_COMPILER_ID MATCHES "GNU")
  target_compile_properties(MyLib PROPERTIES CVISIBILITY_PRESET "hidden")
endif()

Explanation:

  • The if statement checks the compiler ID.

  • If it's GCC, target_compile_properties sets the CVISIBILITY_PRESET property to "hidden" for the target MyLib, effectively hiding symbols by default.

  • Custom Linker Flags: You can pass custom linker flags using target_link_libraries with the LINK_OPTIONS clause:

target_link_libraries(MyProgram PRIVATE MyLib LINK_OPTIONS "-Wl,-rpath,/path/to/additional/libraries")

Explanation:

  • This line links the program MyProgram with MyLib and adds the linker flag -Wl,-rpath,/path/to/additional/libraries, specifying an additional search path for libraries at runtime.

Cross-Platform Considerations

  • Toolchain Files: CMake allows defining toolchain files that specify compiler and linker settings for different platforms. This enables consistent builds across operating systems.

  • Platform-Specific Flags: Be mindful of platform-specific compiler and linker flags. CMake can handle some of these through built-in properties like CMAKE_CXX_FLAGS or CMAKE_EXE_LINKER_FLAGS, but you might need to add custom flags for specific needs.

Example of a cross platform build:

cmake_minimum_required(VERSION 3.0)

project(MyMultithreadedApp)

# Find pthread library (replace with the actual find_package call for your target platform)
find_package(Threads REQUIRED)

# Include directories (adjust based on your header locations)
target_include_directories(MyProgram PRIVATE
  "${CMAKE_CURRENT_BINARY_DIR}"  # Include current directory for project headers
  "${CMAKE_THREAD_INCLUDE_DIRS}"  # Include directory from Threads package
)

# Source files (replace with your actual source files)
add_executable(MyProgram main.cpp worker.cpp)

# Link with pthread library
target_link_libraries(MyProgram PRIVATE Threads::Threads)

# Platform-specific flags (optional)
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
  target_link_libraries(MyProgram PRIVATE "-lpthread")  # Link with pthread on Linux
endif()

if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
  target_link_libraries(MyProgram PRIVATE "-framework pthread")  # Link with pthread on macOS
endif()

Explanation:

  1. CMake Minimum Version: We specify the minimum required CMake version.

  2. Project Name: We define the project name.

  3. Finding Threads Library: We use find_package(Threads REQUIRED) to attempt finding the pthread library. This might require replacing Threads with the actual package name for your target platform (e.g., PkgConfig on some Linux distributions).

  4. Include Directories: We set the include directories for our project headers and the headers from the Threads package.

  5. Source Files: We define the source files for our application.

  6. Linking with Threads: We link the executable MyProgram with the target Threads::Threads from the found library.

  7. Platform-Specific Flags (Optional): We use conditional statements to add platform-specific linker flags. On Linux, we link with -lpthread, and on macOS, we link with the -framework pthread framework.