CMake

CMake简单使用笔记

Posted by YuCong on February 20, 2020

CMakeLists 简单使用笔记

CMake

Created 2020.02.20 by William Yu; Last modified: 2021.03.31-V1.0.4

Contact: windmillyucong@163.com

Copyleft! 2021 William Yu. Some rights reserved.


References

  • 《CMake Practice》
  • https://cmake.org/cmake/help/latest/index.html

1.常用指令总结

设定cmake版本
1
cmake_minimum_required(VERSION 3.3)
project()

设定project名称,一个项目每个独立的模块都可以设置自己的project name

set()

指定自定义的变量,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#### 创建一个 Library ####
project(module_lib)

# list of all the source files (headers and .ccs)
set(PROJECT_LIBRARY_SRCS  # 添加库的所有源文件
        "impl/a_function.cc"
        "impl/b_function.cc"  
        "impl/c_function.cc"
        )

# Include all the target headers.  # 添加库的头文件目录
set(PROJECT_LIBRARY_HDRS
        ${PROJECT_SOURCE_DIR}/include
        )

# Include all the depending libraries.  # 添加库会用到的其他三方库
set(PROJECT_LIBRARY_LIBS
        eigen
        opencv
        glog
        )

# 使用刚刚定义的变量,创建一个共享库
# Create a shared library from this module.
user_add_library(${PROJECT_NAME} 
	"${PROJECT_LIBRARY_HDRS}" "${PROJECT_LIBRARY_SRCS}" "${PROJECT_LIBRARY_LIBS}")

# 函数的具体实现
# adds a named library in build targets, with included headers, sources and linked libraries.
function(USER_ADD_LIBRARY lib_name hdrs srcs link_libs)
    # Add library build target
    add_library(${lib_name} SHARED ${srcs})
    # Target includes
    target_include_directories(${lib_name} PRIVATE ${hdrs}
            SYSTEM INTERFACE ${hdrs})
    # Link libraries
    target_link_libraries(${lib_name} PRIVATE ${link_libs})
endfunction()
message()

类似于print

1
2
3
4
5
General messages
  message([<mode>] "message text" ...)

Reporting checks
  message(<checkState> "message text" ...)
  • 普通消息的输出

  • 1
    
    FATAL_ERROR
    

    CMake Error, stop processing and generation.

  • 1
    
    SEND_ERROR
    

    CMake Error, continue processing, but skip generation.

  • 1
    
    WARNING
    

    CMake Warning, continue processing.

  • 检查报告

    • 1
      
      STATUS
      

      简单报告

    • 1
      2
      3
      4
      5
      6
      7
      8
      
      CHECK_START
      Record a concise message about the check about to be performed.
            
      CHECK_PASS
      Record a successful result for a check.
            
      CHECK_FAIL
      Record an unsuccessful result for a check.
      

      更详细的信息报告,将STATUS细分成了三类

    • For example:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
      message(CHECK_START "Finding my things")
      list(APPEND CMAKE_MESSAGE_INDENT "  ")
      unset(missingComponents)
            
      message(CHECK_START "Finding partA")
      # ... do check, assume we find A
      message(CHECK_PASS "found")
            
      message(CHECK_START "Finding partB")
      # ... do check, assume we don't find B
      list(APPEND missingComponents B)
      message(CHECK_FAIL "not found")
            
      list(POP_BACK CMAKE_MESSAGE_INDENT)
      if(missingComponents)
        message(CHECK_FAIL "missing components: ${missingComponents}")
      else()
        message(CHECK_PASS "all components found")
      endif()
      
include()

添加

add_subdirectory()

添加子目录下的CMakeLists.txt

add_executable()

编译可执行文件

include_directories()

添加头文件路径

链接库到目标文件

add_library()

编译库

function()
  • https://cmake.org/cmake/help/latest/command/function.html?highlight=function

自定义函数

1
2
3
function(<name> [<arg1> ...])
  <commands>
endfunction()
list()
1
list(APPEND LIBS ${TRIFO_PROJECT_LIBRARY_LIBS})

添加东西进列表

2.常用环境变量与关键字总结

目录
1
2
3
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR

这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录。如果是 out-of-source 编译,指的是工程编译发生的目录

工程顶层目录
1
2
3
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR

这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。 也就是在 in source 编译时,他跟 CMAKE_BINARY_DIR 等变量一致。

当前目录
1
2
3
4
5
6
CMAKE_CURRRENT_BINARY_DIR

如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,
如果是 out-of-source 编译,他指的是 target 编译目录。
使用我们上面提到的 ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。
使用 SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
cmak模块路径
CMAKE_MODULE_PATH

这个变量用来定义自己的 cmake 模块所在的路径。
如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理
CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。
比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
这时候你就可以通过 INCLUDE 指令来调用自己的模块了。
输出的可执行程序与库文件的路径
1
EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
1
2
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/lib)   
系统信息
1
2
3
4
5
6
7
CMAKE_MAJOR_VERSION     CMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSION     CMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSION     CMAKE 补丁等级,比如 2.4.6 中的 6
CMAKE_SYSTEM            系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME       不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION    系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR  处理器名称,比如 i686

3.库的生成与使用

code example:

  • 3.create_lib
  • 4.use_lib

库的生成需要头文件.h和源代码.cpp

1
2
3
4
5
6
7
8
include_directories(${PROJECT_SOURCE_DIR}/include/)   # 添加头文件搜索路径

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin/)   # 指定编译生成的可执行程序的位置
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/)      # 指定编译生成的库的输出位置  ./build/lib

set(LIBTEST_SRC a_function.cc)   # 指定库的源码
add_library(test_shared SHARED ${LIBTEST_SRC})   # 编译共享库libtest_shared.so
add_library(test_static STATIC ${LIBTEST_SRC})   # 编译静态库libtest_static.a

库的使用需要载入头文件和lib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
include_directories(${PROJECT_SOURCE_DIR}/include/)   # 添加头文件搜索路径

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin/)   # 指定编译生成的可执行程序的位置
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/)      # 指定编译生成的库的输出位置  ./build/lib

set(LIBTEST_SRC a_function.cc)   # 指定库的源码
add_library(test_shared SHARED ${LIBTEST_SRC})   # 编译共享库libtest_shared.so
add_library(test_static STATIC ${LIBTEST_SRC})   # 编译静态库libtest_static.a

add_executable(main main.cpp)   # 编译二进制目标文件
target_link_libraries(main test_shared)  # 将共享库链接到二进制文件

add_executable(main_static main.cpp)   # 编译二进制目标文件
target_link_libraries(main_static test_static)  # 将静态库链接到二进制文件

4.载入三方库

4.1. 直接载入

code example

  • 5.find_sys_lib

若清楚库的头文件和lib所在位置,直接include进来即可

1
2
include_directories(/usr/include)      # 包含库的头文件
target_link_libraries(目标文件名 库名XXX)   # 查找动态库 libXXX.so 并链接

4.2. 查询载入,使用命令 find_package

若不清楚库头文件和lib的位置,可以使用查询载入

  • 两种搜索模式
    • Module模式
    • Config模式
  • 默认采取Module模式
  • Module 没有找到库,才会采取Config模式

4.2.1. Module模式

code example

  • 5.find_sys_lib
基本原理
  • 查找 FindXXX.cmake 文件

  • 查找的路径:CMAKE_MODULE_PATH 默认/usr/share/cmake-3.5/Modules/

  • FindXXX.cmake 文件内部通常提供这几个变量:

    1
    2
    3
    
    <name>_FOUND # 查找标志,找到为TRUE
    <name>_INCLUDE_DIR or <name>_INCLUDES  # 库的头文件
    <name>_LIBRARY or <name>_LIBRARIES # 库
    
补充笔记:cmake内置的模块信息
  • cmake内置了很多模块的信息

  • 查看CMake支持的模块

    1
    
    cmake --help-module-list
    
  • cmake 内置模块信息搜索路径为

    1
    2
    
    /usr/share/cmake/Modules/
    /usr/share/cmake-3.5/Modules/
    

    里面有大量内置模块的Findxxx.Cmake

引入外部库的命令

以curl 库为例:

1
2
3
4
5
6
7
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
  INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
  TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
  MESSAGE(FATAL_ERROR "CURL library not found")
ENDIF(CURL_FOUND)
补充:FindXXX.cmake文件究竟长啥样子呢?

code example:

  • 6.user_lib_with_Fingcmake

如果我是库的开发人员,应该如何为我的库编写FindXXX.cmake 文件?

很简单,提供给调用者三个参数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 简单的Findxxx.cmake文件 提供三个参数:
#   - 头文件路径
#   - 库文件路径
#   - XXX_FOUND标志位


find_path(HELLO_INCLUDE_DIR  # 确认头文件
  a_function.h
  ${PROJECT_SOURCE_DIR}/include/)
message(".h dir: ${HELLO_INCLUDE_DIR}")

find_library(HELLO_LIBRARY # 确认库文件
  libtest_shared.so 
  ${PROJECT_SOURCE_DIR}/lib/)
message("lib dir: ${HELLO_LIBRARY}")

if(HELLO_INCLUDE_DIR AND HELLO_LIBRARY) # 设置xxx_FOUND
  set(HELLO_FOUND TRUE)
endif()

4.2.2. Config模式

基本原理
  • 查找 XXXConfig.cmake 文件

  • 搜索路径:

    1
    
    cd /usr/local/lib/cmake/
    
  • XXXConfig.cmake文件提供:

    1
    2
    
    XXX_INCLUDE_DIRS
    XXX_LIBRARIES
    
  • 如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会去/usr/local/lib/cmake/XXX/中查找XXXConfig.cmake文件

  • 通常安装库的时候会拷贝一份xxxConfig.cmake文件到系统目录中

  • 和4.2.1.Module模式差不多,只是文件写法不一样,文件放置的位置也不一样

  • 个人感觉没有Module模式常用

引入外部库的命令
  • 如果xxx没有安装在系统目录,无法自动找到xxxConfig.cmake,可以手动引入

    1
    
    set(xxx_DIR <real_path_to_dir_of_xxxConfig.cmake>)  # 添加xxxConfig.cmake所在路径
    
  • 然后再FIND_PACKAGE即可

5. 命令行参数

1
-D<键>=<值> -D<键>=<值>

e.g.:

1
cmake .. -DLIBRARY_TYPE=SHARED -DCMAKE_BUILD_TYPE=Release 

CMakeLists.txt里面接收命令行参数

  • 判断相等的写法:MATCHES
1
2
3
4
5
6
7
8
9
if(LIBRARY_TYPE MATCHES "SHARED")
  add_library(test_shared SHARED ${LIBTEST_SRC})   # 编译共享库libtest_shared.so
  add_executable(main main.cpp)   # 编译二进制目标文件
  target_link_libraries(main test_shared)  # 将共享库链接到二进制文件
elseif(LIBRARY_TYPE MATCHES "STATIC")
  add_library(test_static STATIC ${LIBTEST_SRC})   # 编译静态库libtest_static.a
  add_executable(main_static main.cpp)   # 编译二进制目标文件
  target_link_libraries(main_static test_static)  # 将静态库链接到二进制文件
endif()

6.其他

设置c++ 版本
1
2
3
4
# 下面这三行是设置编译的时候采用c++14标准,默认是11标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
查看编译速度
1
2
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")

编译速度优化


Contact

Feel free to contact me windmillyucong@163.com anytime for anything.

License

CC0