cmake步骤

  1. 编写源代码

  2. 编写CMakeList.txt

  3. cmake [`CMakeList.txt`所在的目录]
    
    1
    2
    3
    4
    5

    这将生成makefile

    4. ```makefile
    make [makefile所在的目录]
    编译完成

cmake关键字

PROJECT关键字

可以用来指定工程的名字和支持的语言,默认支持所有语言

PROJECT (HELLO) 指定了工程的名字,并且支持所有语言—建议

PROJECT (HELLO CXX) 指定了工程的名字,并且支持语言是C++

PROJECT (HELLO C CXX) 指定了工程的名字,并且支持语言是C和C++

该指定隐式定义了两个CMAKE的变量

_BINARY_DIR,本例中是 HELLO_BINARY_DIR

_SOURCE_DIR,本例中是 HELLO_SOURCE_DIR

MESSAGE关键字就可以直接使用者两个变量,当前都指向当前的工作目录,后面会讲外部编译

问题:如果改了工程名,这两个变量名也会改变

解决:又定义两个预定义变量:PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,这两个变量和HELLO_BINARY_DIR,HELLO_SOURCE_DIR是一致的。所以改了工程名也没有关系

SET关键字

用来显示的指定变量的

SET(SRC_LIST main.cpp) SRC_LIST变量就包含了main.cpp

也可以 SET(SRC_LIST main.cpp t1.cpp t2.cpp)

MESSAGE关键字

向终端输出用户自定义的信息

主要包含三种信息:

  • SEND_ERROR,产生错误,生成过程被跳过。
  • SATUS,输出前缀为—的信息。
  • FATAL_ERROR,立即终止所有 cmake 过程.

ADD_EXECUTABLE关键字

生成可执行文件

ADD_EXECUTABLE(hello ${SRC_LIST}) 生成的可执行文件名是hello,源文件读取变量SRC_LIST中的内容

也可以直接写 ADD_EXECUTABLE(hello main.cpp)

上述例子可以简化的写成

PROJECT(HELLO)
ADD_EXECUTABLE(hello main.cpp)

注意:工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的

cmake 语法

  • 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名

  • 指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或分号分开。 以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.cpp 源文件

    就要写成:ADD_EXECUTABLE(hello main.cpp func.cpp)或者ADD_EXECUTABLE(hello main.cpp;func.cpp)

  • 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令

语法注意事项

  • SET(SRC_LIST main.cpp) 可以写成 SET(SRC_LIST “main.cpp”),如果源文件名中含有空格,就必须要加双引号
  • ADD_EXECUTABLE(hello main) 后缀可以不行,他会自动去找.c和.cpp,最好不要这样写,可能会有这两个文件main.cpp和main

内部构建和外部构建

上面的例子就是内部构建,生产的临时晚间特别多,不方便清理

外部构建,就会把生成的临时文件放在build目录下,推荐使用外部构建

  1. 在项目根目录下创建build目录

  2. cd ./build
    build ..
    
    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
    67
    68
    69
    70
    71
    72
    73
    74
    75

    此时的<projectname>_BINARY_DIR变量变为`\root\project\build`

    <projectname>_SOURCE_DIR变为`\root\project`

    3. 直接在build目录下make



    ## 工程化的cmake(父子`CMakeList`)

    在父目录下的`CMakeList`中添加`ADD_SUBDIRECTORY(src bin)`

    意思是src目录下源码生成的二进制文件都会放到bin目录下,如果不加bin,那么编译结果(包括中间结果)都将存放在build/src目录

    在src这个子目录下`CMakeList`的主要语句是`ADD_EXECUTABLE(hello main.cpp)`

    ## 使用cmake安装

    ### 安装文件COPYRIGHT和README

    INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)

    FILES:文件

    DESTINATION:

    1、写绝对路径

    2、可以写相对路径,相对路径实际路径是:${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>

    CMAKE_INSTALL_PREFIX 默认是在 /usr/local/

    cmake -DCMAKE_INSTALL_PREFIX=/usr 在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径

    ### 安装脚本

    PROGRAMS:非目标文件的可执行程序安装(比如脚本之类)

    INSTALL(PROGRAMS runhello.sh DESTINATION bin)

    说明:实际安装到的是 /usr/bin

    ### 安装 doc 中的 文件

    - 一、是通过在 doc 目录建立CMakeLists.txt ,通过install下的file

    - 二、是直接在工程目录通过

    INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)


    DIRECTORY 后面连接的是所在 Source 目录的相对路径

    注意:abc 和 abc/有很大的区别

    目录名不以/结尾:这个目录将被安装为目标路径下的

    目录名以/结尾:将这个目录中的内容安装到目标路径

    ### 安装过程

    cmake ..

    make

    make install

    ## 静态库和动态库的构建

    需要创建lib目录,在lib目录下创建`CMakeList`,内容为

    ```cmake
    SET(LIBHELLO_SRC hello.cpp)
    ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

同时构建静态和动态库

1
2
3
4
5
6
7
// 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

// 修改静态库的名字,这样是可以的,但是我们往往希望他们的名字是相同的,只是后缀不同而已
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

SET_TARGET_PROPERTIES

这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本

同时构建静态和动态库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SET(LIBHELLO_SRC hello.cpp)

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

//对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
//cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)

动态库的版本号

一般动态库都有一个版本号的关联

1
2
3
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2

CMakeLists.txt 插入如下

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

VERSION 指代动态库版本,SOVERSION 指代 API 版本。

安装共享库和头文件

本例中我们将 hello 的共享库安装到/lib目录,

将 hello.h 安装到/include/hello 目录

1
2
3
4
5
6
//文件放到该目录下
INSTALL(FILES hello.h DESTINATION include/hello)

//二进制,静态库,动态库安装都用TARGETS
//ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

注意:

安装的时候,指定一下路径,放到系统下

cmake -DCMAKE_INSTALL_PREFIX=/usr ..

使用外部共享库和头文件

准备工作,新建一个目录来使用外部共享库和头文件

1
2
3
4
5
6
7
[root@MiWiFi-R4CM-srv cmake3]# tree
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp

main.cpp

1
2
3
4
5
#include <hello.h>

int main(){
HelloFunc();
}

如果报错找不到hello.h:使用关键字:INCLUDE_DIRECTORIES 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割。在CMakeLists.txt中加入头文件搜索路径INCLUDE_DIRECTORIES(/usr/include/hello)

如果报错找不到引用(undefined reference to xxx ):

使用关键字:LINK_DIRECTORIES 添加非标准的共享库搜索路径。指定第三方库所在路径,LINK_DIRECTORIES(/home/myproject/libs)

使用关键字:TARGET_LINK_LIBRARIES 添加需要链接的共享库,TARGET_LINK_LIBRARIES的时候,只需要给出动态链接库的名字就行了。

在CMakeLists.txt中插入链接共享库,主要要插在executable的后面TARGET_LINK_LIBRARIES(main libhello.so)()

可以通过ldd来确定可执行文件链接的共享库,例如:

1
2
3
4
5
6
7
8
[root@MiWiFi-R4CM-srv bin]# ldd main 
linux-vdso.so.1 => (0x00007ffedfda4000)
libhello.so => /lib64/libhello.so (0x00007f41c0d8f000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f41c0874000)
libm.so.6 => /lib64/libm.so.6 (0x00007f41c0572000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f41c035c000)
libc.so.6 => /lib64/libc.so.6 (0x00007f41bff8e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f41c0b7c000)

链接静态库

TARGET_LINK_LIBRARIES(main libhello.a)

特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

注意:这两个是环境变量而不是 cmake 变量,可以在linux的bash中进行设置

我们上面例子中使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)来指明include路径的位置

我们还可以使用另外一种方式,使用环境变量export CMAKE_INCLUDE_PATH=/usr/include/hello

补充:生产debug版本的方法:
cmake .. -DCMAKE_BUILD_TYPE=debug

遇到的cmake知识补充

FIND_PACKAGE:搜包命令,找到系统目录下该包的头文件和库文件,并将其加入cmake变量

cmake_minimum_required(VERSION 3.16):指定cmake版本,可以指定最高版本和最低版本

ctest:cmake的测试工具,主要由enable_test add_test语句组成,使用ctest命令执行测试。

1.enable_testing()

​ 使用 enable_testing() 命令可以启用测试。一定要在根目录下的 CMakeLists.txt 中开启,不然执行 make test 时会报错。

2.add_test()

1
2
3
4
add_test(NAME <name> COMMAND <command> [<arg>...]
[CONFIGURATIONS <config>...]
[WORKING_DIRECTORY <dir>]
[COMMAND_EXPAND_LISTS])

add_test 用来添加新测试:

  • NAME:指定本测试的名称,可以随意命名。
  • COMMAND:指定测试命令行。如果 指定了一个可执行目标(由 add_executable()创建),它将自动替换为生成时创建的可执行文件的位置。
  • CONFIGURATIONS :Debug/Release 选项可以控制在不同的编译版本下是否进行测试。
  • WORKING_DIRECTORY:指定要在其中执行测试的工作目录。如果未指定,测试将在当前工作目录设置为与当前源目录对应的生成目录的情况下运行。
  • COMMAND_EXPAND_LISTS:3.16 版新增。命令参数中的列表将展开,包括使用生成器表达式创建的列表。
1
2
3
4
add_test(
NAME mytest
COMMAND $<TARGET_FILE:mytest> #<TARGET_FILE:mytest> 指代mytest这个目标的完整路径。
)

​ $<TARGET_FILE:mytest> 是生成器表达式,它是在生成构建系统生成时的表达式。生成器表达式在测试时非常方便,因为不必显式地将可执行程序的位置和名称,可以硬编码到测试中。

3.ctest 命令

​ CTest 遵循的标准约定是:返回零意味着成功,非零返回意味着失败。可以返回零或非零的脚本都可以做测试用例。(Windows 端使用 VS 编译时要保证安装 “用于 Windows 的 C++ CMake 工具”)

使用 ctest 命令时,必须加上编译类型 -C 参数 ,才可以正确执行。默认情况下只有 Passed/Failed 提示信息,此外,我们可以添加以下命令:

  • –output-on-failure:将测试程序生成的任何内容打印到屏幕上,以免测试失败。
  • -v:将启用测试的详细输出。
  • -vv:启用更详细的输出。

cmake的基本参数和用法_亚古兽要进化的博客-CSDN博客_cmake参数