最近研究Monado runtime和ORB_SLAM3源码
这两个工程源码都是在Linux环境下使用CMake进行工程构建,所以各级目录下的 CMakeLists.txt 是一个很好了解项目代码结构的途径,但是发现对 CMake 的语法较为欠缺,下面是收集整理的一些关于 CMake 的语法细节,以便以后查阅。
1.1 CMake 是什么:
(1).CMake是一个支持生成跨平台建构文件的工具
(2).CMake并不直接建构最终的软件,而是描述项目文件被编译的过程,生成标准的建构档(如 Unix 的 Makefile 或 VS 的 projects/workspaces),然后再以对应平台的建构方式使用。
1.2 CMake源文件:
(1).CMake编写的源⽂件以CMakeLists.txt 命名或以.cmake为扩展名
(2).CMake的源⽂件包括 命令和注释
(3).CMake源文件中所有有效的语句都是命令
可以是内置命令或者自定义的函数(function) 或 宏命令(macro)
(4).可以通过add_subdirectory()命令把子录的CMake源文件添加进来
1.3 CMake编译C/C++原理:
(1).CMake比Unix的make更为高级,使用起来要方便得多。
(2).终端cmake命令将CMakeLists.txt文件建构为make所需要的makefile文件,
最后用make命令编译源码生成可执行程序或共享库(so(shared object))
因此CMake在Linux终端执行步骤总的来说就两个:
1.cmake
2.make
(3).终端执行cmake后会生成很多编译中间文件以及makefile文件,
一般会新建一个build目录专门用来编译:
1.mkdir build
2.cd build
3.cmake ..
4.make
build的创建也可以在CMakeLists.txt中使用命令创建。
cmake指向CMakeLists.txt所在的目录,
cmake .. 表示当前CMakeLists.txt目录的上一级目录
对于一个庞大的工程,编写Makefile相当复杂,
有了CMake工具之后就可以读入所有源文件,自动生成Makefile等构建文件。
(1).单行注释:#注释内容
(2).多行注释:可以使用括号来实现多行注释:
#[[多行注释
多行注释
多行注释]]
(1).CMake中所有的变量都是string类型。
(2).set()/unset():声明/移除一个变量
(3).声明变量:set(变量名 变量值)
set(var 123)
(4).引用变量:${变量名}
${var}
(5).打印变量:message("变量名 = ${变量名}")
message("var = ${var}")
(1).列表也是字符串,可以把列表看做是一个特殊的变量,这个变量有多个值。
(2).语法格式:set(列表名 值1 值2 ... 值n) 或 set(列表名 “值1;值2;...值n”)
(3).声明列表:set(列表名 值1 值2 ... 值n) 或 set(列表名 “值1;值2;...值n”)
set(list_var 1 2 3 4 5) 或 set(list_var "1;2;3;4;5")
(4).引用列表:${列表名}
(5).打印列表:message("列表名 = ${列表名}")
message("list_var = ${list_var}")
(1).全局层:cache变量,在整个项目范围可见,
一般在set定义变量式,指定CACHE参数就能定义cache变量
(2).目录层:在当前⽬录CMakeLists.txt中定义,
以及在该文件包含的其他CMake源文件中定义的变量
(3).函数层:在命令函数中定义的变量,属于函数作用域内的变量
(1).操作符:
优先级: () > 一元 > 二元 > 逻辑
(3).条件命令 if():
语法格式:
if (表达式)
COMMAND(ARGS...)
elseif(表达式)
COMMAND(ARGS...)
else(表达式)
COMMAND(ARGS...)
endif(表达式)
示例:
set(if_tap OFF)
set(elseif_tap ON)
if(${if_tap})
message("if")
elseif(${elseif_tap})
message("elseif")
else(${if_tap})
message("else")
endif(${if_tap})
elseif和else部分是可选的, 也可以使⽤多个elseif部分
缩进和空格对语句的解析没有影响
(4).循环命令 while():
语法格式:
while(表达式)
COMMAND(ARGS...)
endwhile(表达式)
示例:
set(a "")
while(NOT a STREQUAL "xxx")
set(a "${a}x")
message("a = ${a}")
endwhile()
break() 可以跳出整个循环
continue() 可以跳出当前循环
(5).循环遍历 foreach():
语法格式:
foreach(循环变量 参数1 参数2... 参数N)
COMMAND(ARGS...)
endforeach(循环变量)
遍历RANGE:
#循环范围从start到stop,循环增量为step
foreach(循环变量 RANGE start stop step)
COMMAND(ARGS...)
endforeach(循环变量)
遍历LISTS:
foreach(循环遍历 IN LISTS 列表)
COMMAND(ARGS...)
endforeach(循环变量)
示例:
foreach(item 1 2 3)
message("item = ${item}")
endforeach(item)
#RANGE:RANGE 4 表示从0到4
foreach(item RANGE 4)
message("item = ${item}")
endforeach(item)
#RANGE:打印 1 3 5
foreach(item RANGE 1 5 2)
message("item = ${item}")
endforeach(item)
#LISTS:
set(list_var 1 2 3)
foreach(item IN LISTS list_var)
message("item = ${item}")
endforeach(item)
foreach也支持 break() 和 continue() 命令跳出循环
语法格式:
function(
COMMAND(ARGS...)
endfunction(
调用格式:
name(参数列表)
示例:
function(func x y z)
message("call function func")
message("x = ${x}")
message("y = ${y}")
message("z = ${z}")
# ARGC 内置变量 参数个数
message("ARGC = ${ARGC}")
# ARGVn 内置变量 第 n 个参数,从0开始
message("arg1 = ${ARGV0}")
message("arg2 = ${ARGV1}")
message("arg3 = ${ARGV2}")
# ARGV 内置变量 参数列表
message("all args = ${ARGV}")
endfunction(func)
调用:fun(1 2 3)
语法格式:
macro(
COMMAND(ARGS...)
endmacro(
调用格式:
name(实参列表)
示例:
marco(ma x y z)
message("call macro ma")
message("x = ${x}")
message("y = ${y}")
message("z = ${z}")
endmacro(ma)
调用:ma(1 2 3)
函数命令有自己的作用域
宏的作用域和调用者的作用域是一样的
CMake预设了一些常用变量,这些变量通常会在编写CMakeLists.txt文件时使用到:
CMAKE_MAJOR_VERSION:cmake 主版本号
CMAKE_MINOR_VERSION:cmake 次版本号
CMAKE_C_FLAGS:设置 C 编译选项
CMAKE_CXX_FLAGS:设置 C++ 编译选项
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录
CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt 所在路径
CMAKE_CURRENT_BINARY_DIR:目标文件编译目录
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置
UNIX:如果为真,表示为UNIX-like的系统,包括AppleOSX和CygWin
WIN32:如果为真,表示为 Windows 系统,包括 CygWin
APPLE:如果为真,表示为 Apple 系统
CMAKE_SIZEOF_VOID_P:表示void*的大小(例如为4或者8),可以使用其来判断当前构建为32位还是64位
CMAKE_CURRENT_LIST_DIR:表示正在处理的CMakeLists.txt文件所在目录的绝对路径
CMAKE_ARCHIVE_OUTPUT_DIRECTORY:用于设置ARCHIVE目标的输出路径
CMAKE_LIBRARY_OUTPUT_DIRECTORY:用于设置LIBRARY目标的输出路径
CMAKE_RUNTIME_OUTPUT_DIRECTORY:用于设置RUNTIME目标的输出路径
(1) project命令:
命令语法:project( [languageName1 languageName2 ...])
命令简述:用于指定项目的名称
使用范例:project(Main)
(2) cmake_minimum_required命令:
命令语法:cmake_minimum_requried(VERSION major[.minor[.patch)
命令简述:用于指定需要的CMake的最低版本
使用范例:cmake_minimum_requried(VERSION 2.8.3)
(3) aux_source_directory命令:
命令语法:aux_source_directory( )
命令简述:用于包含源文件目录,dir目录下的所有源文件的名字保存在变量variable中
使用范例:aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src DIR_SRCS)
(4) add_executable命令:
命令语法:add_executable( [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL] source1 source2 … sourceN)
命令简述:用于指定从一组源文件source1 source2 ... sourceN 编译出一个可执行文件且命名为name
使用范例:add_executable(Main $(DIR_SRCS))
(5) add_library命令:
命令语法:add_library([STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1source2 … sourceN)
命令简述:用于指定从一组源文件 source1 source2 ... sourceN编译出一个库文件且命名为name
使用范例:add_library(Lib $(DIR_SRCS))
(6) add_dependencies命令:
命令语法:add_dependencies(target-name depend-target1 depend-target2 …)
命令简述:用于指定某个目标(可执行文件或者库文件)依赖于其他的目标。
这里的目标必须是add_executable、add_library、add_custom_target命令创建的目标
(7) add_subdirectory命令:
命令语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
命令简述:用于添加一个需要进行构建的子目录
使用范例:add_subdirectory(Lib)
(8) target_link_libraries命令:
命令语法:target_link_libraries( [item1 [item2 […]]][[debug|optimized|general] ] …)
命令简述:用于指定target需要链接item1 item2 ...。这里target必须已经被创建,链接的item可以是已经存在的target(依赖关系会自动添加)
使用范例:target_link_libraries(Main Lib)
(9) set命令:
命令简述:用于设定变量 variable 的值为 value。如果指定了 CACHE 变量将被放入 Cache(缓存)中。
命令语法:set( [[CACHE [FORCE]] | PARENT_SCOPE])
使用范例:set(ProjectName Main)
(10) unset命令:
命令语法:unset( [CACHE])
命令简述:用于移除变量 variable。如果指定了 CACHE 变量将被从 Cache 中移除。
使用范例:unset(VAR CACHE)
(11) message命令:
命令语法:message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] “message todisplay”…)
命令简述:用于输出信息
使用范例:message(“Hello World”)
(12) include_directories命令:
命令语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
命令简述:用于设定目录,这些设定的目录将被编译器用来查找 include 文件
使用范例:include_directories(${PROJECT_SOURCE_DIR}/lib)
(13) find_path命令:
命令语法:find_path( name1 [path1 path2 …])
命令简述:用于查找包含文件name1的路径,如果找到则将路径保存在VAR中(此路径为一个绝对路径),如果没有找到则结果为-NOTFOUND.默认情况下,VAR会被保存在Cache中,这时候我们需要清除VAR才可以进行下一次查询(使用unset命令)
find_path(LUA_INCLUDE_PATH lua.h ${LUA_INCLUDE_FIND_PATH})
if(NOT LUA_INCLUDE_PATH)
message(SEND_ERROR "Header file lua.h not found")
endif()
(14) find_library命令:
命令语法:find_library( name1 [path1 path2 …])
命令简述:用于查找库文件 name1 的路径,如果找到则将路径保存在 VAR 中(此路径为一个绝对路径),
如果没有找到则结果为 -NOTFOUND。
一个类似的命令 link_directories 已经不太建议使用了
(15) add_definitions命令:
命令语法:add_definitions(-DFOO -DBAR …)
命令简述:用于添加编译器命令行标志(选项),通常的情况下我们使用其来添加预处理器定义
使用范例:add_definitions(-D_UNICODE -DUNICODE)
(16) file命令:
命令简述:此命令提供了丰富的文件和目录的相关操作(这里仅说一下比较常用的)
使用范例:
# 目录的遍历
# GLOB 用于产生一个文件(目录)路径列表并保存在variable 中
# 文件路径列表中的每个文件的文件名都能匹配globbing expressions(非正则表达式,但是类似)
# 如果指定了 RELATIVE 路径,那么返回的文件路径列表中的路径为相对于 RELATIVE 的路径
file(GLOB variable [RELATIVE path][globbing expressions]...)
# 获取当前目录下的所有的文件(目录)的路径并保存到 ALL_FILE_PATH 变量中
file(GLOB ALL_FILE_PATH ./*)
# 获取当前目录下的 .h 文件的文件名并保存到ALL_H_FILE 变量中
# 这里的变量CMAKE_CURRENT_LIST_DIR 表示正在处理的 CMakeLists.txt 文件的所在的目录的绝对路径(2.8.3 以及以后版本才支持)
11.1 基本流程
(1).需要编译的源文件
(2).编写CMakeLists.txt
(3).终端运行cmake命令(1.3中有讲到),由CMake根据CMakeLists.txt生成Makefile
(4).终端运行make命令(1.3中有讲到),由Make根据Makefile,调用gcc生成可执行文件
11.2 基础命令:
一个CMakeLists.txt的编写,如下几个基础命令是十分常用的,具体释义在上一节中已有讲到
(1).cmake_minimum_required(VERSION x.x.x):用于指定cmake所需最低版本
(2).project(Project) :用于指定项目名称
(3).include_directories() :用于包含头文件目录
(4).aux_source_directory(src dir_srcs):用于包含源文件目录
(5).set(TEST_MATH) :用于设置环境变量,编译用到的源文件全部都要放到这里
(6).add_executable(${PROJECT_NAME} ${TEST_MATH}):用于添加要编译的可执行文件
(7).target_link_libraries(${PROJECT_NAME} m):用于添加可执行文件所需要的库
11.3 Hello_CMake
(1).目录结构:
├── CMakeLists.txt #父目录的CMakeList.txt
├── main.cpp #源文件,包含main函数
├── sub #子目录
└── CMakeLists.txt #子目录的CMakeLists.txt
└── test.h #子目录头文件
└── test.cpp #子目录源文件
(2).源文件代码:
/hello_cmake/sub/test.h
#include
void test(std::string str);
/hello_cmake/sub/test.cpp
#include "test.h"
#include
void test(std::string str)
{
std::cout << str << std::endl;
}
/hello_cmake/main.cpp
#include "test.h"
#include
int main(int argc, char** argv)
{
std::cout << "In main..." << std::endl;
test("hello, world!");
return 0;
}
(3).CMakeLists.txt代码:
#/hello_cmake/sub/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2) #编译所需cmake最低版本号
project(sub)
add_library(sub test.cpp) #test.cpp被编译成名为sub的库,参看之前的函数释义
test.cpp会被编译成[STATIC|SHARED|MODULE]其中
一种类型的库,如果没有设置,则默认编程STATIC也就
是.a库
#/hello_cmake/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(test)
include_directories(sub) #包含目录sub
add_subdirectory(sub output) #指定需要进行构建的子目录sub ,并且子目录中编译输出在
output,如果不设置,默认会建一个build目录作为输出
当执行到add_subdirectory(xx)命令的时候会进入(xx)子
目录并执行其中的CMakeLists.txt文件
add_executable(test main.cpp) #main.cpp编译为执行文件test
target_link_libraries(test sub) #test可执行文件需要链接Lib库
(4).编译前后对比
编译前目录结构:
编译后目录结构:
11.4 target_link的三种属性
target_link_libraries(
target必须先由add_executable()或add_library()之类的命令创建,并且不能是别名(ALIAS)
target_link_libraries()有三种链接属性:
PUBLIC:target源文件和头文件中都包含target文件头
INTERFACE:只有target头文件中包含了item文件头
PRIVATE: 只有target源文件(例如cpp)中包含了item头文件
如果没有设置,默认是INTERFACE
CMake基本语法释义就先写到这里。
后续在实际开发过程中再遇到新的需要摸清的知识点,再进行添加。
参考文档:
https://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pcCmake语法详解 - 百度文库
https://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pchttps://baijiahao.baidu.com/s?id=1695087052957704420&wfr=spider&for=pc
(六) CMake基本语法_li三河的博客-CSDN博客
Cmake命令之add_subdirectory介绍 - 简书