Wenyin 的拾萃园
CMake Commands

CMake 着实让人有些头疼,CMake 的复杂不仅来源于其自身,还来源于它意图统一各平台编译工具链。这里我们简单记录一些命令以方便回顾。

CMakeLists.txt 文件的开头一般会有两行命令,首先强制要求 CMake 的最低版本,再指定项目的名称等基本信息。

cmake_minimum_required(VERSION 3.10)
project(modern-cmake-libAndApp-ex)
# 设置该库的 include 目录
set(MHDCXX_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 要继续走到子目录里的时候就用
add_subdirectory(src)

在 CMake 中,程序将要生成的可执行文件和静态库动态库等都可以视为目标(target),分别要调用以下两种命令:

add_executable
add_library

给一个目标单独设置一些编译指令,常用以下几种命令

target_include_directories
target_compile_definitions
target_link_libraries # *e.g.* target_link_libraries(app PRIVATE lib1)

每一命令都可以是 PRIVATE(仅被这一模块自身使用),INTERFACE(仅供此模块的客户使用)或 PUBLIC(模块自身和其客户都可使用),于是我们可以自如地进行粒度的粗细调节。

自 Modern CMake (CMake 3)以后,全局地进行属性设置已经不再提倡了,

include_directories
compile_definitions
link_libraries

这三者在过去的 CMakeLists.txt 中常常可以看到,但我们已经不再使用了。

Library 库

在 CMake 中 add_library 可以构建各种各样的库,分 Normal Library(一般库)、Object Libraries(对象库)、Interface Libraries(接口库)、Imported Libraries(导入库)、Alias Libraries(别名库)

Normal Library 一般库

静态库、动态库这些属于传统的一般库(normal libraries),在程序上常常体现如下。

# Create a static library.
add_library(lib1 STATIC ${lib1_SOURCES})
# 有些头文件是暴露给客户用的,有些头文件不暴露给客户,模块自己用,放在 src 文件夹里。
target_include_directories(lib1 PUBLIC include PRIVATE src)

xtensor 这种数据结构式的、仅由头文件构成的 C++ 库,常常得要 PUBLIC 式嵌入其他目标(因为它的 xarrayxtensor 大概率会常常出现在其他库的接口上,而不仅仅是在实现中)。

# Look for xtensor which has been installed on your computer. 
find_package(xtensor REQUIRED)
target_include_directories(your_target PUBLIC ${xtensor_INCLUDE_DIRS})
target_link_libraries(your_target PUBLIC xtensor)

Interface Library 接口库

现在许多现代化的 C++ 小项目都采用了仅头文件(header-only)的组织结构,在调用时只需要 include 这些头文件即可,它们的 CMakeList.txt 里面常常会出现这样的命令。

add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

先是声明一个接口库(interface library),然后再给这个接口库加上一个别名,这样 ${PROJECT_NAME}${PROJECT_NAME}::${PROJECT_NAME} 两个名字都可以用来调用这个库。

Imported Library 导入库

Alias Library 别名库

How to Include Headers 如何包含头文件

CMake 3 之后的版本不再推荐全局粗糙的 include_directories 了,转而使用 target_include_directories 以精细化对头文件搜索目录的设置。

set(HEADER_FILES ${YOUR_DIRECTORY}/file1.h ${YOUR_DIRECTORY}/file2.h)

add_library(mylib libsrc.cpp ${HEADER_FILES})
target_include_directories(mylib PRIVATE ${YOUR_DIRECTORY})
add_executable(myexec execfile.cpp ${HEADER_FILES})
target_include_directories(myexec PRIVATE ${YOUR_DIRECTORY})

细节可见 How to properly add include directories with CMake

Specify The Compiler 指定编译器

当 Ubuntu 系统中同时共存多版本的 GNU 时,有些比较新的项目可能会使用 C++17 或者 C++20 版的编译器新特性,这时需要手动指定。

~/path-to-your-repo/build$ cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ ..

if, while and for loop

if(WIN32)
    message("You're running CMake on Windows.")
endif()
while(A LESS "1000000")
    message("${A}")                 # Print A
    math(EXPR T "${A} + ${B}")      # Add the numeric values of A and B; store result in T
    set(A "${B}")                   # Assign the value of B to A
    set(B "${T}")                   # Assign the value of T to B
endwhile()
foreach(ARG ${MY_LIST})
    message("${ARG}")
endforeach()

Debug

CMake 中 debug 是有专门工具的 CMakePrintHelpers,在给 CMake debug 的时候常常需要精确地确定路径,这时候交给 CMakePrintHelpers 就可以了。比如,

include(CMakePrintHelpers)
set(PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
cmake_print_variables(PROJECT_SOURCE_DIR PROJECT_INCLUDE_DIR)

References