参考文档《CMake+Pratice》
一、初识cmake
官网:www.cmake.org
优点:
1、开源代码,使用类BSD许可发布。
2、跨平台,并可以生成native编译配置文件,在linux/Unix平台,生成makefile,在苹果平台可以生成Xcode,在windows平台,可以生成MSVC的工程文件。
3、能够管理大型项目。
4、简化编译构建过程和编译过程。cmake的工具链:cmake+make。
5、高效率,因为cmake在工具链中没有libtool。
6、可扩展,可以为cmake编写特定功能的模块,扩展cmake功能。
缺点:
1、cmake只是看起来比较简单,使用并不简单;
2、每个项目使用一个CMakeLists.txt(每个目录一个),使用的是cmake语法。
3、cmake跟已有体系配合不是特别的理想,比如pkgconfig。
二、安装cmake
环境:Ubuntu 20.04 LTS
安装指令:
sudo apt install cmake #安装cmake
cmake --version #查看cmake版本
三、简单测试
- 在根目录新建两个文件,main.c和CMakeLists.txt
main.c文件
#include <stdio.h>
int main()
{
printf("this is test1... \n");
return 0;
}
CMakeLists.txt文件内容:
PROJECT(TEST1)
SET(SRC_LIST main.c)
MESSAGE(STAUS "This is BINARY dir" ${TEST1_BINARY_DIR})
MESSAGE(STAUS "This is SOURCE dir" ${TEST1_SOURCE_DIR})
ADD_EXECUTABLE(test1 ${SRC_LIST})
- 编译cmake文件
指令: cmake .
构建流程:
生成一系列的过程文件,包括CMakeFiles,cmake_install.cmake,CMakeCache.txt 和Makefile。
重点关注,生成了Makefile文件。
执行 make指令,编译出test1可执行文件
执行test1
四、cmake常用变量
4.1 CMAKE_BINARY_DIR
CMAKE_BINARY_DIR变量表示项目构建目录,即编译生成的可执行文件、库文件等文件所在的目录。在CMakeLists.txt文件中使用该变量可以方便地设置输出文件的路径。
4.2 CMAKE_SOURCE_DIR
CMAKE_SOURCE_DIR变量表示项目的顶层目录,即项目的根目录。在CMakeLists.txt文件中使用该变量可以方便地引用项目中的其他文件或目录。
4.3 CMAKE_CURRENT_SOURCE_DIR
CMAKE_CURRENT_SOURCE_DIR变量表示当前处理的CMakeLists.txt文件所在的目录。
4.4 CMAKE_CURRENT_BINARY_DIR
CMAKE_CURRENT_BINARY_DIR变量表示当前处理的CMakeLists.txt文件的构建目录。
4.5 CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS变量用于设置C++编译器的编译选项。可以通过该变量设置一些特定的编译选项,如优化级别、警告级别等。
4.6 CMAKE_EXE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS变量用于设置链接器的选项。可以通过该变量设置一些特定的链接选项,如链接器的库路径、链接的库文件等。
4.7 CMAKE_INSTALL_PREFIX
CMAKE_INSTALL_PREFIX变量用于设置安装目录的路径。在项目构建完成后,可以通过make install命令将生成的可执行文件、库文件等安装到指定的路径下。
五、常用指令
5.1 PROJECT指令
PROJECT(projectname [CXX] [C] [Java])
可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。
5.2 SET指令。
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
SET 指令可以用来显式的定义变量即可。比如用到的是SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c)。
5.3 MESSAGE指令。
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过。
SATUS,输出前缀为–的信息。
FATAL_ERROR,立即终止所有cmake 过程。
5.4 ADD_EXECUTABLE 指令。
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表。
在本例使用了${}来引用变量,这是cmake 的变量应用方式,但是,有一些例外,比如在IF 控制语句,变量是直接使用变量名引用。
5.5 cmake 调用环境变量的方式
使用$ENV{NAME}指令就可以调用系统的环境变量了。
比如:
MESSAGE(STATUS "HOME dir: $ENV{HOME}")
设置环境变量的方式是:
SET(ENV{变量名} 值)
(1)CMAKE_INCLUDE_CURRENT_DIR自动添加CMAKE_CURRENT_BINARY_DIR 和CMAKE_CURRENT_SOURCE_DIR 到当前处理的 CMakeLists.txt。相当于在每个CMakeLists.txt 加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})。
5.6 ADD_SUBDIRECTORY 指令:
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。
5.7 INCLUDE_DIRECTORIES指令
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面。
5.8 ADD_LIBRARY指令
使用该命令可以在Linux下生成(静态/动态)库so或者.a文件,Windows下就是dll与lib文件,它有两种命令格式
第一种格式:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
生成一个名为 < name > 的library,注意以下规则:
1. < name > 应该保证在一个项目中的唯一性。
2. 实际生成的library文件名是基于平台的约定规则,比如linux下的lib< name >.a, Windows下的< name >.lib等
3. STATIC,SHARED,MODULE用于指定创建的library类型。
STATIC库:生成obj文件后,将其链接成静态库,用于链接到其他targets。
SHARED库:生成obj文件后,将其链接成动态库,用于运行时加载。
MODULE库:不能链接到其他targets,但是可以用dlopen之类的方法在运行时动态加载。
如果没有明确指定要生成的library的类型到底是STATIC,SHARED还是MODULE。则查看BUILD_SHARED_LIBS变量,如果值为ON,则默认是SHARED,否则默认STATIC。
对于SHARED和MODULE类型的库,POSITION_INDEPENDENT_CODE属性自动置为ON。
4. EXCLUDE_FROM_ALL:表明该target是否从默认构建target中排除。
5. source参数可以使用generator表达式($ <…>)。
第二种格式:
生成一个obj文件对象,该对象库只编译源文件,但不链接。
add_library(<name> OBJECT [<source>...])
由add_library()或 add_executable()创建的目标可以使用$<TARGET_OBJECTS:name>这样的表达式作为源引用对象,其中,name是对象库的名称。格式如下:
add_library(... $<TARGET_OBJECTS:name> ...)
add_executable(... $<TARGET_OBJECTS:name> ...)
5.8.1 SET_TARGET_PROPERTIES指令
SET_TARGET_PROPERTIES基本语法:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
5.8.2 GET_TARGET_PROPERTY指令
与SET_TARGET_PROPERTY对应的指令是GET_TARGET_PROPERTY基本语法为:
GET_TARGET_PROPERTY(VAR target property)
5.9 为 target 添加共享库
LINK_DIRECTORIES 添加非标准的共享库搜索路径,语法是:
LINK_DIRECTORIES(directory1 directory2 ...)
TARGET_LINK_LIBRARIES 用来为target 添加需要链接的共享库,语法是:
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)
5.10 ADD_DEFINITIONS
向C/C++编译器添加-D 定义,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)
参数之间用空格分割。
如果你的代码中定义了
#ifdef ENABLE_DEBUG
// ...
#endif
这个代码块就会生效。
如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和CMAKE_CXX_FLAGS 变量设置。
5.11 ADD_DEPENDENCIES
定义target 依赖的其他target,确保在编译本target 之前,其他的target 已经被构建。
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
5.12 ADD_TEST 与ENABLE_TESTING 指令。
ENABLE_TESTING 指令用来控制 Makefile 是否构建test 目标,涉及工程所有目录。语法没有任何参数:
ENABLE_TESTING()
一般情况这个指令放在工程的主 CMakeLists.txt 中。
ADD_TEST 指令的语法是:
ADD_TEST(testname Exename arg1 arg2 ...)
testname 是自定义的test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的。
比如:
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING()
生成Makefile 后,就可以运行 make test 来执行测试了。
5.13 AUX_SOURCE_DIRECTORY
基本语法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前cmake 还不能自动发现新添加的源文件。
比如:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
也可以通过后面提到的FOREACH 指令来处理这个LIST
5.14 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)
5.15 INCLUDE 指令
用来载入CMakeLists.txt 文件,也用于载入预定义的cmake 模块:
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL 参数的作用是文件不存在也不会产生错误。
你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH 中搜索这个模块并载入。
5.16 FIND_指令
FIND_系列指令主要包含以下的命令:
(1) FIND_FILE(<VAR> name1 path1 path2 …)
VAR变量代表找到的文件全路径,包含文件名
(2) FIND_LIBRARY(<VAR> name1 path1 path2 …)
VAR变量表示找到的库全路径,包含库文件
(3) FIND_PATH(<VAR> name1 path1 path2 …)
VAR变量代表包含这个文件的路径。
(4) FIND_PROGRAM(<VAR> name1 path1 path2 …)
VAR变量代表包含这个程序的全路径。
(5)FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets …]])
用来有调用预定义在CAMEK_MODULE_PATH下的FIND<name>.cmake模块,你也可以自己定义FInd<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用,在后面的章节会详细介绍FIND_PACKAGE的使用方法和FIND模块的编写。
5.17 IF 指令
基本语法为:
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 或 <var>_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 时为真。
5.18 WHILE
WHILE 指令的语法是:
WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)
5.19 FOREACH
FOREACH 指令的使用方法有三种形式:
(1)列表:
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
像我们前面使用的AUX_SOURCE_DIRECTORY 的例子:
AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
(2)范围:
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从 0 到 total 以1为步进。
(3)范围和步进:
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从 start 开始到stop 结束,以step 为步进。
这个指令需要注意的是,遇到ENDFOREACH 指令,整个语句块才会得到真正的执行。
5.20 target_include_directorie
target_include_directories 是 CMake 中用于指定目标(target)的头文件搜索路径的命令。
它的语法如下:
target_include_directories(target
[SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
其中,target
是目标名称,可以是库或可执行文件的名称。
SYSTEM
表示这些头文件是系统头文件,BEFORE
表示这些路径将被添加到其他路径之前。INTERFACE
、PUBLIC
和 PRIVATE
是指定这些路径的可见性。它们的含义如下:
INTERFACE
:这些路径只会被添加到目标的接口中,不会被添加到目标自身的编译选项中。PUBLIC
:这些路径会被添加到目标自身的编译选项中,同时也会被添加到目标的接口中。PRIVATE
:这些路径只会被添加到目标自身的编译选项中,不会被添加到目标的接口中。
items
是头文件搜索路径,可以是绝对路径或相对路径。如果是相对路径,会相对于当前 CMakeLists.txt 文件所在的目录进行解析。
5.21 add_custom_command和add_custom_target
在很多时候,需要在cmake中创建一些目标,如clean、copy等等,这就需要通过add_custom_target来指定。同时,add_custom_command可以用来完成对add_custom_target生成的target的补充。
add_custom_target
add_custom_target(Name [ALL] [command1 [args1...]]
[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[JOB_POOL job_pool]
[VERBATIM] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS]
[SOURCES src1 [src2...]])
ALL:表明该目标会被添加到默认的构建目标,使得它每次都被运行;
COMMAND:指定要在构建时执行的命令行;
DEPENDS:指定命令所依赖的文件;
COMMENT:在构建时执行命令之前显示给定消息;
WORKING_DIRECTORY:使用给定的当前工作目录执行命令。如果它是相对路径,它将相对于对应于当前源目录的构建树目录;
BYPRODUCTS:指定命令预期产生的文件。
add_custom_command
这种场景是:添加定制命令来生成文件。它的命令内容为:
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]``
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[BYPRODUCTS [files...]]
[IMPLICIT_DEPENDS <lang1> depend1
[<lang2> depend2] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[DEPFILE depfile]
[JOB_POOL job_pool]
[VERBATIM] [APPEND] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS])
OUTPUT:指定命令预期产生的输出文件。如果输出文件的名称是相对路径,即相对于当前的构建的源目录路径;
COMMAND:指定要在构建时执行的命令行;
DEPENDS:指定命令所依赖的文件;
COMMENT:在构建时执行命令之前显示给定消息;
WORKING_DIRECTORY:使用给定的当前工作目录执行命令。如果它是相对路径,它将相对于对应于当前源目录的构建树目录;
DEPFILE:为生成器指定一个.d depfile .d文件保存通常由自定义命令本身发出的依赖关系;
MAIN_DEPENDENCY:指定命令的主要输入源文件;
BYPRODUCTS:指定命令预期产生的文件。
5.22 add_dependencies
在顶级目标之间添加一个依赖项。
add_dependencies(<target> [<target-dependency>]...)
使顶级<目标>依赖于其他顶级目标,以确保它们在<目标>之前构建。顶级目标是由add_executable()、add_library()或add_custom_target()命令创建的。