CMake详细解读

原文来自:CMake 保姆级教程

视频来自B站:CMake 保姆级教程C/C++

1、快速操作:

原文来自:在 VScode 中使用 CMake 快速创建cpp工程

首先创建一个 C/C++ 工程文件夹 CALC,用 VSCode 打开,目录结构如下:

  1. 使用快捷键 Ctrl+Shift+P 打开vscode 的控制面板。输入 CMake:quick start,或选择如下:
    请添加图片描述

  2. 输入项目名称:CALC
    请添加图片描述

  3. 选择创建 C++ 项目

    请添加图片描述

  4. 选择创建可执行文件
    在这里插入图片描述

  5. 最后直接 ok

  6. 添加对应的加减乘除文件

    1、add.c

    #include <stdio.h>
    #include "head.h"
    
    int add(int a, int b)
    {
        return a+b;
    }
    

    2、sub.c

    #include <stdio.h>
    #include "head.h"
    
    // 你好
    int subtract(int a, int b)
    {
        return a-b;
    }
    

    3、mult.c

    #include <stdio.h>
    #include "head.h"
    
    int multiply(int a, int b)
    {
        return a*b;
    }
    

    4、div.c

    #include <stdio.h>
    #include "head.h"
    
    double divide(int a, int b)
    {
        return (double)a/b;
    }
    

    5、head.h

    #ifndef _HEAD_H
    #define _HEAD_H
    // 加法
    int add(int a, int b);
    // 减法
    int subtract(int a, int b);
    // 乘法
    int multiply(int a, int b);
    // 除法
    double divide(int a, int b);
    #endif
    

    6、main.c

    #include <stdio.h>
    #include "head.h"
    
    int main()
    {
        int a = 20;
        int b = 12;
        printf("a = %d, b = %d\n", a, b);
        printf("a + b = %d\n", add(a, b));
        printf("a - b = %d\n", subtract(a, b));
        printf("a * b = %d\n", multiply(a, b));
        printf("a / b = %f\n", divide(a, b));
        return 0;
    }
    
  7. 修改 CMakeLists.txt 中的可执行文件

    cmake_minimum_required(VERSION 3.0.0)
    project(CALC VERSION 0.1.0 LANGUAGES C CXX)
    
    add_executable(CALC main.c add.c sub.c mult.c div.c)
    
  8. 直接运行即可得到以下结果:
    在这里插入图片描述

2、CMake 的使用

1、注释

注释行

CMake 使用 # 进行行注释

# 这是一个 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.0)
注释块

CMake 使用 #[[ ]] 形式进行块注释

#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)

2、案例引入

准备工作

为了方便测试,在我本地电脑准备了这么几个测试文件

  • add.c

    #include <stdio.h>
    #include "head.h"
    
    int add(int a, int b)
    {
        return a+b;
    }
    
  • sub.c

    #include <stdio.h>
    #include "head.h"
    
    // 你好
    int subtract(int a, int b)
    {
        return a-b;
    }
    
  • mult.c

    #include <stdio.h>
    #include "head.h"
    
    int multiply(int a, int b)
    {
        return a*b;
    }
    
  • div.c

    #include <stdio.h>
    #include "head.h"
    
    double divide(int a, int b)
    {
        return (double)a/b;
    }
    
  • head.h

    #ifndef _HEAD_H
    #define _HEAD_H
    // 加法
    int add(int a, int b);
    // 减法
    int subtract(int a, int b);
    // 乘法
    int multiply(int a, int b);
    // 除法
    double divide(int a, int b);
    #endif
    
  • main.c

    #include <stdio.h>
    #include "head.h"
    
    int main()
    {
        int a = 20;
        int b = 12;
        printf("a = %d, b = %d\n", a, b);
        printf("a + b = %d\n", add(a, b));
        printf("a - b = %d\n", subtract(a, b));
        printf("a * b = %d\n", multiply(a, b));
        printf("a / b = %f\n", divide(a, b));
        return 0;
    }
    
上述文件的目录结构如下:
$ tree
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c
添加 CMakeLists.txt 文件
# 指定使用的 cmake 的最低版本
cmake_minimum_required(VERSION 3.0.0)
# 定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 定义工程会生成一个可执行程序
# 格式:add_executable(可执行程序名 源文件名称),也可用“;”分隔
add_executable(CALC main.c add.c sub.c mult.c div.c)
执行CMake 命令
# cmake 命令原型
$ cmake CMakeLists.txt文件所在路径
$ tree
.
├── add.c
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

0 directories, 7 files
$ cmake .
$ make

最终可执行程序 CALC 就被编译出来了(这个名字是在CMakeLists.txt中指定的)。

规范管理项目生成的目录和文件
$ mkdir build
$ cd build
$ cmake ..

现在 cmake 命令是在 build 目录中执行的,但是 CMakeLists.txt 文件是 build 目录的上一级目录中,所以 cmake 命令后指定的路径为..,即当前目录的上一级目录。

tips:

  1. 若终端路径与 CMakeLists.txt 文件所在路径一致,则执行 cmake .
  2. 若终端路径为 CMakeLists.txt 文件所在路径的下一级,则执行 cmake ..

最终效果:

$tree -L 1
.
├── add.c
├── calc
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── Makefile
├── mult.c
└── sub.c

1 directory, 11 files

3、CMake 语法

1、定义变量(set)

# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

其中,VAR 为变量名,VALUE 为变量值

简化上面的 CMakeLists.txt 的部分内容:

set(SRC_LIST add.c  div.c   main.c  mult.c  sub.c)
add_executable(CALC  ${SRC_LIST})

2、指定C++标准

法1:在 CMakeLists.txt 中通过 set 命令指定
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
法2:在执行 cmake 命令的时候指定出这个宏的值
#增加-std=c++11
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
#增加-std=c++14
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
#增加-std=c++17
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

3、指定输出的路径

在CMake中指定可执行程序输出的路径,也对应一个宏,叫做 EXECUTABLE_OUTPUT_PATH ,它的值还是通过 set 命令进行设置:

set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 第一行:定义一个变量用于存储一个绝对路径;
  • 第二行:将拼接好的路径值设置给 EXECUTABLE_OUTPUT_PATH 宏,如果这个路径中的子目录不存在,会自动生成,无需自己手动创建

示例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 指定可执行文件的输出目录
# ${CMAKE_CURRENT_SOURCE_DIR}为该 CMakeLists.txt 所在的目录
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 定义可执行文件
add_executable(calc main.c add.c sub.c mult.c div.c)

4、搜索文件

如果一个项目里边的源文件很多,在编写 CMakeLists.txt 文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在CMake中为我们提供了搜索文件的命令,可以使用 aux_source_directory 命令或者 file 命令。

法1:aux_source_directory

命令格式为:

aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录;
  • variable:将从 dir 目录下搜索到的源文件列表存储到该变量中。

举例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 定义可执行文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src src_path)
add_executable(calc ${src_path})

tips:如果报错找不到头文件,记得将头文件的路径添加到 CMakeLists.txt 中,如上面的第7行。

法2:file

命令格式为:

file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
  • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。

举例:

file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)

示例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
add_executable(calc ${src_path})

tips:这里为了方便寻找路径,CMake 提供了两个命令:PROJECT_SOURCE_DIRCMAKE_CURRENT_SOURCE_DIR

1、PROJECT_SOURCE_DIR

PROJECT_SOURCE_DIR 是指顶层CMakeLists.txt文件所在的目录。它通常表示整个项目的源代码的根目录。无论你在CMake构建过程中运行哪个CMakeLists.txt文件,PROJECT_SOURCE_DIR 都始终指向顶层的源目录。

举个例子,如果你的项目结构如下:

/path/to/project/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── include/
    └── CMakeLists.txt

/path/to/project/CMakeLists.txt 中和 /path/to/project/src/CMakeLists.txt 中,PROJECT_SOURCE_DIR 都会指向 /path/to/project

2、CMAKE_CURRENT_SOURCE_DIR

CMAKE_CURRENT_SOURCE_DIR 是指当前处理的 CMakeLists.txt 文件所在的目录。每当 CMake 处理一个 CMakeLists.txt 文件时,CMAKE_CURRENT_SOURCE_DIR 会更新为这个文件所在的目录。

使用同样的项目结构,在 /path/to/project/CMakeLists.txt 中,CMAKE_CURRENT_SOURCE_DIR 会是 /path/to/project,而在 /path/to/project/src/CMakeLists.txt 中,CMAKE_CURRENT_SOURCE_DIR 会是 /path/to/project/src

总结
  1. PROJECT_SOURCE_DIR 始终指向项目的顶层源目录。
  2. CMAKE_CURRENT_SOURCE_DIR 指向当前处理的 CMakeLists.txt 文件所在的目录。
    这两个变量在配置项目路径、引用文件和组织CMake逻辑时非常有用,特别是在处理具有多个子目录的复杂项目时。
示例

假设你在顶层CMakeLists.txt文件中写:

message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")

并在 /path/to/project/src/CMakeLists.txt 文件中写:

message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")

然后运行 CMake 配置,你将看到类似如下的输出:

PROJECT_SOURCE_DIR: /path/to/project
CMAKE_CURRENT_SOURCE_DIR: /path/to/project

PROJECT_SOURCE_DIR: /path/to/project
CMAKE_CURRENT_SOURCE_DIR: /path/to/project/src

这清晰地展示了这两个变量在不同上下文中的值。

5、包含头文件

在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在 CMake 中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是 include_directories:

include_directories(headpath)

举例说明,有源文件若干,其目录结构如下:

$ tree
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
└── src
    ├── add.cpp
    ├── div.cpp
    ├── main.cpp
    ├── mult.cpp
    └── sub.cpp

3 directories, 7 files

CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
add_executable(calc ${src_path})

其中,第七行指定就是头文件的路径,PROJECT_SOURCE_DIR 宏对应的值就是我们在使用 cmake 命令时,后面紧跟的目录,一般是工程的根目录。

6、制作动态库或静态库

满足用户自定义第三方库的需要:静态库和动态库。

制作静态库

命令格式:

add_library(库名称 STATIC 源文件1 [源文件2] ...) 

在Linux中,静态库名字分为三部分: l i b + 库名字 + . a lib + 库名字 + .a lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。(windows 中为 l i b + 库名字 + . l i b lib + 库名字 + .lib lib+库名字+.lib )

下面有一个目录(将 main.c 从 src 文件夹移出),需要将 src 目录中的源文件编译成静态库,然后再使用:

$tree -L 2
.
├── build
│   ├── bin
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── head.h
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

5 directories, 10 files

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# 制作静态库
add_library(calc_static_lib STATIC ${src_path})

这样最终就会生成对应的静态库文件 libcalc.a

$tree -L 2
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── libcalc_static_lib.a
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── head.h
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

4 directories, 11 files
动态库

在cmake中,如果要制作动态库,需要使用的命令如下:

add_library(库名称 SHARED 源文件1 [源文件2] ...) 

在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。(windows 中为 l i b + 库名字 + . d l l lib+库名字+.dll lib+库名字+.dll )

在Windows中虽然库名和Linux格式不同,但也只需指定出名字即可。

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# 制作动态库
add_library(calc_shared_lib SHARED ${src_path})

这样最终就会生成对应的动态库文件libcalc.so

tips:生成所需的库文件需要包含头文件内容。

$ tree -L 2
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── libcalc_shared_lib.so
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── head.h
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

4 directories, 11 files
指定库文件的输出路径

命令格式:

set(LIBRARY_OUTPUT_PATH 文件存放位置)

示例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)

# 设置动态库的存放位置,注意是:LIBRARY_OUTPUT_PATH
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# 制作动态库
add_library(calc_shared_lib SHARED ${src_path})
$ tree -L 2
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   └── libcalc_shared_lib.so
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

5 directories, 11 files

tips:

  1. 静态库和动态库可以同时设置,但不能同名;
  2. 可通过 set_target_properties 来设置他们的名称相同。

7、包含库文件

链接静态库

在cmake中,链接静态库的命令如下:

link_libraries(<static lib> [<static lib>...])
  1. 参数 1:指定出要链接的静态库的名字,可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
  2. 参数 2 − N 2-N 2N:要链接的其它静态库的名字
$ tree -L 2
.
├── build
│   ├── bin
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   ├── libcalc_shared_lib.so
│   └── libcalc_static_lib.a
└── src
    └── main.c

6 directories, 8 files

示例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)

# 设置库文件的输出地址
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# 包含静态库路径
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)

# 链接静态库
link_libraries(calc_static_lib)

# 定义可执行文件
add_executable(calc ${src_path})

tips:在生成静态库后,选择删去了函数源文件(只保留了main.c),链接静态库时报错:找不到四个加减乘除函数源文件,但此时因为已经生成了静态库,所以不需要了,将制作静态库的语句注释即可。

链接动态库

在cmake中链接动态库的命令如下:

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target:指定要加载动态库的文件的名字

    • 该文件可能是一个源文件

    • 该文件可能是一个动态库文件

    • 该文件可能是一个可执行文件

  • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC一般情况下选取public即可

    • 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可

    • 动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。

      target_link_libraries(A B C)
      target_link_libraries(D A)
      
      • PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用
      • PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
      • INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

示例:

cmake_minimum_required(VERSION 3.0)
project(CALC VERSION 0.1.0 LANGUAGES C CXX)
# 指定C++标准
set(CMAKE_CXX_STANDARD 11)

# 包含头文件的路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin)

# 定义可执行文件
file(GLOB src_path ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)

# 设置库文件的输出地址
# set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 制作动态库
# add_library(calc_shared_lib SHARED ${src_path})

# 生成可执行文件
add_executable(calc ${src_path})
# 链接动态库
target_link_libraries(calc calc_shared_lib)

tips:与静态库同理,如果函数源文件被删除了,需要注释掉 cmake 中创建动态库的相关语句。

链接动态库和链接静态库的区别

动态库的链接和静态库是完全不同的:

  • 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
  • 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

8、日志

命令格式:

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
  • (无) :重要消息
  • STATUS :非重要消息
  • WARNING:CMake 警告, 会继续执行
  • AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
  • SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
  • FATAL_ERROR:CMake 错误, 终止所有处理过程

示例:

# message 消息
# 重要消息
message("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# 非重要消息
message(STATUS "111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# 错误消息,立即终止
# message(FATAL_ERROR "222xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")

9、变量操作

追加
法1:set

命令格式:

set(变量名1 ${变量名1} ${变量名2} ...)

示例:

cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")
法2:list

命令格式:

list(APPEND <list> [<element> ...])

APPEND 表示进行数据追加,后边的参数和 set 就一样。

cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

list 还可以实现字符串移除、字符串反转、字符串排序等

10、宏定义

在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:

#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("我是一个程序猿, 我不会爬树...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

在程序的第七行对 DEBUG 宏进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。

为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在 gcc/g++ 命令中去指定,如下:

$ gcc test.c -DDEBUG -o app

在 gcc/g++ 命令中通过参数 -D 指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为DEBUG

CMake 中我们也可以做类似的事情,对应的命令叫做 add_definitions:

add_definitions(-D宏名称)

针对于上面的源文件编写一个CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 3.0)
project(TEST)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./test.c)

通过这种方式,上述代码中的第八行日志就能够被输出出来了。

11、嵌套的CMake

有如下目录结构:

$ tree
.
├── build
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files
添加子目录

命令格式:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:指定了CMakeLists.txt 源文件和代码文件的位置,其实就是指定子目录
  • binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
  • EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
示例

根目录中的 CMakeLists.txt 文件内容如下:定义全局变量和添加子目录

cmake_minimum_required(VERSION 3.0)
project(test)
# 定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

calc 目录中的 CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})

sort 目录中的 CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})

test1 目录中的 CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_NAME_1} ${SRC})

test2 目录中的 CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
link_directories(${LIB_PATH})
add_executable(${APP_NAME_2} ${SRC})
target_link_libraries(${APP_NAME_2} ${SORT_LIB})
静态库链接静态库

比如:sort中链接calc静态库

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
# 格式如下:直接添加要链接的静态库即可
link_libraries(${CALC_LIB})
# 如果是第三方的库,则需要指出路径
link_directories(${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})
静态库链接动态库

1、首先要将需链接的库设为动态库:

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} SHARED ${SRC})

2、在静态库中链接该动态库:

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
# 设置动态库的地址
link_directories(${LIB_PATH})
# 静态库链接动态库
add_library(${SORT_LIB} STATIC ${SRC})
# d库链接动态库
# add_library(${SORT_LIB} SHARED ${SRC})
# 链接动态库
target_link_libraries(${SORTLIB} ${CALCLIB})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/696197.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索软件工程师在新能源汽车研发中的角色与贡献

随着全球对可持续发展的关注不断增加&#xff0c;新能源汽车的研发与应用成为了汽车行业的一个重要方向。作为软件工程师&#xff0c;参与新能源汽车研发不仅能够推动科技创新&#xff0c;还能为环保事业贡献力量。本文将深入探讨软件工程师在新能源汽车研发中的具体贡献、所需…

如何画系统架构图学习

原文链接:https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E4%BB%8E%200%20%E5%BC%80%E5%A7%8B%E5%AD%A6%E6%9E%B6%E6%9E%84/51%20%E5%A6%82%E4%BD%95%E7%94%BB%E5%87%BA%E4%BC%98%E7%A7%80%E7%9A%84%E8%BD%AF%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E5%9B%BE%EF…

C语言学习系列:初识C语言

前言&#xff0c;C语言是什么 语言&#xff0c;比如中文、英语、法语、德语等&#xff0c;是人与人交流的工具。 C语言也是语言&#xff0c;不过是一种特殊的语言&#xff0c;是人与计算机交流的工具。 为什么叫C语言呢&#xff1f; 这就要从C语言的历史说起了。 一&#…

RAG vs Fine-Tuning 微调哪种大模型(LLM)技术更好?

数据科学和机器学习的研究人员和从业者都在不断探索创新策略来增强语言模型的能力。在众多方法中&#xff0c;出现了两种突出的技术&#xff0c;即检索增强生成 (RAG)和微调。本文旨在探讨模型性能的重要性以及 RAG 和微调策略的比较分析。 模型性能在 NLP 中的重要性 增强用…

[数据集][图像分类]黑色素瘤分类数据集10015张7类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;10015 分类类别数&#xff1a;7 类别名称:[“0”,“1”,“2”,“3”,“4”,…

Java学习 - MyBatis - 入门实例详解

前言 在上一篇文章中&#xff0c;我们讨论了持久化的概念&#xff0c;并简要介绍了 MyBatis。今天我们将深入到 MyBatis 的实际应用中&#xff0c;通过创建一个入门实例来展示如何使用 MyBatis 执行基本的 CRUD&#xff08;创建、读取、更新、删除&#xff09;操作。这个过程将…

demo xshell (程序替换 工作目录 内建命令)

1.程序替换 在学习完一些列的进程替换接口之后我们大概就能知道&#xff0c;我们的环境变量以及命令行参数是如何传递给子进程的&#xff0c;这些参数是我们在调用进程替换时就传给了子进程的数据。 那么如果我们自己要实现一个简单的命令行解释器&#xff0c;我们是不是首先…

记录某书请求返回406及响应{“code“:-1,“success“:false}

今天测试某个平台的爬虫时使用requests post请求正常写了个测试脚本把各种参数带上出来以后出现了406情况&#xff0c;和网站数据是完全一样的 以为是 X-S、X-T参接不对&#xff0c;但在postman里测试又是可以的成功&#xff0c;以为是检验了参数顺序&#xff0c;测试发现也没…

Ubuntu 24.04 LTS 安装配置 MySQL Community Server 8.4.0 LTS

1 安装 Apt Repository ​​​​​​​地址MySQL :: Download MySQL APT Repository sudo dpkg -i mysql-apt-config_0.8.30-1_all.deb #安装mysql 8.4 lts sudo apt update sudo apt-get install mysql-server #修改mysql root密码策略 2 查看版本 testtest:~$ mysqld --v…

mysql 数据库datetime 类型,转换为DO里面的long类型后,只剩下年了,没有了月和日

解决方法也简单&#xff1a; 自定义个一个 Date2LongTypeHandler <resultMap id"BeanResult" type"XXXX.XXXXDO"><result column"gmt_create" property"gmtCreate" jdbcType"DATE" javaType"java.lang.Long&…

【内存管理】页表映射

页表的一些术语 现在Linux内核中支持四级页表的映射&#xff0c;我们先看下内核中关于页表的一些术语&#xff1a; 全局目录项&#xff0c;PGD&#xff08;Page Global Directory&#xff09; 上级目录项&#xff0c;PUD&#xff08;Page Upper Directory&#xff09; 中间目…

2024 AEE | 风丘科技将亮相日本爱知国际会展中心——共同创造!

2024年名古屋汽车工程博览会&#xff08;Automotive Engineering Exposition 2024 NAGOYA&#xff09;将于7月17-19日在日本爱知县国际展示场&#xff08;Aichi Sky Expo&#xff09;开展。本展会是专门为活跃在汽车行业的工程师和研究人员举办的汽车技术展览&#xff0c;汇聚了…

React保姆级教学

React保姆级教学 一、创建第一个react项目二、JSX基本语法与react基础知识1、 插值语法&#xff1a;2、 循环一个简单列表3、 实现简单条件渲染4、 实现复杂的条件渲染5、 事件绑定6、 基础组件&#xff08;函数组件&#xff09;7、 使用useState8、 基础样式控制9、 动态类名1…

ui自动化中,selenium进行元素定位,以及CSS,xpath定位总结

几种定位方式 简单代码 from selenium import webdriver import time# 创建浏览器驱动对象 from selenium.webdriver.common.by import Bydriver webdriver.Chrome() # 参数写浏览器驱动文件的路径&#xff0c;若配置到环境变量就不用写了 # 访问网址 driver.get…

JDBC简介以及快速入门

这些都是JDBC提供的API 简介 每一个数据库的底层细节都不一样 不可能用一套代码操作所有数据库 我们通过JDBC可以操作所有的数据库 JDBC是一套接口 我们自己定义了实现类 定义实现类 然后就能用Java操作自己的数据库了 MySQL对于JDBC的实现类 就是驱动 快速入门 创建新的项…

冯喜运:6.10周一黄金还会再次拉升吗?日内黄金原油操作策略

【黄金消息面分析】&#xff1a;周一(6月10日)亚市盘中&#xff0c;现货黄金交在上周五暴跌后仍然承压&#xff0c;目前金价位于2294美元/盎司左右。因强劲非农数据刺激美元大涨&#xff0c;现货黄金上周五出现暴跌。此外&#xff0c;上周五数据显示&#xff0c;最大黄金消费国…

Duck Bro的第512天创作纪念日

Tips&#xff1a;发布的文章将会展示至 里程碑专区 &#xff0c;也可以在 专区 内查看其他创作者的纪念日文章 我的创作纪念日第512天 文章目录 我的创作纪念日第512天一、与CSDN平台的相遇1. 为什么在CSDN这个平台进行创作&#xff1f;2. 创作这些文章是为了赚钱吗&#xff1f…

基于运动控制卡的圆柱坐标机械臂设计

1 方案简介 介绍一种基于运动控制卡制作一款scara圆柱坐标的机械臂设计方案&#xff0c;该方案控制器用运动控制卡制作一台三轴机械臂&#xff0c;用于自动抓取和放料操作。 2 组成部分 该机械臂的组成部分有研华运动控制卡&#xff0c;触摸屏&#xff0c;三轴圆柱坐标的平面运…

MySQL时间和日期类型详解(零基础入门篇)

目录 1. DATE 2. DATETIME 3. TIMESTAMP 4. TIME 5. YEAR 6. 日期和时间的使用示例 以下SQL语句的测试可以使用命令行&#xff0c;或是使用SQL工具比如MySQL Workbench或SQLynx等。 在 MySQL 中&#xff0c;时间和日期数据类型用于存储与时间相关的数据&#xff0c;如何…

【西瓜书】大题

1.线性回归 思路&#xff1a;ywxb&#xff0c;w为一维数组&#xff0c;求均方误差MSE&#xff0c;对w和b分别求偏导为0得到关于w和b的闭式求解。预测第十年的代入ywxb求解即可。 2.查准率、查全率 思路&#xff1a;先计算每个算法测试结果的混淆矩阵&#xff0c;再根据混淆矩阵…