CMake使用

学习日记

Posted by BY Bigboss on December 30, 2023

CMake使用

1.概述

CMake 是一个项目构建工具,并且是跨平台的。自己手动写Makefile的工作量很大,解决依赖关系的时候也容易出错,CMake的优点是根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具,其编译流程如下图:

pFi6ahn.png

2.使用

2.1注释

  1. 单行注释使用#

  2. 多行注释使用#[[ ]]

2.2项目流程

  1. 添加CMakeList.txt

  2. 执行CMake命令,执行命令之后会出现很多CMake相关的文件,因此一般是在工程文件目录下建一个build文件,然后在build文件下使用cmake后面加..表示编译上一级的文件,最终生成的相关文件都会出现在build文件目录下

    cmake 命令原型

    cmake CMakeLists.txt文件所在路径

  3. 在生成makefile文件后,此时再执行make命令,就可以对项目进行构建得到所需的可执行程序了。

3.语法

3.1 注释

# CMake中,#用于注释,#[[]]用于块注释
cmake_minimum_required(VERSION 3.0.0) #表示指定CMake的最低版本,可选,非必需,如果不加可能会有警告

3.2 project

# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
       [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
       [DESCRIPTION <project-description-string>]
       [HOMEPAGE_URL <url-string>]
       [LANGUAGES <language-name>...])
#project(my_project)

3.3 生成可执行程序

add_executable(可执行程序名 源文件名称)
#add_executable(app 1.c 2.c 3.c)  或者  add_executable(app 1.c;2.c;3.c)

3.4 变量的设置

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

# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c  div.c   main.c  mult.c  sub.c)

# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app  ${SRC_LIST})

3.5 指定编译的标准

#指定编译命令中需要使用哪个标准
$ g++ *.cpp -std=c++11 -o app
#在CMake中有两种方式可以指定标准
#1.在CMakeLiast.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.6 指定输出的路径

#通过set命令设置 EXECUTABLE_OUTPUT_PATH 的值即可
set(HOME /home/bigboss)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)

3.7 搜索文件

#方式1.使用 aux_source_directory 命令
aux_source_directory(< dir > < variable >)
# dir:要搜索的目录 variable:将从dir目录下搜索到的源文件列表存储到该变量中
# 例子:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app  ${SRC_LIST})

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

#搜索当前目录的src目录下所有的源文件,并存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")
#CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。
#关于要搜索的文件路径和类型可加双引号,也可不加

3.8 包含头文件

include_directories(headpath)
#例子:
cmake_minimum_required(VERSION 3.0)
project(CALC)
set(CMAKE_CXX_STANDARD 11)
set(HOME /home/robin/Linux/calc)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin/)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
add_executable(app  ${SRC_LIST})
#其中,第六行指定就是头文件的路径,PROJECT_SOURCE_DIR宏对应的值就是我们在使用cmake命令时,后面紧跟的目录,一般是工程的根目录。

3.9 制作静态库和动态库

3.9.1 制作静态库

add_library(库名称 STATIC 源文件1 [源文件2] ...) 
#linux中,静态库的名字格式是 lib+名字+.a,此处就是写入名字,自动补充其余部分

3.9.2 制作动态库

add_library(库名称 SHARED 源文件1 [源文件2] ...) 
#linux中,静态库的名字格式是 lib+名字+.so,此处就是写入名字,自动补充其余部分

3.9.3 指定输出的路径

3.9.3.1 EXECUTABLE_OUTPUT_PATH
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# Linux下生成的动态库文件是默认有执行权限的,因此可以使用此条语句,适用于动态库的生成,不适用于静态库的生成
3.9.3.2 LIBRARY_OUTPUT_PATH
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 这个宏对于静态库文件和动态库文件都是适用的

3.10 链接静态库和动态库

3.10.1 链接静态库

link_libraries(<static lib> [<static lib>...])
#[[
参数1:指定出要链接的静态库的名字(可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx)
参数2-N:要链接的其它静态库的名字
]]
#如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来
link_directories(<lib path>)

3.10.2 链接动态库

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
#target:指定要加载动态库的文件的名字,该文件可能是一个源文件,该文件可能是一个动态库文件,该文件可能是一个可执行文件
#PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC,动态库的链接具有传递

#由于静态库在生成可执行文件的链接阶段被打包到可执行文件中,所以可执行文件执行的时候,静态库就被加载到内存中了
#动态库是可执行文件启动后,调用了动态库中的函数的时候,动态库才会被加载到内存中

target_link_libraries(app pthread)
#app: 对应的是最终生成的可执行程序的名字 
#pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。

3.11 日志

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(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

3.12 变量操作

3.12.1 适用set拼接

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

3.12.2 list相关操作

  • 拼接

    list(APPEND <list> [<element> ...])
    
  • 字符串移除

    list(REMOVE_ITEM <list> <value> [<value> ...])
    
  • 获取字符串长度

    list(LENGTH <list> <output variable>)
    
    • :新创建的变量,用于存储列表的长度。
  • 读取列表中指定索引的元素,可以指定多个索引

    list(GET <list> <element index> [<element index> ...] <output variable>)
    
    • :列表元素的索引
      • 从0开始编号,索引0的元素为列表中的第一个元素
      • 索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
      • 当索引(不管是正还是负)超过列表的长度,运行会报错
    • :新创建的变量,存储指定索引元素的返回结果,也是一个列表。
  • 将列表中的元素用连接符(字符串)连接起来组成一个字符串

    list (JOIN <list> <glue> <output variable>)
    
    • :当前操作的列表
    • :指定的连接符(字符串)
    • :新创建的变量,存储返回的字符串
  • 查找列表是否存在指定的元素,若果未找到,返回-1

    list(FIND <list> <value> <output variable>)
    
    • :当前操作的列表
    • :需要再列表中搜索的元素
    • :新创建的变量
      • 如果列表中存在,那么返回在列表中的索引
      • 如果未找到则返回-1。
  • 将元素追加到列表中

    list (APPEND <list> [<element> ...])
    
  • 列表排序

    list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
    
    • COMPARE:指定排序方法。有如下几种值可选:
      • STRING:按照字母顺序进行排序,为默认的排序方法
      • FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
      • NATURAL:使用自然数顺序排序
    • CASE:指明是否大小写敏感。有如下几种值可选:
      • SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
      • INSENSITIVE:按照大小写不敏感方式进行排序
    • ORDER:指明排序的顺序。有如下几种值可选:
      • ASCENDING:按照升序排列,为默认值
      • DESCENDING:按照降序排列

3.13 宏定义

例子:

#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;
}

在用g++编译的时候

$ gcc test.c -DDEBUG -o app

在用CMake的时候

add_definitions(-D宏名称)

3.14 预定义宏

  • PROJECT_SOURCE_DIR : 使用cmake命令后紧跟的目录,一般是工程的根目录
  • PROJECT_BINARY_DIR : 执行cmake命令的目录
  • CMAKE_CURRENT_SOURCE_DIR : 当前处理的CMakeLists.txt所在的路径
  • CMAKE_CURRENT_BINARY_DIR : target 编译目录
  • EXECUTABLE_OUTPUT_PATH : 重新定义目标二进制可执行文件的存放位置
  • LIBRARY_OUTPUT_PATH : 重新定义目标链接库文件的存放位置
  • PROJECT_NAME : 返回通过PROJECT指令定义的项目名称
  • CMAKE_BINARY_DIR : 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

部分参考于https://subingwen.cn/cmake/CMake-primer/