一 简介

 

CMake 是一个跨平台的、开源的构建工具。cmake 是 makefile 的上层工具,它们的目的正是为了产生可移植的makefile,并简化自己动手写makefile时的巨大工作量.

它允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt 。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。(具体CMake会生成CMakeCache.txtMakefilecmake_install.cmake三个文件和一个文件夹CmakeFiles)
  3. 使用 make 命令进行编译。

基本语法

一个最基本的CmakeLists.txt文件最少需要包含以下三行:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

cmake的语法支持大小、小写和大小写混合

只有系统指令是不区分大小写的,但是变量和字符串是区分大小写的。

二 CMakeLists 变量

可以使用 SET(set) 来定义变量

语法 :

SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

 指令功能 : 用来显式的定义变量 例子 : 

SET (SRC_LST main.c other.c)

 说明: 用变量代替值,例子中定义 SRC_LST 代替后面的字符串。

我们可以使用 ${NAME} 来获取变量的名称。变量引用可以嵌套,并从内部进行替换,例如${outer_${inner_variable}veriable}。 环境变量引用的形式为$ENV{VAR},并在相同的上下文中作为正常变量引用。

cmake 常用变量

注:in-source编译就是把编译输出文件(包括.o文件)和源文件放在同一个目录。

out-source编译就是把编译输出文件(包括.o文件)放到与源文件目录不同的其他目录

环境变量名描述
CMAKE_BINARY_DIR,

PROJECT_BINARY_DIR, 

<projectname>_BINARY_DIR

如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。
CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR,

 <projectname>_SOURCE_DIR

工程顶层目录。
CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRRENT_BINARY_DIR

如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 target 编译目录。

EXECUTABLE_OUTPUT_PATH , LIBRARY_OUTPUT_PATH最终目标文件存放的路径。
PROJECT_NAME通过 PROJECT 指令定义的项目名称。

CMAKE系统信息

系统信息变量名描述
CMAKE_MAJOR_VERSIONCMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSIONCMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSIONCMAKE 补丁等级,比如 2.4.6 中的 6
CMAKE_SYSTEM系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR处理器名称,比如 i686.

cmake 编译选项

编译控制开关名描述
BUILD_SHARED_LIBS使用 ADD_LIBRARY 时生成动态库
BUILD_STATIC_LIBS使用 ADD_LIBRARY 时生成静态库
CMAKE_C_FLAGS设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。
CMAKE_CXX_FLAGS设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。

三 CMAKE常用指令

指令语法定义

ADD_DEFINITIONS

向 C/C++编译器添加 -D 定义. 如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效

ADD_DEPENDENCIES

ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)

定义 target 依赖的其他 target, 确保在编译本 target 之前,其他的 target 已经被构建。

AUX_SOURCE_DIRECTORY

AUX_SOURCE_DIRECTORY(dir VARIABLE)

作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。AUX_SOURCE_DIRECTORY(. SRC_LIST)

ADD_SUBDIRECTORY

ADD_SUBDIRECTORY(NAME)

添加一个文件夹进行编译,该文件夹下的 CMakeLists.txt 负责编译该文件夹下的源码. NAME是想对于调用add_subdirectory的CMakeListst.txt的相对路径.

include_directories

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

将给定目录添加到编译器用来搜索包含文件的目录中。相对路径被解释为相对于当前源目录。

包含目录添加到 INCLUDE_DIRECTORIES 当前CMakeLists文件的目录属性。它们也被添加到INCLUDE_DIRECTORIES当前CMakeLists文件中每个目标的target属性。目标属性值是生成器使用的属性值。

link_libraries

link_libraries([item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)

将库链接到以后添加的所有目标。

ADD_EXECUTABLE

ADD_EXECUTABLE(<name> [source1] [source2 ...])

利用源码文件生成目标可执行程序。

ADD_LIBRARY

ADD_LIBRARY(<name> [STATIC | SHARED | MODULE] [source1] [source2 ...])

根据源码文件生成目标库。

STATIC,SHARED 或者 MODULE 可以指定要创建的库的类型。 STATIC库是链接其他目标时使用的目标文件的存档。 SHARED库是动态链接的,并在运行时加载

ENABLE_TESTING

ENABLE_TESTING()

控制 Makefile 是否构建 test 目标,涉及工程所有目录。 一般情况这个指令放在工程的主CMakeLists.txt 中.

ADD_TEST

ADD_TEST(testname Exename arg1 arg2 ...)

testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等。 后面连接传递给可执行文件的参数。 如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令, 任何 ADD_TEST 都是无效的。

CMAKE_MINIMUM_REQUIRED

CMAKE_MINIMUM_REQUIRED 定义 cmake 的最低兼容版本 比如 CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR) 如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止。
FILE

FILE(WRITE filename "message to write"... ) FILE(APPEND filename "message to write"... ) FILE(READ filename variable) FILE(GLOB variable [RELATIVE path] [globbing expression_r_rs]...) FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expression_r_rs]...) 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)

FILE(WRITE filename "message to write"... ) FILE(APPEND filename "message to write"... ) FILE(READ filename variable) FILE(GLOB variable [RELATIVE path] [globbing expression_r_rs]...) FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expression_r_rs]...) 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)

相似命令区别:

(1)  INCLUDE_DIRECTORIES(添加头文件目录)

它相当于g++选项中的-I参数的作用,也相当于环境变量中增加路径到CPLUS_INCLUDE_PATH变量的作用(这里特指c++。c和Java中用法类似)。

比如:

include_directories("/opt/MATLAB/R2012a/extern/include")

(2) LINK_DIRECTORIES(添加需要链接的库文件目录)

语法:

link_directories(directory1 directory2 ...)

它相当于g++命令的-L选项的作用,也相当于环境变量中增加LD_LIBRARY_PATH的路径的作用。

比如:

LINK_DIRECTORIES("/opt/MATLAB/R2012a/bin/glnxa64")

(3) LINK_LIBRARIES (添加需要链接的库文件路径,注意这里是全路径)

List of direct link dependencies.

比如:

LINK_LIBRARIES("/opt/MATLAB/R2012a/bin/glnxa64/libeng.so")
LINK_LIBRARIES("/opt/MATLAB/R2012a/bin/glnxa64/libmx.so")

也可以写成:

LINK_LIBRARIES("/opt/MATLAB/R2012a/bin/glnxa64/libeng.so" "/opt/MATLAB/R2012a/bin/glnxa64/libmx.so")

(4) TARGET_LINK_LIBRARIES (设置要链接的库文件的名称)

语法:TARGET_LINK_LIBRARIES(targetlibrary1 <debug | optimized> library2 ..)
 
比如(以下写法(包括备注中的)都可以):
TARGET_LINK_LIBRARIES(myProject hello),连接libhello.so库
TARGET_LINK_LIBRARIES(myProject libhello.a)
TARGET_LINK_LIBRARIES(myProject libhello.so)
 
再如:
TARGET_LINK_LIBRARIES(myProject #这些库名写法都可以。
TARGET_LINK_LIBRARIES(myProject TARGET_LINK_LIBRARIES(myProject -leng)

四 CMake 控制指令

IF 指令

if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()

#####

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)

FOREACH 指令

foreach(<loop_var> <items>)
  <commands>
endforeach()

其中<items>是以分号或空格分隔的项目列表。记录foreach匹配和匹配之间的所有命令endforeach而不调用。 一旦endforeach评估,命令的记录列表中的每个项目调用一次<items>。在每次迭代开始时,变量loop_var将设置为当前项的值。

WHILE 指令

while(<condition>)
  <commands>
endwhile()

while和匹配之间的所有命令 endwhile()被记录而不被调用。 一旦endwhile()如果被评估,则只要为<condition>真,就会调用记录的命令列表。

4 macro&function

cmake中有两个相似的关键字,macro和function。这两个都是创建一段有名字的代码稍后可以调用,还可以传参数。

macro(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endmacro(<name>)
function(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
function(<name>)

定义一个名称为name的宏(函数),arg1...是传入的参数。我们除了可以用${arg1}来引用变量以外,系统为我们提供了一些特殊的变量:

变量说明
ARGV##是一个下标,0指向第一个参数,累加
ARGV所有的定义时要求传入的参数
ARGN定义时要求传入的参数以外的参数,比如定义宏(函数)时,要求输入1个,书记输入了3个,则剩下的两个会以数组形式存储在ARGN中
ARGC传入的实际参数的个数,也就是调用函数是传入的参数个数

宏的ARGN、ARGV等参数不是通常CMake意义上的变量。 它们是字符串替换,很像C预处理器对宏的处理。 因此,如下命令是错误的:

if(ARGV1) # ARGV1 is not a variable 
if(DEFINED ARGV2) # ARGV2 is not a variable
if(ARGC GREATER 2) # ARGC is not a variable
foreach(loop_var IN LISTS ARGN) # ARGN is not a variable

正确写法如下

if(${ARGV1})
if(DEFINED ${ARGV2})
if(${ARGC} GREATER 2)
foreach(loop_var IN LISTS ${ARGN})
or
set(list_var "${ARGN}")
foreach(loop_var IN LISTS list_var)

例子:

macro(FOO arg1 arg2 arg3)
    message(STATUS "this is arg1:${arg1},ARGV0=${ARGV0}")
    message(STATUS "this is arg2:${arg2},ARGV1=${ARGV1}")
    message(STATUS "this is arg3:${arg3},ARGV2=${ARGV2}")
    message(STATUS "this is argc:${ARGC}")
    message(STATUS "this is args:${ARGV},ARGN=${ARGN}")
    if(arg1 STREQUAL one)
        message(STATUS "this is arg1")
    endif()
    if(ARGV2 STREQUAL "two")
        message(STATUS "this is arg2")
    endif()
    set(${arg1} nine)
    message(STATUS "after set arg1=${${arg1}}")
endmacro(FOO)

function(BAR arg1)
    message(STATUS "this is arg1:${arg1},ARGV0=${ARGV0}")
    message(STATUS "this is argn:${ARGN}")
    if(arg1 STREQUAL first)
        message(STATUS "this is first")
    endif()
    set(arg1 ten)
    message(STATUS "after set arg1=${arg1}")
endfunction(BAR arg1)

set(p1 one)
set(p2 two)
set(p3 three)
set(p4 four)
set(p5 five)
set(p6 first)
set(p7 second)

FOO(${p1} ${p2} ${p3} ${p4} ${p5})
BAR(${p6} ${p7})
message(STATUS "after bar p6=${p6}")

输出:

-- this is arg1:one,ARGV0=one
-- this is arg2:two,ARGV1=two
-- this is arg3:three,ARGV2=three
-- this is argc:5
-- this is args:one;two;three;four;five,ARGN=four;five
-- after set arg1=nine
-- this is arg1:first,ARGV0=first
-- this is argn:second
-- this is first
-- after set arg1=ten
-- after bar p6=first

四 CMAKE文件生成器

cmake使用教程(四)-文件生成器 - 掘金

五 Cpack生成安装包

cmake使用教程(五)-cpack生成安装包 - 掘金

六 学习demo

直接sudo sh build.sh

demo的GITHUB地址

七 参考文章

CMake 入门实战 | HaHack

CMake 教程 | CMake 从入门到应用 - Aiden Blog

cmake使用教程(一)-起步 - 掘金

八 cmake trick

add_definitions

CMAKE 中 add_definitions的用法._Joe_yaoxiao的博客-CSDN博客_add_definitions

CMakeLists中的add_definitions()函数_古路的博客-CSDN博客_add_definitions

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐