CMake学习笔记
一、 CMake基础知识
1.1 CMake编译原理
CMake是一种跨平台编译工具,比make更为高级,使用起来要方便得多。CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库(so(shared object))。因此CMake的编译基本就两个步骤:
1. cmake
2. make
CMake指向CMakeLists.txt所在的目录,例如cmake .. 表示CMakeLists.txt在当前目录的上一级目录。cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译,例如
mkdir build
cd build
cmake ..
make
make根据生成makefile文件,编译程序。
1.2 使用Cmake编译程序
我们编写一个关于C/C++程序项目,即hello world,以此理解整个CMake编译的过程。
1.1.1准备程序文件
假设文件层级目录结构如下:
├── build
├── CMakeLists.txt
├── include
│ └──helloworld.h
└── src
├──helloworld.c
└──main.c
头文件helloworld.h,如下所示:
#ifndef HELLOWORLD_H
#define HELLOWORLD_H
void hello_world ();
#endif
源文件helloworld.c,如下所示:
#include<stdio.h>
#include "../inc/helloworld.h"
void hello_world ()
{
printf("hello world");
}
main.c主函数,如下所示:
#include <stdio.h>
#include "../inc/helloworld.h"
int main(int argc, char** argv)
{
hello_world();
return 0;
}
1.1.2编写CMakeLists.txt
接下来编写CMakeLists.txt文件,该文件放在和src,include的同级目录,实际方哪里都可以,只要里面编写的路径能够正确指向就好了。CMakeLists.txt文件,如下所示: CMakeLists.txt主要包含以上的7个步骤,具体的意义,请阅读相应的注释。
#1.cmake verson,指定cmake版本
cmake_minimum_required(VERSION 3.2)
#2.project name,指定项目的名称,一般和项目的文件夹名称对应
# 这条指令会自动创建两个变量:
#<project name>_BINARY_DIR(二进制文件保存路径)
#<project name>_SOURCE_DIR(源代码路径)
#Cmake 系统也帮助我们预定义了
#PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR其值与上述对应相等
PROJECT(helloworld)
#3.head file path,头文件目录
INCLUDE_DIRECTORIES(include)
#4.source directory,源文件目录
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
#5.set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol"
SET(HELLOWORLD ${DIR_SRCS})
#6.add executable file,添加要编译的可执行文件
ADD_EXECUTABLE(${PROJECT_NAME} ${HELLOWORLD})
#7.add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称
TARGET_LINK_LIBRARIES(${PROJECT_NAME} m)
1.1.3编译和运行程序
准备好了以上的所有材料,接下来,就可以编译了,由于编译中出现许多中间的文件,因此最好新建一个独立的目录build,在该目录下进行编译,编译步骤如下所示:
mkdir build
cd build
cmake ..
make
操作后,在build目录下生成helloworld
二、 CMake常用指令总结
INCLUDE_DIRECTORIES( "dir1" "dir2" ... )
头文件路径,相当于编译器参数 -Idir1 -Idir2
LINK_DIRECTORIES("dir1" "dir2")
库文件路径。注意:由于历史原因,相对路径会原样传递给链接器。尽量使用FIND_LIBRARY而避免使用这个。
AUX_SOURCE_DIRECTORY( “sourcedir” variable)
收集目录中的文件名并赋值给变量
ADD_EXECUTABLE
可执行程序目标
ADD_LIBRARY
库目标
ADD_CUSTOM_TARGET
自定义目标
ADD_DEPENDENCIES( target1 t2 t3 )
目标target1依赖于t2 t3
ADD_DEFINITIONS( "-Wall -ansi")
本意是供设置 -D... /D... 等编译预处理需要的宏定义参数,对比 REMOVE_DEFINITIONS()
TARGET_LINK_LIBRARIES( target-name lib1 lib2 ...)
设置单个目标需要链接的库
LINK_LIBRARIES( lib1 lib2 ...)
设置所有目标需要链接的库
SET_TARGET_PROPERTIES( ... )
设置目标的属性 OUTPUT_NAME, VERSION, ....
MESSAGE(...)
用于打印信息
INSTALL( FILES “f1” “f2”DESTINATION . )
DESTINATION 相对于 ${CMAKE_INSTALL_PREFIX}
SET( VAR value [CACHE TYPE DOCSTRING [FORCE]])
设置变量值
LIST( APPEND|INSERT|LENGTH|GET| REMOVE_ITEM|REMOVE_AT|SORT ...)
列表操作
STRING( TOUPPER|TOLOWER|LENGTH| SUBSTRING|REPLACE|REGEX ...)
字符串操作
SEPARATE_ARGUMENTS( VAR )
转换空格分隔的字符串到列表
FILE( WRITE|READ|APPEND|GLOB| GLOB_RECURSE|REMOVE|MAKE_DIRECTORY ...)
文件操作
FIND_FILE
注意 CMAKE_INCLUDE_PATH
FIND_PATH
注意 CMAKE_INCLUDE_PATH
FIND_LIBRARY
注意 CMAKE_LIBRARY_PATH
FIND_PROGRAM
FIND_PACKAGE
注意 CMAKE_MODULE_PATH
EXEC_PROGRAM( bin [work_dir] ARGS <..> [OUTPUT_VARIABLE var] [RETURN_VALUE var] )
执行外部程序
OPTION( OPTION_VAR “description” [initial value] )
2.2.1 常用路径
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
这三个变量指代的内容是一致的,是工程顶层目录
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是in source编译,指得就是工程顶层目录,如果 是out-of-source编译,指的是工程编译发生的目录
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的CMakeLists.txt所在的路径
CMAKE_CURRRENT_BINARY_DIR
如果是in-source编译,它跟CMAKE_CURRENT_SOURCE_DIR一致,如果是out-of-source 编译,他指的是target编译目录
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的CMakeLists.txt的完整路径
CMAKE_BUILD_TYPE
控制 Debug 和 Release 模式的构建
CMAKE_INCLUDE_PATH
配合 FIND_FILE() 以及 FIND_PATH() 使用。如果头文件没有存放在常规路径(/usr/include, /usr/local/include等),则可以通过这些变量就行弥补。如果不使用 FIND_FILE 和 FIND_PATH的话,CMAKE_INCLUDE_PATH,没有任何作用。
CMAKE_LIBRARY_PATH
配合 FIND_LIBRARY() 使用。否则没有任何作用
CMAKE_MODULE_PATH
cmake 为上百个软件包提供了查找器(finder):FindXXXX.cmake
当使用非cmake自带的finder时,需要指定finder的路径,这就是CMAKE_MODULE_PATH,配合 FIND_PACKAGE()使用
CMAKE_INSTALL_PREFIX
控制make install是文件会安装到什么地方。默认定义是/usr/local 或 %PROGRAMFILES%
BUILD_SHARED_LIBS
如果不进行设置,使用ADD_LIBRARY且没有指定库类型,默认编译生成的库是静态库。
UNIX
在所有的类UNIX平台为TRUE,包括OS X和cygwin
WIN32
在所有的win32平台为TRUE,包括cygwin
这条指令会自动创建两个变量:
<projectname>_BINARY_DIR(二进制文件保存路径)
<projectname>_SOURCE_DIR(源代码路径)
cmake系统也帮助我们预定义了PROJECT_BINARY_DIR和
PROJECT_SOURCE_DIR其值与上述对应相等
- SET(变量名 变量值)
SET(VAR [VALUE] [CACHE TYPEDOCSTRING [FORCE]])
SET(SRC_LIST main.c t1.ct2.c)
SET(SRC_LIST main.c)
例:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)更改
生成的可执行文件路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)更改生成
的库文件路径
- MESSAGE
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display” …)
向终端输出用户定义的信息或变量的值
SEND_ERROR, 产生错误,生成过程被跳过
STATUS, 输出前缀为-的信息
FATAL_ERROR, 立即终止所有cmake过程
- ADD_EXECUTABLE
ADD_EXECUTABLE(可执行文件名 生成该可执行文件的源文件)
例:
ADD_EXECUTABLE(hello ${SRC_LIST})
说明:SRC_LIST变量中的源文件需要编译出名为hello的可执行文件
- ADD_SUBDIRECTORY
ADD_SUBDIRECTORY(src_dir [binary_dir] [EXCLUDE_FROM_ALL])
向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置
- EXCLUDE_FROM_ALL
含义:将这个目录从编译过程中排除
- ADD_LIBRARY
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
生成动态静态库
例:
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
- SET_TARGET_PROPERTIES
设置目标的一些属性来改变它们构建的方式。
SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1
prop2 value2 ...)
为一个目标设置属性。该命令的语法是列出所有你想要变更的文件,然后提供你想要设置的值。你能够使用任何你想要的属性/值对,并且在随后的代码中调用GET_TARGET_PROPERTY命令取出属性的值。
- INSTALL
INSTALL(TARGETS hellohello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATIONinclude/hello)
设置目标要连接库文件的名称
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ..)
TARGET_LINK_LIBRARIES(main hello) # 连接libhello.so库
TARGET_LINK_LIBRARIES(main libhello.a)
TARGET_LINK_LIBRARIES(main libhello.so)
使用$ENV{NAME}指令就可以调用系统的环境变量
文件操作指令,基本语法为:
file(WRITE filename "message to write"... )
file(APPEND filename "message to write"... )
file(READ filename variable [LIMIT numBytes] [OFFSET offset] [HEX])
WRITE选项将会写一条消息到名为filename的文件中。如果文件已经存在,该命令会覆盖已有的文件;如果文件不存在,它将创建该文件。
APPEND选项和WRITE选项一样,将会写一条消息到名为filename的文件中,只是该消息会附加到文件末尾。
READ选项将会读一个文件中的内容并将其存储在变量里。读文件的位置从offset开始,最多读numBytes个字节。如果指定了HEX参数,二进制代码将会转换为十六进制表达方式,并存储在变量里。
STRINGS将会从一个文件中将一个ASCII字符串的list解析出来,然后存储在variable变量中。文件中的二进制数据会被忽略。回车换行符会被忽略。它也可以用在Intel的Hex和Motorola的S-记录文件;读取它们时,它们会被自动转换为二进制格式。可以使用NO_HEX_CONVERSION选项禁止这项功能。LIMIT_COUNT选项设定了返回的字符串的最大数量。LIMIT_INPUT设置了从输入文件中读取的最大字节数。LIMIT_OUTPUT设置了在输出变量中存储的最大字节数。LENGTH_MINIMUM设置了要返回的字符串的最小长度;小于该长度的字符串会被忽略。LENGTH_MAXIMUM设置了返回字符串的最大长度;更长的字符串会被分割成不长于最大长度的字符串。NEWLINE_CONSUME选项允许新行被包含到字符串中,而不是终止它们。REGEX选项指定了一个待返回的字符串必须满足的正则表达式。典型的使用方式是:
- INCLUDE指令
用来载入CMakeLists.txt文件,也用于载入预定义的cmake模块.
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL参数的作用是文件不存在也不会产生错误。
你可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜
索这个模块并载入。
载入的内容将在处理到 INCLUDE 语句时直接执行。
- FIND_系列指令主要包含一下指令:
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR变量代表找到的文件全路径,包含文件名
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
VAR变量表示找到的库全路径,包含库文件名
FIND_PATH(<VAR> name1 path1 path2 ...)
VAR变量代表包含这个文件的路径。
FIND_PROGRAM(<VAR> name1 path1 path2 ...)
VAR变量代表包含这个程序的全路径。
FIND_PACKAGE(<name> [major.minor] [QUIET][NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])
用来调用预定义在CMAKE_MODULE_PATH下的Find<name>.cmake模块,你也可以自己
定义Find<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录
中供工程使用
- OPTION
OPTION提供一个用户可以任选的选项。语法如下
OPTION (<option_variable> "help string describing option"
[initial value])
OPTION提供选项让用户选择是 ON 或者 OFF ,如果没有提供初始化值,使用OFF。也就是说默认的值是OFF。
但是请注意:这个OPTION命令和你本地是否存在编译缓存的关系很大。
所以,如果你有关于OPTION的改变,那么请你务必清理 CMakeCache.txt 和 CMakeFiles 文件夹。
还有,请使用标准的 [initial value] 值 ON 或者 OFF。
- CONFIGURE_FILE
CONFIGURE_FILE的作用是让普通文件也能使用CMake中的变量。
也就是说代码文件中可以使用CMake中的变量。
语法如下:
CONFIGURE_FILE (<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
拷贝一个 <input>(输入文件) 文件到 <output> (输出文件),并且替换输入文件中被 @VAR@ 或者 ${VAR} 引用的变量值。每一个变量将被替换成当前的变量值(注:CMake中的变量值)或者空串当变量未定义。
COPYONLY
只拷贝文件,不进行任何的变量替换。这个选项在指定了 NEWLINE_STYLE 选项时不能使用(无效)。
ESCAPE_QUOTES
躲过任何的反斜杠(C风格)转义。
注:躲避转义,比如你有个变量在CMake中是这样的
SET(FOO_STRING "\"foo\"")
那么在没有 ESCAPE_QUOTES 选项的状态下,通过变量替换将变为 ""foo"",如果指定了 ESCAPE_QUOTES 选项,变量将不变。
@ONLY
限制变量替换,让其只替换被 @VAR@ 引用的变量(那么 ${VAR} 格式的变量将不会被替换)。这在配置 ${VAR} 语法的脚本时是非常有用的。
####################分享时代,分享学习#####################
编者水平有限,文中难免存在错误,望指出,谢谢!
文中部分内容属于转载其他人的研究成果,在此表示感谢!
#######################################################