cmake详细教程(三)
创始人
2024-02-17 05:26:01

一、参考资料

二、CMake常用指令

add_compile_options()

功能:添加编译参数

语法:add_compile_options(编译参数)

# 添加编译参数 -wall -std=c++11
add_compile_options(-wall -std=c++11 -o2)

add_definitions()

功能:向C/C++编译器添加编译选项 -D

语法:add_definitions(-DENABLE_DEBUG -DABC),参数之间用空格分隔。

#ifdef ENABLE_DEBUG 
...
#endif

add_dependencies()

CMAKE之add_dependencies使用

问题引入

在项目中通常会遇见这样的情况:(例如一个项目中有:main,libhello.a, libworld.a),当项目过小的时候,编译顺序是*.a,然后是main,但是当一个项目的文件过于庞大,就会导致编译的顺序不会按照主CMAKE的 add_subdirectory 引入的先后顺序,为了解决这一问题,就需要使用 add_dependencies 进行依赖指定

简单示例

项目结构

├── CMakeLists.txt          // 下面用主CMAKE表示
├── hello
│   ├── CMakeLists.txt		// 下面用HELLOCMAKE表示
│   ├── hello.c
│   └── hello.h
├── main
│   ├── CMakeLists.txt		// 下面用MAINCMAKE表示
│   └── main.c
└── world├── CMakeLists.txt		// 下面用WORLDCMAKE表示├── world.c└── world.h

HELLOCMAKE的内容

cmake_minimum_required(VERSION 3.5.1)set(CMAKE_C_STANDARD 99)# 生成hello静态链接库
add_library(hello STATIC world.c hello.h)

MAINCMAKE的内容

cmake_minimum_required(VERSION 3.5.1)
project(CmakeDemo C)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY /home/lib)
set(CMAKE_C_STANDARD 99)# 生成CmakeDemo可执行文件
add_executable(CmakeDemo main.c)
link_directories(/home/lib)target_link_libraries(CmakeDemohelloworld
)

WORLDCMAKE的内容

cmake_minimum_required(VERSION 3.5.1)
set(CMAKE_C_STANDARD 99)# 生成world静态链接库
add_library(world STATIC world.c world.h)

主CMAKE的内容

cmake_minimum_required(VERSION 3.5)add_subdirectory(main)
add_subdirectory(hello)
add_subdirectory(world)add_dependencies(CmakeDemo hello world)

注意

  • 该示例中,只有在 主CMAKE 中使用到 add_dependencies()
  • add_dependencies() 中所填写的名称应该是其他 CMAKE 生成的名称。
  • 该示例中,如果写成 add_dependencies(CmakeDemo libhello.a libworld.a) ,则会报错。

总结

当构建一个项目时,由于依赖关系的存在,所以被依赖的项目总是最先构建,这样可避免出现"找不到库的错误"。

add_executable()

功能:生成可执行文件

语法:add_executable(exe文件名 source1 source2 .. sourceN)

# 编译main.cpp生成可执行文件main
add_executable(main main.cpp)

add_library()

功能:生成库文件,.so动态库、.a静态库、.o中间产物。

语法:add_library(库名 [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 .. sourceN)

# 生成 libmindsporelite.so 动态链接库
add_library(mindsporelite SHARED main.cc)# 生成 libmindsporelite.a 静态链接库
add_library(mindsporelite STATIC main.cc)# 生成 libmindsporelite.o 未被链接的中间产物
# 中间文件不能作为库或者单独执行
add_library(mindsporelite OBJECT main.cc)

aux_source_directory()

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

语法:aux_source_directory(文件夹路径 变量名)

# 遍历当前目录下的所有源代码文件,并将其添加到变量DIR_SRCS中
aux_source_directory(. DIR_SRCS)# 编译SRC变量所代表的源代码文件,生成main可执行文件
add_executable(main ${SRC})

add_subdirectory()

功能:向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。

语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

顶层CMakeLists.txt 通过 add_subdirectory(dir),指明本项目包含的子目录 ,子目录下的 CMakeLists.txt 文件和源代码也会被处理,通过多层的CmakeLists,一一构建各层的库和依赖。binary_dir 指定的目录存放输出文件,如果没有指定则使用source_dir

# 添加src子目录,src中需要有一个CMakeLists.txt
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)

cmake_minimum_required()

功能:指定CMake的最小版本要求。

语法:cmake_minimum_required(VERSION 版本号 [FATAL_ERROR])

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

find_指令

FIND_FILE( name1 path1 path2 …)

功能:VAR变量代表找到的文件全路径,包含文件名。

FIND_LIBRARY( name1 path1 path2 …)

功能:VAR变量表示找到的库文件的全路径,包含库文件。

FIND_PATH( name1 path1 path2 …)

功能:VAR变量代表包含这个文件的路径。

FIND_PROGRAM( name1 path1 path2 …)

功能:VAR变量代表包含这个工程的全路径。

find_package()

Cmake命令之find_package介绍

功能:本地导入外部库。用于查找包(通常是使用三方库),并返回关于包的细节(使用包所依赖的头文件、库文件、编译选项、链接选项等)。

语法:FIND_PACKAGE( [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets …]])

参数解释

  • REQUIRED 表示该库是必须的,如果找不到会报错;
  • COMPONENTS 用于检测该库的对应组件是否存在,如果不存在则认为找到的库不满足条件;
# find a boost install with the libraries filesystem and system
# 使用库文件系统和系统查找boost install
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)#这是第三方库,而不是自己生成的静态动态库
# check if boost was found
if(Boost_FOUND)message ("boost found")
else()message (FATAL_ERROR "Cannot find Boost")
endif()# Add an executable
add_executable(third_party_include main.cpp)# link against the boost libraries
target_link_libraries( third_party_includePRIVATEBoost::filesystem
)

上面的指令,使用 find_package 指令来在本地搜索对应的第三方库,Boost 代表需要查询的库名称;1.46.1 代表需要库的最低版本。

FetchContent

功能:外部导入外部库。FetchContent 是 3.11.0 版本开始提供的功能,只需要一个 URL 或者 Git 仓库即可引入一个库。

使用方法

  1. include(FetchContent) :表示引入 FetchContent。
  2. FetchContent_Declare(第三方库) :获取第三方库,可以是一个 URL 或者一个 Git 仓库。
  3. FetchContent_MakeAvailable(第三方库) :将这个第三方库引入项目。
  4. target_link_libraries(主项目 PRIVATE 子模块::子模块) :链接这个第三方库。
cmake_minimum_required(VERSION 3.14)
project(my_project)# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)include(FetchContent)
FetchContent_Declare(googletestURL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

FILE()

CMake file文件操作命令

功能:文件操作指令。

FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB variable [RELATIVE path] [globbing
expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path]
[globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)

include()

Load and run CMake code from a file or module.

功能:用来载入 CMakeLists.txt 文件,也用于载入预定义的cmake模块。

语法:include( [OPTIONAL] [RESULT_VARIABLE ] [NO_POLICY_SCOPE])

如果指定文件,则直接处理。如果指定module,则寻找 module.cmake 文件,首先在${CMAKE_MODULE_PATH} 中寻找,然后在CMake的module目录中查找。

include_directories()

功能:向工程添加多个特定的 头文件搜索路径,相当于指定g++编译器的 -I 参数。

语法:include_directories([AFTER|BEFORE][SYSTEM] dir1 dir2 ...)

# 包含所有子目录
aux_source_directory()# 包含指定目录
include_directories()# 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
include_directories(/usr/include/myincludefolder ./include)

LIST()

cmake命令之list

link_directories()(不推荐)

功能:向工程添加多个特定的 库文件搜索路径,相当于指定g++编译器的 -L 参数。

语法:link_directories(dir1 dir2 ...)

link_directories(/home/lib)
target_link_libraries(CmakeDemohelloworld
)# 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
link_directories(/usr/lib/mylibfolder ./lib)

注意:官方不建议使用该指令,取而代之的是 find_package()find_library()

Note that this command [link_directories] is rarely necessary. Library locations returned by find_package() and find_library() are absolute paths. Pass these absolute library file paths directly to the target_link_libraries() command. CMake will ensure the linker finds them.

MESSAGE()

功能:向终端输出用户定义的信息。

用法:MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message")

#产生错误,生成过程被跳过。
MESSAGE(SEND_ERROR "message")#输出前缀为--d的信息。
MESSAGE(STATUS "message")#立即终止所有的cmake过程
MESSAGE(FATAL_ERROR "message")

option()

Options that can be configured through environment variables or manually.(option通过配置环境变量或者手动设置)

对于跨平台的项目,我们往往要进行交叉编译,针对不同环境的代码和功能可能有所不同,我们往往通过option确定编译选项,不同平台编译的代码用宏进行隔离,同时编译宏也可以决定某部分代码是否编译,是否包含某个功能,对一些还不稳定的特性进行隔离

比如,在mindsporelilte中,通过设置MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL来确定是否编译OpenGL相关代码:

option(MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL "enable sharing memory with OpenGL" on)if(DEFINED ENV{MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL})set(MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL $ENV{MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL})
endif()if(MSLITE_ENABLE_SHARING_MEM_WITH_OPENGL)add_definitions(-DENABLE_OPENGL_TEXTURE)
endif()

在相关cpp文件中用宏ENABLE_OPENGL_TEXTURE隔离相关代码:

#ifdef ENABLE_OPENGL_TEXTUREif (!lite::opencl::LoadOpenGLLibrary(&gl_handle_)) {MS_LOG(ERROR) << "Load OpenGL symbols failed!";return RET_ERROR;}
#endif

project()

功能:定义工程名称,并可指定工程支持的语言。

语法:project(工程名称 [CXX] [C] [java])

# 指定工程名及版本号
project(HELLOWORLD VERSION 1.0)

set()

功能:显式的定义变量。

语法:set(变量名 [变量值] [CACHE TYPE DOCSTRING [FORCE]])

# 定义SRC变量,其值为main.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)# 指定C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 设置生成可执行文件的路径
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)# 设置生成链接库的路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

SET_TARGET_PROPERTIES()

功能:同时构建 libhello.solibhello.a 两个库。

# 动态库
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_PUTPUT 1)
# 静态库
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)# 增加动态库的版本号
# VERSION指代动态库版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES(hello PROPERTIES VERION 1.2 SOVERSION 1)

target_include_directories()

功能:为编译目标文件 指定头文件

语法:target_include_directories( [SYSTEM] [AFTER|BEFORE] [items1...] [ [items2...] ...])

必须是通过 add_executable()add_library() 创建,且不能是ALIAS目标

target_link_libraries()

功能:为target添加需要链接的共享库,相当于指定g++编译器 -l 参数。

语法:target_link_libraries(target library1 library2...)

target_link_libraries() 往往和 add_subdirectory() 一起使用,在编译好一个可执行文件或库时,如果它依赖其他库,可以使用 target_link_libraries() 链接其他库,方法如下:

# 生成动态库
add_library(mindsporelite SHARED $)
# 生成静态库
add_library(mindsporelite-static STATIC $)
# 生成可执行文件
add_executable(benchmark main.cc)# 动态链接,将 mindsporelite动态库文件链接到 benchmark可执行文件 
target_link_libraries(benchmark mindsporelite)
# 静态链接,将 mindsporelite-static静态库文件链接到 benchmark可执行文件 
target_link_libraries(benchmark mindsporelite-static)

控制指令

IF指令

总体把握一个原则,凡是出现 IF 的地方一定要有对应的ENDIF,出现 ELSEIF 的地方,ENDIF 是可选的。

IF(expression)
# THEN section.
COMMAND1(ARGS ...)COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression)
IF(var)#如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或_NOTFOUND 时,表达式为真。
IF(NOT var )#与上述条件相反。
IF(var1 AND var2)#当两个变量都为真是为真。
IF(var1 OR var2)#当两个变量其中一个为真时为真。
IF(COMMAND cmd)#当给定的 cmd 确实是命令并可以调用是为真。
IF(EXISTS dir)或者 IF(EXISTS file)#当目录名或者文件名存在时为真。
IF(file1 IS_NEWER_THAN file2)#当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。
IF(IS_DIRECTORY dirname)#当 dirname 是目录时,为真。
IF(variable MATCHES regex)
IF(string MATCHES regex)#当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如:
IF("hello" MATCHES "ell")MESSAGE("true")
ENDIF("hello" MATCHES "ell")# 数字比较表达式
IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)# 按照字母序的排列进行比较
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)IF(DEFINED variable)#如果变量被定义,为真。# 一个小例子,用来判断平台差异:
IF(WIN32)
MESSAGE(STATUS “This is windows.”)
# 作一些 Windows 相关的操作
ELSE(WIN32)
MESSAGE(STATUS “This is not windows”)
# 作一些非 Windows 相关的操作
ENDIF(WIN32)

上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,
ELSE(WIN32)之类的语句很容易引起歧义。可使用 CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 开关,避免歧义。

SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
IF(WIN32)
ELSE()
ENDIF()# 如果配合 ELSEIF 使用,可能的写法是这样:
IF(WIN32)
# do something related to WIN32
ELSEIF(UNIX)
# do something related to UNIX
ELSEIF(APPLE)
# do something related to APPLE
ENDIF(WIN32)

WHILE指令

其真假判断条件可以参考 IF 指令。

WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)

FOREACH指令

FOREACH 指令的使用方法有三种形式:

列表形式

FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)

范围形式

FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
#从 0 到 total 以1为步进举例如下:
FOREACH(VAR RANGE 10)MESSAGE(${VAR})
ENDFOREACH(VAR)

最终得到的输出是:
0 1 2 3 4 5 6 7 8 9
10

范围和步长

# 从 start 开始到 stop 结束,以 step 为步长
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)

最终得到的结果是:
5
8
11
14

三、CMake预定义变量

CMake中常用的预定义变量

BUILD_SHARED_LIBS

默认的库编译方式(shared or static),默认为static,一般在ADD_LIBRARY时直接指定编译库的类型。

CMAKE_C_FLAGS

功能:gcc编译选项。

SET(CMAKE_C_FLAGS_PUBLIC "-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4 -ffunction-sections -mno-unaligned-access -fno-aggressive-loop-optimizations -mapcs-frame -rdynamic")
SET(CMAKE_C_FLAGS_DEBUG "-Wall -ggdb3 -DNM_DEBUG ${CMAKE_C_FLAGS_PUBLIC}")
SET(CMAKE_C_FLAGS_RELEASE  "-Wall -O3  ${CMAKE_C_FLAGS_PUBLIC}")

CMAKE_CXX_FLAGS

功能:g++编译选项。

# 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
  • CMAKE_CXX_FLAGS_DEBUG: 设置编译类型为Debug时的编译选项;
  • CMAKE_CXX_FLAGS_RELEASE: 设置编译类型为Release时的编译选项;

CMAKE_CXX_COMPILER

设置C++编译器。

# 设置C++编译器为g++
set(CMAKE_CXX_COMPILER "g++")
# 设置标准库版本为c++17 并开启警告
set(CMAKE_CXX_FLAGS "-std=c++17 -Wall")
# 设置Debug模式下,不开启优化,开启调试,生成更详细的gdb调试信息
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb")
# 设置Release模式下,开启最高级优化
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

CMAKE_BUILD_TYPE

功能:编译类型(Debug, Release)。

# 设定编译类型为debug,因为在调试时需要选择debug
set(CMAKE_BUILD_TYPE Debug)
# 设定编译类型为release,因为在发布时需要选择release
set(CMAKE_BUILD_TYPE release)

CMAKE_CURRENT_SOURCE_DIR

当前CMakeLists.txt文件所在目录。

CMAKE_CURRENT_BINARY_DIR

编译目录,可使用ADD_SUBDIRECTORY来修改此变量。

# 添加cmake执行子目录
ADD_SUBDIRECTORY(example)

EXECUTABLE_OUTPUT_PATH

功能:可执行文件输出的存放路径。

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

LIBRARY_OUTPUT_PATH

功能:库文件输出的存放路径。

set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

PROJECT_NAME

功能:指定项目工程的名称。

project(Demo)

PROJECT_SOURCE_DIR

功能:项目工程的根目录,例如:/PATH/TO/CMakeDemo

PROJECT_BINARY_DIR

功能:执行cmake命令的编译路径,一般是在build目录,例如:即/PATH/TO/CMakeDemo/build

TOP_DIR

功能:项目工程的的根目录。

include(${TOP_DIR}/cmake/utils.cmake)

系统信息变量

root@root:~$ cmake --version
cmake version 3.11.2
  • CMAKE_MAJOR_VERSION: cmake的主版本号cmake version 3.11.2中的3
  • CMAKE_MINOR_VERSION: cmake的次版本号cmake version 3.11.2中的11
  • CMAKE_PATCH_VERSION: cmake的补丁等级cmake version 3.11.2中的2
  • CAMKE_SYSTEM。系统名称比如 LInux-2.6.22
  • CAMKE_SYSTEM_NAME,不包含版本的系统名,比如 linux
  • CMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22
  • CMAKE_SYSTEM_PROCESSOR,处理器的名称,比如 i686

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...