cmake学习笔记

看了B站一个cmake教程,部分笔记内容来自别人

CMake中的变量

  • 变量和含义

    常用变量 含义
    PROJECT_NAME 工程名变量
    PROJECT_SOURCE_DIR 顶层的项目目录
    PROJECT_BINARY_DIR 使用cmake的路径
    CMAKE_ROOT CMAKE安装的根目录
    CMAKE_BUILD_TYPE 编译类型:emptyDebugRelease
    CMAKE_SOURCE_DIR 顶层的CMakeLists.txt所在路径
    CMAKE_BINARY_DIR 顶层的CMakeLists.txtbuild所在目录
    CMAKE_<LANG>_COMPILER 设定某个语言LANG的编译器,比如g--
    CMAKE_INSTALL_PREFIX 指令install的路径
    CMAKE_CURRENT_SOURCE_DIR 当前CMakeLists.txt所在路径
    CMAKE_CURRENT_BINARY_DIR 当前CMakeLists.txtbuild所在目录
    EXECUTABLE_OUTPUT_PATH 可执行文件输出路径
    LIBRARY_OUTPUT_PATH 库输出路径
  • 个别变量解释
    源码目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    jmudou   
    ├── build.sh
    ├── CMakeLists.txt
    └── muduo
    └── base
    ├── Atomic.h
    ├── CMakeLists.txt
    ├── copyable.h
    ├── tests
    │ ├── Atomic_unittest.cc
    │ ├── CMakeLists.txt
    │ └── Timestamp_unittest.cc
    ├── Timestamp.cc
    ├── Timestamp.h
    └── Types.h

    三个CMakeLists.txt输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    jmuduo/CMakeLists.txt: 
    PROJECT_SOURCE_DIR= 11/jmuduo
    PROJECT_BINARY_DIR= 11/build/debug
    CMAKE_SOURCE_DIR= 11/jmuduo # 指定顶层的CMakeLists.txt的,因此所有的都相同
    CMAKE_BINARY_DIR= 11/build/debug
    # 上面四项都是与整个项目相关,因此都是一致的。
    CMAKE_CURRENT_SOURCE_DIR= 11/jmuduo # 指定当前层的CMakeLists.txt的,因此与层相关
    CMAKE_CURRENT_BINARY_DIR= 11/build/debug

    muduo/base/test/CMakeLists.txt:
    PROJECT_SOURCE_DIR= 11/jmuduo
    PROJECT_BINARY_DIR= 11/build/debug
    CMAKE_SOURCE_DIR= 11/jmuduo
    CMAKE_BINARY_DIR= 11/build/debug
    CMAKE_CURRENT_SOURCE_DIR= 11/jmuduo/muduo/base/tests
    CMAKE_CURRENT_BINARY_DIR= 11/build/debug/muduo/base/tests

    muduo/base/CMakeLists.txt:
    PROJECT_SOURCE_DIR= 11/jmuduo
    PROJECT_BINARY_DIR= 11/build/debug
    CMAKE_SOURCE_DIR= 11/jmuduo
    CMAKE_BINARY_DIR= 11/build/debug
    CMAKE_CURRENT_SOURCE_DIR= 11/jmuduo/muduo/base
    CMAKE_CURRENT_BINARY_DIR= 11/build/debug/muduo/base

函数

所有的函数列表中,<>表示的参数必须要有,[]表示的参数为可选。

  • set
    可以设置三个类型的变量值:正常变量,cache variable、环境变量。

    • Normal Variable:set(<variable> <value>... [PARENT_SCOPE])
    • cacheset(<variable> <value>... CACHE <type> <docstring> [FORCE])
    • envset(ENV{<variable>} [<value>])
  • OPTION:提供用户可以选择的选项

    • 格式:option(<variable> "description" [initial value])
    • 比如:
      1
      2
      3
      4
      5
      option(
      USE_MYPATH
      "user path"
      ON
      )
  • aux_source_directory

    • 语法:aux_source_directory(<dir> <variable>)
    • 查找目录dir下的所有源文件(即.c, .cpp, .cc等文件),并将名称保存到 variable 变量
  • add_compile_options

    • add_compile_options(<option> ...)
    • COMPILE_OPTIONS目录属性中添加选项,这些选项在编译当前目录及子目录的对象时被使用。
    • e.g: add_compile_options(-std=c++11)
  • add_subdirectory

    • add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
    • 添加一个将被编译的子目录。指明CMakeLists.txt所在目录下包含了一个子目录source_dir。这样source_dir下的源文件和CMakeLists.txt等也会被处理。
  • target_link_libraries

    • target_link_libraries(exec libs)
    • 表示可执行程序exec需要链接到一个名为libs的链接库。
  • add_library

    • `add_library( [STATIC | SHARED | MODULE]

      [EXCLUDE_FROM_ALL]
      [source1] [source2 ...])`
    • 添加一个名为的库文件,该库文件将会根据调用的命令里列出的源文件来创建。对应于逻辑目标名称,而且在一个工程的全局域内必须是唯一的。待构建的库文件的实际文件名根据对应平台的命名约定来构造(比如lib.a或者.lib)。

    • 指定STATIC,SHARED,或者MODULE参数用来指定要创建的库的类型。STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接,在运行时被加载。MODULE库是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。如果没有类型被显式指定,这个选项将会根据变量BUILD_SHARED_LIBS的当前值是否为真决定是STATIC还是SHARED。

  • configure_file

    • 加入一个配置头文件,用于处理 CMake 对源码的设置

      1
      2
      3
      4
      configure_file (
      "${PROJECT_SOURCE_DIR}/config.h.in" # config.h.in文件目录
      "${PROJECT_BINARY_DIR}/config.h" # config.h 生成的头文件目录
      )

      在配置文件config.h中,配置相关项,比如options中的USE_MYPATH

      1
      #cmakedefine USE_MYMATH
  • include

    • include(file [optional]):读取CMake的相关文件。
    • include(moudle [optional])
      the file with name .cmake is searched in the CMAKE_MODULE_PATH
  • include_directories

    1
    include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
    • 将给定的路径添加到编译器搜索包含文件(.h文件)的路径列表中。缺省情况下,该路径会被附加在当前路径列表的后面。这种缺省行为可以通过设置CMAKE_include_directories_BEFORE变量为ON被改变。通过将该变量改变为BEFORE或AFTER,你可以在追加和附加在前端这两种方式中选择,而不用理会缺省设置。如果指定了SYSTEM选项,编译器将会认为该路径是某种平台上的系统包含路径。
  • install
    使用:cmake之后,sudo make install就可以执行相应的库和头文件的安装。

    • TARGET格式

      1
      2
      3
      4
      5
      6
      7
      8
      install(TARGETS targets...
      [[ARCHIVE|LIBRARY|RUNTIME]
      [DESTINATION <dir>]
      [PERMISSIONS permissions...]
      [CONFIGURATIONS [Debug|Release|...]]
      [COMPONENT <component>]
      [OPTIONAL]
      ] [...])
    • targets的类型
      可以安装的库有[ARCHIVE|LIBRARY|RUNTIME]三种:
      1) 可执行程序视为runtime
      2) 静态库视为archieve
      3) Module Library视为library
      4) 共享库和平台有关

另一种说法

有5中可以被安装的目标文件:ARCHIVE,LIBRARY,RUNTIME,FRAMEWORK,和BUNDLE。

除了被标记为MACOSX_BUNDLE属性的可执行文件被当做OS X上的BUNDLE目标外,其他的可执行文件都被当做RUNTIME目标。

静态链接的库文件总是被当做ARCHIVE目标。

模块库总是被当做LIBRARY目标。

对于动态库不是DLL格式的平台来说,动态库会被当做LIBRARY目标来对待,

被标记为FRAMEWORK的动态库是例外,它们被当做OS X上的FRAMEWORK目标。

对于DLL平台而言,动态库的DLL部分被当做一个RUNTIME目标而对应的导出库被当做是一个ARCHIVE目标。

所有基于Windows的系统,包括Cygwin,都是DLL平台。ARCHIVE,LIBRARYRUNTIME和FRAMEWORK参数改变了后续属性会加诸之上的目标的类型。如果只出了一种类型,那么只有那种类型的目标会被安装(这样通常只会安装一个LL或者一个导出库。)

- 参数
    - `DESTINATION`:  
    指定一个文件将要被安装的目录。如果给的是一个全路径,那么就直接使用;如果是相对路径,默认是相对`CMAKE_INSTALL_PREFIX`,其值默认是`/usr/local/`。  
    1) 头文件:`inclide`    
    2) 可执行文件:`bin`  
    3) 库:`lib`  

    - `PERMISSIONS`: 指定安装文件的权限:  
        &emsp;&emsp;1) user&emsp;: ` OWNER_READ, OWNER_WRITE, OWNER_EXECUTE`  
        &emsp;&emsp;2) group:`GROUP_READ, GROUP_WRITE, GROUP_EXECUTE`  
        &emsp;&emsp;3) other:`WORLD_READ, WORLD_WRITE, WORLD_EXECUTE`  
        &emsp;&emsp;4) uid&emsp;:` SETUID, and SETGID`。 

    - `CONFIGURATIONS`:为安装规则建立一个配置文件列表。 
  • install

    • FILES格式

      1
      2
      3
      4
      5
      6
      INSTALL(FILES files... 
      DESTINATION <dir>
      [PERMISSIONS permissions...]
      [CONFIGURATIONS [Debug|Release|...]]
      [COMPONENT <component>]
      [RENAME <name>] [OPTIONAL])
      • files:即文件名
  • 测试

    • enanle_testing():启动测试

    • add_test(testname Exename arg1 arg2 ...)

      • 需要先运行测试程序enanle_testing(),这个指令才有效。
      • Exename是可执行程序名,参数arg1, arg2
    • set_tests_properties(...)

      • 括号内格式:(Exename [Exename2...] PROPERTIES prop1 value1 prop2 value2),其中PROPERTIES是固定的单词不能改
      • Exename设置属性,如果没有这个属性,就报错,有如下属性:
          1) WILL_FAIL:如果设置为true,那么会反转测试结果的pass/fail标志。
          2) PASS_REGULAR_EXPRESSION: 匹配正则表达式,只少有一个匹配,则pass
          2) FAIL_REGULAR_EXPRESSION: 匹配正则表达式,则fail
    • 宏测试

      1
      2
      3
      4
      5
      macro(<name> [arg1 [arg2 [arg3 ...]]])
      COMMAND1(ARGS ...)
      COMMAND2(ARGS ...)
      ...
      endmacro(<name>)

      就类似于写一个函数,用宏实现,调用:name(arg1,arg2,...)

  • 设置项目的版本号

    • 在顶层的CMakeLists.txt中:

      1
      2
      3
      # 加入版本号是 1.0
      set (Project_VERSION_MAJOR 1) # 主版本号
      set (Project_VERSION_MINOR 0) # 副版本号
    • 在配置文件config.h.in中设置:

      1
      2
      #define Project_VERSION_MAJOR @[email protected]
      #define Project_VERSION_MINOR @[email protected]
    • main函数中就可以直接使用这两个宏,代表版本号:

      1
      2
      3
      printf("Version %d.%d\n",
      Project_VERSION_MAJOR,
      Project_VERSION_MINOR);
  • target_compile_definitions

    • 格式

      1
      2
      3
      target_compile_definitions(<target>
      <INTERFACE|PUBLIC|PRIVATE> [items1...]
      [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    • 作用:编译时定义的宏

  • string

    • REPLACE

      1
      2
      3
      string(REPLACE 
      <match_string> <replace_string>
      <output_variable> <input> [<input>...])

      将所有input中出现的match_String替换为replace_string,并且将结果存在output_variable

  • message

    1
    message([<mode>] "message to display" ...)
    • 显示信息给用户
      mode取决于信息的类型:

      • STATUS:以简洁的方式显示用户感兴趣的信息。

        1
        2
        3
        4
        5
        6
        message(STATUS 
        "CXX_FLAGS = "
        ${CMAKE_CXX_FLAGS}
        " "
        ${CMAKE_CXX_FLAGS_${BUILD_TYPE}}
        )

        可以理解为printf,把后面的几个信息以空格相间,然后打印出来。显示结果为(和上面对应分成三段):

        1
        2
        3
        4
        5
        6
        7
        8
        9
        CXX_FLAGS = 

        -g -D_FILE_OFFSET_BITS=64 -Wall -Wextra
        -Werror -Wconversion -Wno-unused-parameter
        -Wold-style-cast -Woverloaded-virtual
        -Wpointer-arith -Wshadow -Wwrite-strings
        -march=native -rdynamic

        -O0
1
2
3
4
5
6
(无)           = 重要消息;
STATUS = 非重要消息;
WARNING = CMake 警告, 会继续执行;
AUTHOR_WARNING = CMake 警告 (dev), 会继续执行;
SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤;
FATAL_ERROR = CMake 错误, 终止所有处理过程;
  • find_package

    1
    2
    3
    4
    5
    find_package(<PackageName> 
    [version] [EXACT] [QUIET] [MODULE]
    [REQUIRED] [[COMPONENTS] [components...]]
    [OPTIONAL_COMPONENTS components...]
    [NO_POLICY_SCOPE])

    主要是寻找和加载外部项目。如果PackageName找到了,PackageName-found会显出,当没有找到时,默认显示
    PackageName-not found。通过模式的选择,可以处理在没有找到包时的解决方案。

    • QUIET:不显示有用信息,
    • REQUIRED:报错
  • find_path

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    find_path (
    <VAR>
    name | NAMES name1 [name2 ...]
    [HINTS path1 [path2 ... ENV var]]
    [PATHS path1 [path2 ... ENV var]]
    [PATH_SUFFIXES suffix1 [suffix2 ...]]
    [DOC "cache documentation string"]
    [NO_DEFAULT_PATH]
    [NO_PACKAGE_ROOT_PATH]
    [NO_CMAKE_PATH]
    [NO_CMAKE_ENVIRONMENT_PATH]
    [NO_SYSTEM_ENVIRONMENT_PATH]
    [NO_CMAKE_SYSTEM_PATH]
    [CMAKE_FIND_ROOT_PATH_BOTH |
    ONLY_CMAKE_FIND_ROOT_PATH |
    NO_CMAKE_FIND_ROOT_PATH]
    )

    用以寻找包含着name1等文件的目录,如果找到了结果存储在VAR,没有找到结果结果是VAR-not found。成功时,变量被清除find_path再次搜索,没有成功,fin_path再次以相同的变量被调用时搜索。

  • find_library
    同上find_path

    1
    find_library (<VAR> name0|NAMES name1 [path1 path2 ...])
    • OPTIONS
      • NAMES
        library指定一个或多个可能的名字。

生成库

  • 用add_library命令
静态库与动态库的区别
  • 静态库:

    • 是在链接阶段,库中目标文件所含的所有将被程序使用的函数的机器码被拷贝到最终的可执行文件中。
    • 这个链接的过程是在编译期完成的,好处是程序运行时与函数库无关了,移植方便。运行效率快,以为所有的代码都变成机器码了。
    • 当然,编译结果对象体积会变大。而且如果多个程序公用一个库代码,其实每个程序运行起来都会在内存中包含一份库的代码,造成内存浪费。
  • 动态库:

    • 相反的,目标文件需要的库代码在编译时不会被连接到目标代码中,而是在运行时才被载入。
    • 可执行文件只包含了它需要的函数的引用表,而不是代码。
    • 只有执行时需要的函数代码才被拷贝到内存中。
    • 可以增量更新。
    • 缺点也明显,如果缺失了一些动态库程序会报错。

生成库的使用

  • cmake中链接内部库的依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(2.8)
project(demo)

add_compile_options(-std=c++11)

set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(LIBRARY_OUTPUT_NAME ${PROJECT_NAME})

# 创建一个动态库,名字是libA
add_library(libA SHARED src/classa.cpp)

add_executable(${PROJECT_NAME} src/main.cpp)

# 链接上面创建的动态库
# 也可以在add_executable中加入classa.cpp,但是如果后面还有用到这个库话抽象成一个lib可以复用。
target_link_libraries(${PROJECT_NAME} libA )
  • 导入外部库文件
    • 绝对路径引用方式
    • install方式
    • find_package
  • 定义FindlibB.cmake:这个文件本质是为了find_package服务器的,因为这个函数并没有查询自定义目录的能力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
find_path(libB_INCLUDE_DIR
NAMES libB.h
PATHS "C:/Users/admin/Desktop/CMAKE/src"
)

find_library(libB_LIBRARY
NAMES libB
PATHS "C:/Users/admin/Desktop/CMAKE/src")

if(libB_INCLUDE_DIR AND libB_LIBRARY)
set(libB_FOUND TRUE)
endif(libB_INCLUDE_DIR AND libB_LIBRARY)

if(libB_FOUND)
if(NOT libB_QUIETLY)
message(STATUS "找到了libB ${libB_LIBRARY}")
endif(NOT libB_QUIETLY)
else(libB_FOUND)
if(NOT libB_REQUIRED)
message(FATAL_ERROR "没找到了libB ")
endif(NOT libB_REQUIRED)
endif(libB_FOUND)
  • 使用find_package的完整版
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
cmake_minimum_required(VERSION 3.0.0)
project(demo)

add_compile_options(-std=c++11)

# 设置源码路径
set(src_path ${PROJECT_SOURCE_DIR}/src)
set(modules_path ${PROJECT_SOURCE_DIR}/modules/)
set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(LIBRARY_OUTPUT_NAME ${PROJECT_NAME})


# 创建一个动态库,名字是libB
add_library(libB STATIC ${src_path}/libB.cpp)

# add_executable(${PROJECT_NAME} src/main.cpp)

# # 链接上面创建的动态库
# # 也可以在add_executable中加入classa.cpp,但是如果后面还有用到这个库话抽象成一个lib可以复用。
# target_link_libraries(${PROJECT_NAME} libA )

# # 使用第三方库
# # 使用绝对路径
# set(libB xxx/yyy/libB)

# 使用install
# 设置FindXXX.cmake文件路径路径,目前测试的结果是无法识别,路径全对。当把文件放到C:\Program Files\CMake\share\cmake-3.15\Modules后可以识别。
# set(CMAKE_MODULE_PATH "C:/Users/admin/Desktop/CMAKE/modules/")
set(CMAKE_MODULE_PATH ${modules_path})
message(STATUS "CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}")

# 需要指定头文件和库文件的安装路径
# 先指定安装的前缀CMAKE_INSTALL_PREFIX为 工程src目录下的install文件夹
message(STATUS "源码目录:${PROJECT_SOURCE_DIR}")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/libB/install)

# 安装camke文件到 ${PROJECT_SOURCE_DIR}/libB/install/cmake

install(FILES FindlibB.cmake DESTINATION cmake)
# 安装文件到
install(FILES ${src_path}/libB.h
# 将文件安装到${PROJECTOURCE_DIR}/libB/install/include 下
DESTINATION include
)
# 规定了安装工程中的目标(targets)的规则
install(TARGETS libB # 安装的对象是libB
ARCHIVE
# 将静态库安装到${PROJECT_SOURCE_DIR}/libB/install/lib下
DESTINATION lib
)

# find_package查找
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/libB/cmake/)
# 查找libB相关的内容:头文件、库文件。将结果放到libB_FOUND这个变量中,这个变量的名称是xxx_FOUND这样的固定格式
find_package(libB REQUIRED)
# libB_FOUND找到后
if(libB_FOUND)
# 把libB/install/include路径设置到头文件的搜索路径中
include_directories(${libB_INCLUDE_DIR})
# 编译目标
add_executable(${PROJECT_NAME} src/main.cpp)
# 把libB/install/lib中的库文件链接到目标,PS:不确定libB_LIBRARY是个路径还是文件
target_link_libraries(${PROJECT_NAME} ${libB_LIBRARY})
else(libB_FOUND)
message(ERROR)
endif(libB_FOUND)
  • cmake中有大量的约定性的变量,比如FindlibB.cmake这个文件,Find就是标准写法。比如libB_INCLUDE_DIRlibB_LIBRARYlibB_FOUND这些都是固定下发,可以看下 Find Modules 这部分。

  • cmake语言本身很接近编程语言,因此也有一些类型判断的内容,可以参考 if 的用法这块。

  • 参考

# CPP
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×