CMake2: CMakeLists.txt的常用命令

参考链接:

爱编程的大丙 | CMake教程
CMakeLists指令以及常用方法
现代 CMake 教程

文章目录

  • [1. cmake_minimum_required( )](#1. cmake_minimum_required( ))
  • [2. project( )](#2. project( ))
  • [3. add_executable( )](#3. add_executable( ))
  • [4. set()](#4. set())
  • [5. aux_source_directory( )](#5. aux_source_directory( ))
  • [6. file( )](#6. file( ))
  • [7. include_directories( )](#7. include_directories( ))
  • [8. add_library( )](#8. add_library( ))
  • [9. link_libraries()与link_directories()](#9. link_libraries()与link_directories())
  • [10. target_link_libraries( )](#10. target_link_libraries( ))
  • [11. message()](#11. message())
  • [12. list()](#12. list())
  • [13. add_definitions()](#13. add_definitions())
  • [14. find_package()](#14. find_package())
  • [15. install( )](#15. install( ))
  • [16. add_subdirectory( )](#16. add_subdirectory( ))
  • [17. target_include_directories()](#17. target_include_directories())

1. cmake_minimum_required( )

用于规定cmake的最低版本(可选,非必须,如果不加可能会有警告)

cmake 复制代码
cmake_minimum_required(VERSION 2.8)

2. project( )

定义工程名称,并可指定工程的版本、工程描述、web 主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

语法:

plain 复制代码
project(<PROJECT-NAME> )  
project(<PROJECT-NAME>
       [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
       [DESCRIPTION <project-description-string>]
       [HOMEPAGE_URL <url-string>]
       [LANGUAGES <language-name>...])

一般仅会用到一个参数,如:

cmake 复制代码
project(myslam)

3. add_executable( )

工程会生成一个可执行程序,语法:

plain 复制代码
add_executable(可执行程序名 源文件名称)

例子,生成的可执行文件名称为myslam

cmake 复制代码
add_executable(myslam  main.cpp) 

4. set()

在cmake中使用set来定义变量,变量的值均为字符串类型,同时该命令也可用于拼接字符串,语法是:

plain 复制代码
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
  • VAR:变量名
  • VALUE:变量值

取出变量的值时使用${ VAR }进行

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

# 方式2: 各个源文件之间使用分号 ; 间隔
# 多次对同一变量set以最后一次为准
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app  ${SRC_LIST})

常用场景:

(1) 用于指定C++标准

C++标准在cmake中对应的宏叫做DCMAKE_CXX_STANDARD。同时,在CMake要指定C++标准有两种方式:

  • 在 CMakeLists.txt 中通过 set 命令指定
cmake 复制代码
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
  • 在执行 cmake 命令的时候指定出这个宏的值
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

# build目录下
cmake .. -DCMAKE_CXX_STANDARD=11

注:-D表示指定宏名,其紧接着宏名并进行赋值

(2)用于指定输出的路径

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

cmake 复制代码
set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 第一行:定义一个变量用于存储一个绝对路径
  • 第二行:将拼接好的路径值设置给EXECUTABLE_OUTPUT_PATH宏

注: 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建;并且由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录,即build目录;故输出路径一般写成绝对路径

(3)拼接字符串

如果使用set进行字符串拼接,对应的命令格式如下:

plain 复制代码
set(变量名1 ${变量名1} ${变量名2} ...)

关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。

5. aux_source_directory( )

在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:

plain 复制代码
aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录
  • variable:将从dir目录下搜索到的源文件列表存储到该变量中
cmake 复制代码
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})

注:此处的宏PROJECT_SOURCE_DIR与CMAKE_CURRENT_SOURCE_DIR均为访问CMakeLists.txt所在的路径,即工程的根目录

6. file( )

在CMake中使用file命令完成搜索文件的任务

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

搜索当前目录的src目录下所有的源文件,并存储到变量中

cmake 复制代码
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
# 等价于 file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

7. include_directories( )

在编译项目源文件的时候,需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译,使用include_directories来包含头文件目录

plain 复制代码
include_directories(headpath)

例子,目录结构如下:

bash 复制代码
$ 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 复制代码
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})

8. add_library( )

有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,在cmake中使用add_library命令实现:

(1)生成静态库

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

plain 复制代码
add_library(库名称 STATIC 源文件1 [源文件2] ...) 

(2)生成动态库

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

plain 复制代码
add_library(库名称 SHARED 源文件1 [源文件2] ...) 

(3) 设置路径生成

由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

在cmake中,链接静态库的命令为link_libraries

plain 复制代码
link_libraries(<static lib> [<static lib>...])
  • 参数1:指定出要链接的静态库的名字。可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
  • 参数2-N:要链接的其它静态库的名字

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以使用link_directories命令指定静态库或动态库的路径

plain 复制代码
link_directories(<lib path>)

例子:

cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)

add_executable(app ${SRC_LIST})

在cmake中链接库文件的命令为target_link_libraries,该命令既可以链接动态库,也可以链接静态库文件。

plain 复制代码
target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

target:指定要加载动态库文件的名字。该文件可能是一个源文件;该文件可能是一个动态库文件;该文件可能是一个可执行文件。

PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC

  • PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
  • PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,其它库如果链接到该target,则无法获知private后面的库信息
cmake 复制代码
target_link_libraries(A B C)
target_link_libraries(D A)
#D 可访问 A B C

target_link_libraries(A private B C)
target_link_libraries(D A)
#D 可访问 A, 但 不可访问 B 和 C
  • INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号;即当前的target只知道链接到了某一函数,但不知道该函数来自哪个库

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

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

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

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后

11. message()

在CMake中可以用户显示一条消息,该命令的名字为message:

plain 复制代码
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 错误, 终止所有处理过程

CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。

CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。

注:该命令也可用于打印定义的变量信息,方便进行调试

12. list()

该指令用于操作字符串,由如下子属性以实现不同的字符串功能。

子命令 功能描述 编号
LENGTH 用于读取列表长度 (3)
GET 获取索引对应元素的子命令,可以同时指定多个索引 (4)
JOIN 连接列表中的元素,并在元素间添加连接符的子命令 (5)
SUBLIST 用于获取列表中的一部分(子列表) (15)
FIND 查找指定元素 (6)
APPEND 将数据追加到列表中,也可进行字符串拼接 (1)
INSERT 在指定位置将元素(一个或多个)插入到列表中 (7)
PREPEND 在列表的头部(索引为0处)插入(一个或多个)元素 (8)
POP_BACK 将列表中最后元素移除(可将移除元素存入变量) (9)
POP_FRONT 将列表中第一个元素移除(可将移除元素存入变量) (10)
REMOVE_ITEM 将指定的元素从列表中移除 (2)
REMOVE_AT 将指定索引的元素从列表中移除 (11)
REMOVE_DUPLICATES 移除列表中的重复元素 (12)
REVERSE 将整个列表反转 (13)
SORT 将整个列表元素排序 (14)

(1)字符串拼接

如果使用list进行字符串拼接,对应的命令格式如下:

plain 复制代码
list(APPEND <list> [<element> ...])
  • APPEND : 表示进行数据追加
  • list : 为列表
  • element : 为字符串,也可用${变量名}
cmake 复制代码
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}")

注意:在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde。

(2) 字符串删除

如果使用list移除指定的字符串,对应命令:

plain 复制代码
list(REMOVE_ITEM <list> <value> [<value> ...])
  • REMOVE_ITEM: 表示进行字符串移除
  • list : 为列表
  • value: 为字符串,也可用${变量名}
cmake 复制代码
cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
# 移除前日志
message(STATUS "message: ${SRC_1}")
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)
# 移除后日志
message(STATUS "message: ${SRC_1}")

值得注意的是,list指令移除的是一个元素,即用分号分隔的一个字符串,而不是元素的一部分。

(3)获取 list 的长度

plain 复制代码
list(LENGTH <list> <output variable>)
  • LENGTH:子命令LENGTH用于读取列表长度
  • list:当前操作的列表
  • output variable:新创建的变量,用于存储列表的长度,也是字符串类型

(4)读取列表中指定索引的的元素,可以指定多个索引

plain 复制代码
list(GET <list> <element index> [<element index> ...] <output variable>)
  • GET : 获取索引对应元素的子命令
  • list:当前操作的列表
  • element index:列表元素的索引。从0开始编号,索引0的元素为列表中的第一个元素;索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推;当索引(不管是正还是负)超过列表的长度,运行会报错
  • output variable:新创建的变量,存储指定索引元素的返回结果,也是一个列表。

(5)将列表中的元素用连接符(字符串)连接起来组成一个字符串

plain 复制代码
list (JOIN <list> <glue> <output variable>)
  • JOIN : 连接列表中的元素,并在元素间添加连接符的子命令
  • list :当前操作的列表
  • glue :指定的连接符(字符串)
  • output variable :新创建的变量,存储返回的字符串

(6)查找列表是否存在指定的元素,如果未找到,返回-1

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

(7)在list中指定的位置插入若干元素

plain 复制代码
list(INSERT <list> <element_index> <element> [<element> ...])
  • INSERT: 插入指定元素
  • list:当前操作的列表
  • element_index:列表中元素的索引;插入其element_index的前一索引之后,其后的元素往后移
  • element:插入的元素

(8)将元素插入到列表的0索引位置

plain 复制代码
list (PREPEND <list> [<element> ...])

(9)将列表中最后元素移除

plain 复制代码
list (POP_BACK <list> [<out-var>...])
  • list :欲移除最后元素的列表名称
  • out-var :输出变量

如果未指定,则仅仅只将原列表中最后一个元素移除;如果指定,会将列表最后一个元素赋值给输出变量,然后再将列表最后一个元素移除。

如果指定多个输出变量,则会依次将列表中最后一个元素赋值到输出变量中。

如果输出变量个数大于列表长度,则超出部分的输出变量未定义。

(10)将列表中第一个元素移除

plain 复制代码
list (POP_FRONT <list> [<out-var>...])
  • list :欲移除第一个元素的列表名称
  • out-var :输出变量

如果未指定,则仅仅只将列表中第一个元素移除;如果指定,会将列表第一个元素赋值给输出变量,然后再将列表第一个元素移除。

如果指定多个输出变量,则会依次将列表中第一个元素赋值到输出变量中。

如果输出变量个数大于列表长度,则超出部分的输出变量未定义。

(11)将指定索引的元素从列表中移除

plain 复制代码
list (REMOVE_AT <list> <index> [<index> ...])
  • list :欲移除元素的列表名称
  • index :指定的索引

当指定的索引不存在的时候,会提示错误;如果指定的索引存在重复,如 list(REMOVE_AT MYLIST 1 1),则只会执行一次移除动作。

(12)移除列表中的重复元素

plain 复制代码
list (REMOVE_DUPLICATES <list>)
  • list :欲移除元素的列表名称

如果列表中没有重复元素,则列表不会改变。

如果列表中所有元素都是一样的,则会保留一个。

如果有多个重复元素,则保留第一个。

(13)列表翻转

plain 复制代码
list(REVERSE <list>)
  • list :欲反转元素列表名称

(14)列表排序

plain 复制代码
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
  • list :欲排序列表名称
  • COMPARE :指定排序方法
    • SRING :按照字母顺序进行排序(默认)
    • FILE_BASENAME :如果是一系列路径名,会使用basename进行排序
    • NATURAL :使用自然数顺序排序
  • CASE :指明是否大小写敏感
    • SENSITIVE :按大小写敏感的方式进行排序(默认)
    • INSENSITIVE :按大小写不敏感方式进行排序
  • ORDER :排序的顺序
    • ASCENDING :按照升序排序(默认)
    • DESCENDING :按照降序排序

(15)获取列表中的一部分子列表

plain 复制代码
list(SUBLIST <list> <begin> <length> <out-var>)
  • list 为欲获取子列表的父列表名称;
  • begin 开始索引;
  • length 子列表长度。
  • out-var 为新创建的变量,用于存储获取结果子列表。

若length值为0,返回子列表为空列表;

若length值为-1 或 父列表长度小于begin+length值(即索引越界或超出长度),将会把父列表中从begin索引开始的所有剩余元素返回。

13. add_definitions()

使用该命令为程序添加宏定义:

plain 复制代码
add_definitions(-D宏名称)

例子:

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

可配合debug的日志文件使用,在编写C++程序时,可使用预处理命令判断宏是否存在来决定Debug的日志文件输出。

c++ 复制代码
#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("Debugging information...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

14. find_package()

plain 复制代码
find_package(<Package> [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
             [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])
  • <Package>:要查找的软件包的名称,例如 OpenCV、Boost 等。
  • version:可选参数,用于指定所需的软件包版本。
  • EXACT:可选参数,要求版本号完全匹配。
  • QUIET:可选参数,如果找不到软件包,则不产生错误。
  • MODULE:可选参数,指定使用模块方式查找软件包。
  • REQUIRED:可选参数,如果找不到软件包,则产生致命错误。
  • COMPONENTS:可选参数,指定要查找的软件包的组件。
  • OPTIONAL_COMPONENTS:可选参数,指定可选的组件。
  • NO_POLICY_SCOPE:可选参数,指定不将策略设置限定在调用的目录。

如果找到了相应的库,则会自动定义以下几个变量:

  • <LibraryName>_FOUND:

    • 这个变量用于指示是否找到了指定的库。

    • 如果找到了,这个变量的值为 TRUE;否则,值为 FALSE。

  • <LibraryName>_INCLUDE_DIR 或 <LibraryName>_INCLUDES:

    • 这个变量用于存储库的头文件所在目录的路径。

    • 如果库提供了多个头文件目录,可能是一个路径列表。

  • <LibraryName>_LIBRARY 或 <LibraryName>_LIBRARIES:

    • 这个变量用于存储库文件(通常是静态库或动态库)的路径或名称。

    • 如果库提供了多个库文件,可能是一个路径列表。

    • 在链接目标时,应使用这些变量指定要链接的库。

find_package具有两种模式,一种是Module模式,另一种叫做Config模式。

  • 在Module模式中,cmake需要找到一个叫做Find<LibraryName>.cmake的文件。这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。cmake搜索这个文件的路径有两个,一个cmake安装目录下的/usr/share/cmake-<version>/Modules目录,另一个使我们指定的CMAKE_MODULE_PATH的所在目录。

  • 在Config模式中,cmake主要通过<LibraryName>Config.cmake or <lower-case-package-name>-config.cmake这两个文件来引入我们需要的库。一般在/usr/local/lib/cmake可找到一些配置文件,有些库的config文件则在share中,如/usr/share/OpenCV/OpenCVConfig.cmake。

对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录,这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供find_package使用。而对于非由cmake编译的项目,我们通常会编写一个Find<LibraryName>.cmake,通过脚本来获取头文件、库文件等信息。通常,原生支持cmake的项目库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。

示例:

cmake 复制代码
cmake_minimum_required(VERSION 3.10)

project(Demo)

find_package(OpenCV REQUIRED)

add_executable(demo main.cpp)

target_link_libraries(
  demo
  PRIVATE ${OpenCV_LIBS}
)

15. install( )

该指令用于将生成的文件安装到系统目录

(1)安装目标库文件以及可执行文件

plain 复制代码
 install(TARGETS targets... [EXPORT <export-name>]
         [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
           PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
          [DESTINATION <dir>]
          [PERMISSIONS permissions...]
          [CONFIGURATIONS [Debug|Release|...]]
          [COMPONENT <component>]
          [NAMELINK_COMPONENT <component>]
          [OPTIONAL] [EXCLUDE_FROM_ALL]
          [NAMELINK_ONLY|NAMELINK_SKIP]
         ] [...]
         [INCLUDES DESTINATION [<dir> ...]]
  )
  • targets:指定要安装的目标名称。
  • EXPORT <export-name>:指定一个导出集,安装的目标将被添加到该集合中。
  • ARCHIVE\|LIBRARY\]:指定正在安装的目标的类型。 * 静态库: ARCHIVE * 动态库: LIBRARY * 可执行二进制文件: RUNTIME * 与库关联的PUBLIC头文件: PUBLIC_HEADER * 与库关联的PRIVATE头文件: PRIVATE_HEADER

  • PERMISSIONS:指定安装文件的权限;有效权限包括: OWNER_READ,OWNER_WRITE,OWNER_EXECUTE,GROUP_READ,GROUP_WRITE,GROUP_EXECUTE,WORLD_READ,WORLD_WRITE,WORLD_EXECUTE,SETUID和SETGID;
  • CONFIGURATIONS:指定安装规则适用的构建配置列表(DEBUG或RELEASE等);
  • COMPONENT:指定了该安装规则相关的一个安装部件的名字,如"runtime";
  • EXCLUDE_FROM_ALL:指定该文件从完整安装中排除,仅作为特定于组件的安装的一部分进行安装;
  • OPTIONAL:如果要安装的文件不存在,则指定不是错误。

使用案例:

cmake 复制代码
# 将 myslam 库安装到指定目录
install(TARGETS myslam
	    #安装动态库使用这行指令 DESTINATION指定安装目录
        LIBRARY DESTINATION /usr/local/lib
        #安装静态库使用这行指令
        ARCHIVE DESTINATION /usr/local/lib
)

(2)安装头文件目录或其它类型的文件目录

plain 复制代码
install(DIRECTORY dirs...
        TYPE <type> | DESTINATION <dir>
        [FILE_PERMISSIONS permissions...]
        [DIRECTORY_PERMISSIONS permissions...]
        [USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>] [EXCLUDE_FROM_ALL]
        [FILES_MATCHING]
        [[PATTERN <pattern> | REGEX <regex>]
        [EXCLUDE] [PERMISSIONS permissions...]] [...])
  • dirs:目录名。不以/结尾,包含目录本身,目录名以/结尾,目录下的内容,不包括目录本身。
  • DESTINATION <dir>:目录的安装位置
  • FILES_MATCHING:进行文件匹配,匹配方式由PATTERN|REGEX确定
  • PATTERN|REGEX:以精细的粒度控制目录的安装,可以指定一个通配模式或正则表达式对输入目录过滤;
  • PERMISSIONS:覆盖匹配文件或目录的权限设置。
cmake 复制代码
# 将 include 目录下的所有头文件安装到指定目录
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/
        DESTINATION /usr/local/include/myslam
        # 匹配${PROJECT_SOURCE_DIR}/include 下的所有.h文件进行安装
        FILES_MATCHING PATTERN "*.h")

16. add_subdirectory( )

使用add_subdirectory命令添加子目录,执行子目录下的CMakeLists.txt

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

例子:

bash 复制代码
$ tree
.
├── build
├── CMakeLists.txt
├── include
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
└── test
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files

根目录下的CMakeLists.txt

cmake 复制代码
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(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME test)
# 添加子目录
add_subdirectory(sort)
add_subdirectory(test)

sort子目录下的CMakeLists.txt

cmake 复制代码
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})

test子目录下的CMakeLists.txt

cmake 复制代码
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} ${SRC})
target_link_libraries(${APP_NAME} ${SORT_LIB})

17. target_include_directories()

在 CMake 中,target_include_directories 是一个非常重要的命令,用于为特定目标(如可执行文件或库)指定头文件的搜索路径。它的主要作用是告诉编译器在编译该目标时应该从哪些目录中查找头文件。基本语法:

plain 复制代码
target_include_directories(<target> [SYSTEM] [BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
  • <target>:指定要配置的目标名称(由 add_executable 或 add_library 创建的目标)
  • 作用域关键字(必须至少指定一个):
    • PRIVATE:头文件路径仅对当前目标的源文件可见,不会传递给依赖该目标的其他目标
    • PUBLIC:头文件路径对当前目标可见,同时也会传递给依赖该目标的其他目标
    • INTERFACE:头文件路径不用于当前目标,仅传递给依赖该目标的其他目标
  • 其他可选参数:
    • SYSTEM:将目录标记为系统目录,编译器可能会对系统目录下的头文件采取不同的处理方式(如忽略某些警告)
    • BEFORE:使指定的目录在已有的包含目录之前被搜索

示例:

cmake 复制代码
# 创建一个库目标
add_library(my_lib src/my_lib.cpp)

# 为库目标指定头文件目录
target_include_directories(my_lib
  PUBLIC
  	# 生成器表达式(generator expressions),马上讲解
    $<INSTALL_INTERFACE:include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# 创建可执行文件目标,依赖于my_lib
add_executable(my_app src/main.cpp)

# 为可执行文件指定头文件目录
target_include_directories(my_app
  PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# 链接库
target_link_libraries(my_app PRIVATE my_lib)

生成器表达式(generator expressions)

这是一种特殊语法,用于在构建时根据不同条件(如构建阶段、目标类型等)动态指定路径。

生成器表达式的格式是

plain 复制代码
 $<条件:内容>

它不会在 CMake 解析阶段(cmake ... 时)立即生效,而是在生成构建系统(如 Makefile 或 Visual Studio 项目)时才确定最终值。

示例中用到了两个最常用的生成器表达式:

  • $<BUILD_INTERFACE:路径>
    • 含义:仅在「构建当前项目时」生效的路径。
    • 示例中 ${CMAKE_CURRENT_SOURCE_DIR}/include 是当前项目源码中的 include 目录(比如 project/include)。
    • 作用:编译 my_lib 自身或依赖它的本地目标(如同一项目中的可执行文件)时,编译器会从这个目录找头文件。
  • $<INSTALL_INTERFACE:include>
    • 含义:仅在「安装项目后」生效的路径。
    • 示例中 include 是安装后的相对路径(比如最终会被安装到 /usr/local/include 或 C:\Program Files\my_lib\include)。
    • 作用:当其他人安装你的库后,用它来编译自己的项目时,编译器会从安装目录的 include 文件夹找头文件。
相关推荐
risc-v@cn3 小时前
【在ubuntu下使用vscode打开c++的make项目及编译调试】
c++·vscode·ubuntu
让我们一起加油好吗3 小时前
【C++】多态(详解)
c++·visualstudio·多态·虚函数
草莓熊Lotso3 小时前
【C++】--函数参数传递:传值与传引用的深度解析
c语言·开发语言·c++·其他·算法
zylyehuo3 小时前
C++提高编程
c++
scx201310044 小时前
20250822 组题总结
c++·算法
云边有个稻草人5 小时前
【C++】第二十五节—C++11 (上) | 详解列表初始化+右值引用和移动语义
c++·c++11·右值引用·移动语义·列表初始化·移动构造·移动赋值
源代码•宸6 小时前
网络流量分析——基础知识(二)(Tcpdump 基础知识)
运维·开发语言·网络·c++·经验分享·tcpdump
johnZhangqi13 小时前
深圳大学-计算机信息管理课程实验 C++ 自考模拟题
java·开发语言·c++
StudyWinter14 小时前
【C++】仿函数和回调函数
开发语言·c++·回调函数·仿函数