Search:

PmWiki

pmwiki.org

edit SideBar

Main / Cmake

CMake is a build system generation tool, so for example it creates your Makefiles for you if you use Make.

Tutorial: https://cmake.org/cmake/help/latest/guide/tutorial/index.html#a-basic-starting-point-step-1

Note that newproject is the name of the executable that results, and newlib is the name of the library's folder.

Think of CMake in execution as a line-by-line interpreted language, so things happen sequentially. For example, if you need a variable to be picked up by a subdir, it needs to be defined in your top level before the subdir is added.

CMake defaults differ by platform:
On Windows, it defaults to using MSVC project (Visual Studio toolchain)
On Mac, Xcode project
On Linux, Makefiles

Configuration

Set up your project in a CMakeLists.txt file.

Here is a template for the file:

# make cmake check the version in use
cmake_minimum_required(VERSION X.X)

# set the project name; note that if a language is not included here those files will be ignored
project(newproject VERSION 1.0 LANGUAGES CXX C)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#set(CMAKE_CXX_EXTENSIONS OFF)

# other build options, like debug
set(CMAKE_BUILD_TYPE Debug)

# set compiler flags explicitly
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")

# header with version number
configure_file(newprojver.h.in newprojver.h)

# point to the directory containing a library
add_subdirectory(newlib)

# add the executable
add_executable(newproject newproject.cxx)

# instruction to link to a library
target_link_libraries(newproject PUBLIC newlib)

# include file search path
target_include_directories(newproject PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           "${PROJECT_SOURCE_DIR}/newlib"
                           )

In the library subdir, create this CMakeLists.txt:

add_library(newlib libfile.cxx)

This add_library creates a static library (.a) by default so if you want a shared dynamic lib you should put in the SHARED option to get .so files.

After you create the build system, the compiler flags that have been set for you can be found in the build directory in CMakeFiles/<projectname>.dir/flags.make.

Usage

Execute these commands inside your build/ destination folder.

First, you create the build system:

  • cmake <sourcedir>
  • cmake -DCMAKE_TOOLCHAIN_FILE=path/to/file <sourcedir> (Cross-compile using settings in specified file, which should be in your source dir)

Note that CMake has no deep clean command for the build system, you just need to delete everything inside the build directory and it will re-create it all.

Then, you issue commands to the build system:

  • cmake --clean-first (Build target clean first, then build)
  • cmake --target clean (To clean only)
  • cmake --build <dir> (Build to <dir>)

If you want to see the variables, one way is to install and use the curses TUI version for the system generation step like so: ccmake -LH <sourcedir>

Passing debug verbosity flag to Make

# instead of 
cmake --build .;
# use
cmake --build . -- -d VERBOSE=1;

Syntax, Patterns, Commands

Variables are referenced with ${VARNAME}. To print the variables you can use message( ${CMAKE_CURRENT_SOURCE_DIR} ) for example. Good discussion about variable scope here: https://www.mgaudet.ca/technical/2017/8/31/some-notes-on-cmake-variables-and-scopes

INTERFACE vs PRIVATE vs PUBLIC includes: https://stackoverflow.com/questions/26243169/cmake-target-include-directories-meaning-of-scope

You can pass along command line options to the underlying make system like this:

cmake --build . -- <make-options-here>

as in 

cmake --build . -- VERBOSE=1 -d

If you are building two libraries for your program, and one depends on the other, then for the linking step to be carried out correctly you must tell CMake about this dependency. In the CMakeLists.txt for the needy library, add a target_link_libraries(thislib neededlib) command.

Common Sequences

cmake -DCMAKE_TOOLCHAIN_FILE=cross.cmake ../
cmake --build .

EV Reference

  • CMAKE_CURRENT_BINARY_DIR - This the full path to the build directory that is currently being processed by cmake.
  • CMAKE_BINARY_DIR - This is the full path to the top level of the current CMake build tree.
  • CMAKE_CURRENT_SOURCE_DIR - This the full path to the source directory that is currently being processed by cmake.
  • CMAKE_SOURCE_DIR - The path to the top level of the source tree.
  • PROJECT_BINARY_DIR - Full path to build directory for project. This is the binary directory of the most recent project() command.
  • PROJECT_SOURCE_DIR - This is the source directory of the last call to the project() command made in the current directory scope or one of its parents.

Adding Docker to CMake

# set up command line option for Docker image build here
# invoked as "cmake -DBUILD_DIMAGE <path>"
# expects Dockerfile to be at top level
option(BUILD_DIMAGE "Build Docker image option" OFF)
if (BUILD_DIMAGE)
  find_program(Docker_EXECUTABLE docker)
  if(NOT Docker_EXECUTABLE)
    message(FATAL_ERROR "Cannot find the docker executable!")
  endif()

  if(EXISTS ${CMAKE_SOURCE_DIR}/Dockerfile)  
    string(APPEND dimagename
        "mycompany/${CMAKE_PROJECT_NAME}:${projectname_VERSION_MAJOR}.${projectname_VERSION_MINOR}"
        )
    add_custom_target(docker_image ALL
      COMMAND echo "Building Docker image: ${dimagename}"
      COMMAND sudo ${Docker_EXECUTABLE} build -t ${dimagename} ${CMAKE_SOURCE_DIR}
      )
    add_dependencies(docker_image projectname)
  else()
    message(FATAL_ERROR "Cannot find the Dockerfile!")
  endif()
endif(BUILD_DIMAGE)
unset(BUILD_DIMAGE CACHE)

Adding Doxygen to CMake

# set up command line option for Doxygen documentation generation here
# invoked as "cmake -DDOXYGEN=ON <path>"
# expects Doxyfile to be at top level
option(DOXYGEN "Generate Doxygen documentation option" OFF)
if (DOXYGEN)
  find_program(Doxygen_EXECUTABLE doxygen)
  find_program(Graphviz_EXECUTABLE dot)
  if(NOT Doxygen_EXECUTABLE)
    message(FATAL_ERROR "Cannot find the doxygen executable!")
  endif()
  if(NOT Graphviz_EXECUTABLE)
    message(FATAL_ERROR "Cannot find the graphviz-dot executable!")
  endif()

  if(EXISTS ${CMAKE_SOURCE_DIR}/Doxyfile)
    add_custom_target(doxygen ALL
      WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
      COMMAND echo "Generating documentation"
      COMMAND ${Doxygen_EXECUTABLE} ${CMAKE_SOURCE_DIR}/Doxyfile
      )
  else()
    message(FATAL_ERROR "Cannot find the Doxyfile!")
  endif()
endif(DOXYGEN)
unset(DOXYGEN CACHE)

Adding cppcheck to CMake

To be inserted before add_executable()

# set up command line option for cppcheck static analysis here
# invoked as "cmake -DCPPCHECK=ON <path>"
option( CPPCHECK "Run cppcheck static analysis" OFF )
if( CPPCHECK )
  find_program( CPPCHECK_APP NAMES cppcheck PATH_SUFFIXES "" ".exe")
  # Run this if cppcheck found
  if ( NOT (${CPPCHECK_APP} STREQUAL "CPPCHECK_APP-NOTFOUND") )
    set( CMAKE_CXX_CPPCHECK ${CPPCHECK_APP} )
    list(APPEND CMAKE_CXX_CPPCHECK
      "--enable=warning"
      "--inconclusive"
      # "--force"
      "--inline-suppr"
      #"--suppressions-list=${CMAKE_SOURCE_DIR}/CppCheckSuppressions.txt"
      )
  endif()
else()
  message("NOTICE: cppcheck turned off")
endif(CPPCHECK)
unset(CPPCHECK CACHE)

Set a C pre-processor definition

Add the option and assignment to the cmake command line, like:

cmake -DBUILD_CONNECT_TYPE=SDR_NET_CONNECT ../;

Edit the CMakeLists.txt file of the module in which you need this definition, using the add_compile_definitions cmake command.

if (NOT BUILD_CONNECT_TYPE)
  set(BUILD_CONNECT_TYPE SDR_NET_CONNECT)
endif()
message(STATUS "BUILD_CONNECT_TYPE={${BUILD_CONNECT_TYPE}}")
add_compile_definitions(${BUILD_CONNECT_TYPE})

String literals

This will work in your c++ source file:

#define STRNAME "vbr0"
static constexpr auto g_ethIFname = STRNAME;

But this will not:

CMakelists.txt
add_compile_definitions(STRNAME="\"vbr0\"")

C++ source:
static constexpr auto g_ethIFname = STRNAME;

Although in theory you can create a C pre-proc define in CMake with this method, there is a phantom unexplained problem with assigning that to a constexpr.

Boost header-only library example

We don't even need to specify the library we want to use (like asio for example which has just header-only dependencies as of Boost 1.82 anyway).

# make cmake check the version in use
cmake_minimum_required(VERSION 3.16)

# set the project name; note that if a language is not included here those files will be ignored
project(boost_socket_test VERSION 1.0 LANGUAGES CXX C)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#set(CMAKE_CXX_EXTENSIONS OFF)

# other build options, like debug
set(CMAKE_BUILD_TYPE Debug)

# set compiler flags explicitly
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")

# find Boost library
message("Begin search for Boost")
set(BOOST_ROOT "../boost_1_82_0")
find_package(Boost REQUIRED)
message(STATUS "Boost_FOUND: ${Boost_FOUND}")
message(STATUS "Boost_VERSION: ${Boost_VERSION}")

# add the executable
add_executable(boost_socket_test main.cpp)

# include file search path
target_include_directories(boost_socket_test PUBLIC
                            "${PROJECT_BINARY_DIR}"
                            "${Boost_INCLUDE_DIRS}"
                           )

Implicit Includes of Common Folders

CMake has an exclusion list that includes folders like the Linux headers at /usr/include/ such that if you try to add that folder explicitly using something like:

include_directories("/usr/include")

you'll find that when make spits out its g++ command that directory is NOT included in the -I headers location list.


Page last modified on October 01, 2024, at 04:41 PM