(3/100) cmake指令详解

参考博客:
1. cmake核心知识点整理
2. cmake - 编译
3. CMake教程
4. Cmake使用教程(看这一篇就够了)(这篇详细的介绍了cmake的安装、创建和使用过程)
5. CMake 入门实战
6. CMake&CMakeList.txt

0、cmake基础知识

0.1 什么是cmake,与make区别

在各种开源项目中,经常会发现项目中除了代码源文件,还包含了 CMakeList.txt、 Makefile 文件,在项目的编译时候需要用到的命令有 cmake 、 make。我们本次想搞清楚他们之前的关系以及CMakeList的语法规则。

正常情况下,我们编写程序的大体流程为:
1)用编辑器(vim、emacs等)编写源代码文件(.h、.cpp等);
2)用编译器编译代码生成目标文件(.o等);
3)用链接器连接目标文件生成可执行文件(.exe等)。

一个程序在编写时,可能需要编写很多的代码文件,以及依赖很多第三方的库。在这种情况下,手动依次编译每个文件会变的非常麻烦,效率低下。

make 是一个自动化的批量编译工具,可以实现用一个命令构建整个工程的目的。但是其执行需要依赖一个规则文件,这个文件就是 MakefileMakefile 文件里详细描述了构建的细节(文件的依赖关系,编译的先后顺序等)。

对于一个大工程来说,编写 Makefile 文件也是一项非常复杂的事情。

cmake(Cross-platform Make)是一个可以自动生成 Makefile 文件的工具,当然它不只能生成 Makefile ,还能跨平台生成主流IDE(VS, xcode…)构建工程所需的 project 文件。 cmake 的执行同样需要依赖规则文件,这个文件就是 CMakeLists.txt

所以,为了能够把一堆c,cc,cpp,h,hpp代码变成可运行的库或者程序,核心还是Makefile,CMakefiles.txt纯粹是为了降低不懂Unix体系的人去编译C/C++代码做的一个自动系统和环境检测和设置工具,同时帮你生成Makefile。

在这里插入图片描述

0.2 语法特性介绍

基本语法格式:指令(参数 1 参数 2)
    参数使用括弧括起
    参数之间使用空格或分号分开

指令是大小写无关的,参数和变量是大小写相关的

set(HELLO hello.cpp)
add_executable(hello main.cpp hello.cpp)
ADD_EXECUTABLE(hello main.cpp ${HELLO})

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

1、指定编译器并同时设置编译选项

1.1 cmake_minimum_required - 指定CMake的最小版本要求

//cmake_minimum_required(VERSION versionNumber ) 
//CMake最小版本要求为2.8.3 
cmake_minimum_required(VERSION 2.8.3) 

1.2 project - 定义工程名称,并可指定工程支持的语言

//project(projectname [CXX] [C] [Java])
//指定工程名为HELLOWORLD
project(HELLOWORLD)

1.3 set - 显式的定义变量

//set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
//定义SRC变量,其值为main.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)
//设置输出文件位置
//设置运行时目标文件(exe、dll)的输出位置,原来的输出文件demo.exe是在build\Debug下,指定其生成路径改为build\exe\Debug下
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/exe)
message(${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
//设置存档目标文件(lib、a)的输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_CXX_COMPILER      "clang++" )         # 显示指定使用的C++编译器
set(CMAKE_CXX_FLAGS   "-std=c++11")             # c++11
set(CMAKE_CXX_FLAGS   "-g")                     # 调试信息
set(CMAKE_CXX_FLAGS   "-Wall")                  # 开启所有警告
set(CMAKE_CXX_FLAGS_DEBUG   "-O0" )             # 调试包不优化
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG " )   # release包优化

CMAKE_CXX_FLAGS设置的编译选项只会对g++有效,其他编译器不生效。 当然我们也可以通过add_compile_options()设置,但是通过add_compile_options会对所有编译器生效,如:

add_compile_options(-std=c++11) //在编译C代码时就会产生警告信息

2、常用指令

2.1 add_compile_options - 添加编译参数

语法:add_compile_options(
//添加编译参数 -Wall -std=c++11
add_compile_options(-Wall -std=c++11 -O2)

2.2 add_executable - 生成可执行文件

语法:add_executable(exename source1 source2 … sourceN)
//编译main.cpp生成可执行文件main
add_executable(main main.cpp)

2.3 option 定义一个开关,相当于1个bool值

# 语法:option(变量值 "帮助信息" [变量值])
# 作用:定义一个开关,相当于1个bool值
# 说明:变量值为 ONOFF ,默认为 OFF
//如果开关被打开,输出当前日期;否则不输出
option(DATE_ENABLE "version date" ON)
if(DATE_ENABLE)
    set(data "2022.09.09")
endif()

2.4 configure_file 将输入文件进行替换并生成输出文件

# 语法:configure_file(输入文件 输出文件)
# 作用:将输入文件进行替换并生成输出文件
# 说明:输入文件中形如 @VAR@ 或 ${VAR} 的字符串会被替换为这些变量的当前值,如果未定义则被替换为空字符串。
//添加config file,输入文件是config.h.in输出文件是config.h
configure_file(config.h.in config.h)

2.5 add_subdirectory 添加源文件目录

# 语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# 说明:source_dir指定源文件目录的位置,binary_dir 指定编译结果存放的位置,该参数可以不写。
# 作用:向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
//添加src子目录,src中需有一个CMakeLists.txt
add_subdirectory(src)

2.6 aux_source_directory - 发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表

 语法:aux_source_directory(dir VARIABLE)
//定义SRC变量,其值为当前目录下所有的源代码文件
aux_source_directory(. SRC)

2.7 include_directories 指定所有目标的头文件路径

# 语法:include_directories(dir1 [dir2 ...])
# 作用:指定所有目标的头文件添加路径。如果不写会导致无法找到头文件,编译无法过去。
# 说明:目录会被添加到当前文件的 INCLUDE_DIRECTORIES 属性中
//将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
include_directories(/usr/include/myincludefolder ./include)

2.8 target_include_directories 指定一个目标的头文件路径

# 语法:target_include_directories(target 名
					   <INTERFACE | PUBLIC | PRIVATE> [items1...]
					   <INTERFACE | PUBLIC | PRIVATE> [items2...] ...)
# 作用:指定一个目标的头文件路径。推荐使用target_include_directories,因为有些目标不需要添加路径,使用include_directories(dir1 [dir2 ...])会增加编译时间。
# 说明:目标文件有 INCLUDE_DIRECTORIESINTERFACE_INCLUDE_DIRECTORIES 两个属性:
	# ①INCLUDE_DIRECTORIES:对内头文件目录
	# ②INTERFACE_INCLUDE_DIRECTORIES,对外头文件目录
//指定目标的头文件路径
target_include_directories(HelloWorld PUBLIC "${PROJECT_BINARY_DIR}")

2.9 link_directories - 向工程添加多个特定的库文件搜索路径 —>相当于指定g++编译器的-L参

# 语法:**link_directories(dir1 dir2 …) **
//将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
link_directories(/usr/lib/mylibfolder ./lib)

2.10 message()输出某个信息:

//其中输出类型可选择:STATUS(输出前缀为-的信息),SEND_ERROR(产生错误),FATAL_ERROR(终止cmake过程)
message(STATUS "messages")  

2.11 file()对文件和文件夹的操作:比如搜索文件、打开文件、写入文件

# 语法:file(GLOB <variable> [LIST_DIRECTORIES true[false] [RELATIVE <path> ] [CONFIGURE_DEPENDS] [<globbing-expression> ...])
# 作用:主要用于匹配规则在指定的目录内匹配到所需要的文
# 说明:LIST_DIRECTORIES true[false]: 如果为false,目录将会被省略,默认情况下返回是带目录
		RELATIVE <path>: 相对路径<path> 返回的结果将不是绝对路径,而是将绝对路径中的<path>部分去掉,返回相对路径
		CONFIGURE_DEPENDS:如果该标记位设置,在主构建系统检查目标添加逻辑,必便在构建时重新运行标记的GLOB命令
		<globbing-expression>:匹配表达式,表达式类似与正则匹配
//寻找当前路径下的cpp文件,且返回的结果中为/public/home的相对路径
file(GLOB TEST_RESULT LIST_DIRECT true RELATIVE /public/home *.cpp)
//如果要创建文件夹路径:
file(MAKE_DIRECTORY ${xxx})   #创建某个路径文件夹

3、编译库文件

3.1 cmake可以通过add_library利用源文件生成动态和静态库文件,指令如下:

add_library(xxx1 SHARED xxx.cpp xxxx.cpp) ##命令根据xxx.cpp和xxxx.cpp生成动态库文件libxxx1.so
add_library(xxx2 STATIC xxx.cpp xxxx.cpp) ##命令根据xxx.cpp和xxxx.cpp生成静态库文件libxxx1.a
add_library(xxx3 MODULE xxx.cpp xxxx.cpp) ##命令根据xxx.cpp和xxxx.cpp生成中间文件libxxx3,该文件不会被加载到其他地方使用

注意:库名称不需要写前缀lib,系统会自动在给出的库名称前面加lib.

库文件生成后,我们需要对其属性进行设置,如重置库文件的名称,设置库文件的版本号等,这些需要通过set_target_properties命令实现:

// 创建动态库
add_library(algorithms SHARED ${src_path})  
// 创建动态链接库:库名称libalgorithms, SHARED表示为.so动态链接库,src_path是.cpp文件所在路径
// 创建静态库
set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)         
// 设置不清除同名动态库hello.so
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)  
// 设置不清除同名静态库hello.a
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")    
// 设置静态库的输出名称为hello,从而即使跟动态库重名也能实现
//设置动态库的版本号
set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1) 
//VERSION指代动态库版本,SOVERSION指代API版本
//注意:add_library除了可以生产库文件之外,还可以生成目标文件,但不打包成lib命令如下
add_library(objlib OBJECT <src>...)
//这种库只编译源文件生成目标文件,但是不把这些目标文件打包进一个lib。
//当其他的库或者目标文件要使用这些目标文件的时候,会以这样的形式来添加,objlib是这个库的名字
add_library(... $<TARGET_OBJECTS:objlib> ...)
add_executable(... $<TARGET_OBJECTS:objlib> ...)

3.2 使用已知路径外部现成的库文件

实测发现默认的.so路径只有一个/usr/lib,也就是库文件如果不在这个路径下则需要手动链接。
有两个语句都可以实现链接库文件:
link_libraries(path) 是在生成可执行文件之前就指定库文件路径(必须放在add_executable()的前边)。
target_link_libraries(main path) 是在生成target可执行文件之后链接(必须放在add_executable()的后边)
两者的区别就是在定义链接文件时 是在可执行文件生成之前还是之后,都可以实现功能。应尽可能首选target_link_libraries命令

# 语法:link_libraries([item1 [item2 [...]]]
                   [[debug|optimized|general] <item>] ...)
# 作用:用于将库链接到稍后添加的所有targets
# 说明:指定在通过诸如add_executable或add_library等命令链接稍后在当前目录或更低目录中创建的任何targets时要使用的库或标志。
add_library(add SHARED ${CMAKE_CURRENT_SOURCE_DIR}/source/add.cpp) #将会在build目录下生成libadd.so
add_library(subtraction SHARED ${CMAKE_CURRENT_SOURCE_DIR}/source/subtraction.cpp) #将会在build目录下生成libsubtraction.so
link_libraries(add subtraction) 
add_executable(sample_add ${CMAKE_CURRENT_SOURCE_DIR}/samples/sample_add.cpp)
add_library(libxxx4 <SHARED|STATIC|MODULE|UNKNOWN> IMPORTED)
//IMPORTED 表明此库在工程之外,是target_link_libraries的方便形式。
//外部库的详细信息通过set_target_properties设置
//其中最重要的就是 IMPORTED_LOCATION 属性,它指定外部库的位置。
add_library(libxxx4 STATIC IMPORTED)
set_target_properties(libxxx4 PROPERTIES IMPORTED_LOCATION /path/to/libboost_system.a) 
//libxxx4其实就是libboost_system.a
target_link_libraries(wang libxxx4)
//其实上述3条命令等价于
target_link_libraries(wang /path/to/libboost_system.a)

如果已知头文件库文件路径,可直接用target_include_directories()target_link_libraries()进行添加。
如果不知道头文件库文件路径,可以有另外2个方法: 一种采用find_package()相当于变量赋值然后用target_include_directories()target_link_libraries()设置即可;
另一种采用find_library()find_path()

3.3 寻找外部依赖库find_package()

在一个大型项目中,免不了需要导入很多外部依赖库,比如一个项目需要使用到opencv库,我们需要知道头文件的位置,库文件的位置以及库文件的名称,此时我们就需要find_package命令
find_package: 是一种查找头文件和库文件的方法,是针对第三方库的常用方法(比如CUDA/opencv)。

如果要使用find_package()查找第三方库的头文件和链接库文件路径:注意:采用find_package()命令cmake的模块**查找顺序**是:先在变量${CMAKE_MODULE_PATH}查找,然后在/usr/shared/cmake/Modules/里边查找。

find_package(CUDA REQUIRED)           # 查找某个第三方库的cmake module,比如CUDA代表的就是FindCUDA.cmake这个module
find_package(OpenCV REQUIRED)         # 多个库分别查找, 然后统一加到include_directories和link_libraries即可 
target_include_directories(tensorrt PUBLIC ${CUDA_INCLUDE_DIRS} ${TENSORRT_INCLUDE_DIR})
target_link_libraries(tensorrt ${CUDA_LIBRARIES} ${TENSORRT_LIBRARY} ${CUDA_CUBLAS_LIBRARIES} ${CUDA_cudart_static_LIBRARY} ${OpenCV_LIBS})

如果要查看cmake所自带支持的所有module和内容,就在如下路径中:

/usr/shared/cmake/Modules/    # 这个路径下所有FindXXXX.cmake都是cmake module文件(大部分是以Find开头,也有不是这么开头的)

如果要为自己写的库定义一个cmake module,则本质上就是先自己查找好头文件、库文件路径,然后欧放到某几个变量中。并且cmake统一规定这几个变量的写法:name_FOUND, name_INCLUDE_DIR, name_LIBRARY,从而只要知道某些库的module关键字(一般大写),然后运行find_package(关键字),然后就能得到两个变量:关键字_INCLUDE_DIR, 关键字_LIBRARY,从而就可以用include_directories(), link_libraries()进行设置了。

# 如下是一个名为FindTEST.cmake的module的写法: 关键字是TEST
# 先找到自己安装的头文件(test.h): 为了避免安装时prefix路径设置不同,
# 这里同时在两个默认存放头文件的路径寻找,一个是所有用户路径,一个是登录用户路径。
//表示寻找test.h文件,找到则把路径赋值给变量TEST_INCLUDE_DIR
find_path(TEST_INCLUDE_DIR test.h /usr/include/mytest      
        /usr/local/include/mytest)
                      
# 再找到自己安装的动态库(libmytest.so): 为了避免安装时prefix路径设置不同,这里也同时在两个路径搜索
//NAMES是关键字,表示寻找名称为mytest(实际名称libmytest.so)的头文件,PATH也是关键字,表示在接下来2个路径中寻找
find_library(TEST_LIBRARY NAMES mytest PATH /usr/lib       
        usr/local/lib)                         
 
if(TEST_INCLUDE_DIR AND TEST_LIBRARY)      # 如果找到则设置标志
    set(TEST_FOUND TRUE)
endif(TEST_INCLUDE_DIR AND TEST_LIBRARY)

3.4 find_library()find_path()查找库文件和头文件

# 语法:find_library(var name HINTS path PATH_SUFFIXES suff1 suff2)
# 作用:搜索名称为name的库文件(实际名称是libname.so),找到后存入var中,并可以带多个关键参数
# 说明:其中HINTS关键参数代表搜索路径,PATH_SUFFIXES代表路径后缀
find_library(_NVINFER_LIB nvinfer HINTS ${TRT_LIB} PATH_SUFFIXES lib lib64)
举例如下:
find_path(dbms_path
              NAMES db_xxx.h
              PATHS /home/dongfang/cmake_example/find_path
              DOC "this is a test for find_path")
查找路径PATH(/home/dongfang/cmake_example/find_path)下是否有db_xxx.h文件,如果有则将"/home/dongfang/cmake_example/find_path"存储在变量dbms_path中
举例如下:
find_library(dbms_library
                 NAMES libDBMS.so
                 PATHS /home/dongfang/cmake_example/find_path
                 DOC "this is a test for find_path")
查找路径PATH(/home/dongfang/cmake_example/find_path)下是否有libDBMS.so文件,如果有则将"/home/dongfang/cmake_example/find_path"存储在变量dbms_library中

在经过查找头文件和库文件路径之后,我们需要对下游负责,明确告知下游头文件路径和库文件路径是否准确找到,可使用如下命令:
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(DBMS dbms_path dbms_library) //如果dbms_path或者dbms_library无值,直接对下游报错

4、安装

不管是库文件还是目标文件,编出来之后,都需要将其放到一定的位置,方便其他目标文件使用,此时就需要install命令

install 可以生成的目标文件包括3种:RUNTIME是可执行文件, LIBRARY是动态库, ARCHIVE是静态库
可以指定安装路径,采用关键字DESTINATION接路径,注意如果是/开头的路径就是绝对路径,如果不是斜杠开头则默认基于CMAKE_INSTALL_PREFIX,也就是{CMAKE_INSTALL_PREFIX}/相对路径

**注意在clion中如果要安装,还需要手动进入项目cmake-build-debug文件夹,执行sudo make install执行,否则clion不会自动帮你安装。
// 安装bin/lib库文件或者头文件:
install(TARGETS MyLib
        EXPORT MyLibTargets 
        LIBRARY DESTINATION lib  # 动态库安装路径
        ARCHIVE DESTINATION libstatic  # 静态库安装路径
        RUNTIME DESTINATION bin  # 可执行文件安装路径
        PUBLIC_HEADER DESTINATION include  # 头文件安装路径
        )
//安装头文件:
install(FILES hello.h DESTINATION include/hello)  //安装头文件
// 安装sh头文件:
install(PROGRAMS runhello.sh DESTINATION bin)// 安装sh文件

install(TARGETS) 仅适用于使用 add_executable()add_library()创建的目标。对于使用自定义目标生成的文件,使用 install(FILES)

add_custom_target(
    main ALL
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/program
)
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/program
    COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/go.sh ${PROJECT_NAME}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/program DESTINATION /usr/local/lib/)

5、编译流程

5.1 构建流程

在 linux 平台下使用 CMake 构建C/C++工程的流程如下:

手动编写 CmakeLists.txt。
执行命令 cmake PATH生成 Makefile ( PATH 是顶层CMakeLists.txt 所在的目录 )。
执行命令make 进行编译。
// important tips
.       // 表示当前目录
./      // 表示当前目录
..      // 表示上级目录
../     // 表示上级目录

5.2 两种构建方式

内部构建(in-source build):不推荐使用

内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。

## 内部构建
//在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .
//执行make命令,生成target
make

外部构建(out-of-source build):推荐使用
将编译输出文件与源文件放到不同目录中

## 外部构建
//1. 在当前目录下,创建build文件夹
mkdir build 
//2. 进入到build文件夹
cd build
//3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件
cmake ..
//4. 执行make命令,生成target
make
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值