CMake&CMakeList.txt

1、各种关系

 

在各种开源项目中,经常会发现项目中除了代码源文件,还包含了 CMakeList.txtMakefile 文件,在项目的编译时候需要用到的命令有 cmakemake。我们本次想搞清楚他们之前的关系以及CMakeList的语法规则。

正常情况下,我们编写程序的大体流程为:

1)用编辑器(vim、emacs等)编写源代码文件(.h、.cpp等);

2)用编译器编译代码生成目标文件(.o等);

3)用链接器连接目标文件生成可执行文件(.exe等)。

一个程序在编写时,可能需要编写很多的代码文件,以及依赖很多第三方的库。在这种情况下,手动依次编译每个文件会变的非常麻烦,效率低下。

make 是一个自动化的批量编译工具,可以实现用一个命令构建整个工程的目的。但是其执行需要依赖一个规则文件,这个文件就是 Makefile。 Makefile 文件里详细描述了构建的细节(文件的依赖关系,编译的先后顺序等)。

对于一个大工程来说,编写 Makefile 文件也是一项非常复杂的事情。

cmake(Cross-platform Make)是一个可以自动生成 Makefile 文件的工具,当然它不只能生成 Makefile ,还能跨平台生成主流IDE(VS, xcode…)构建工程所需的 project 文件。 cmake 的执行同样需要依赖规则文件,这个文件就是 CMakeLists.txt 。

所以,为了能够把一堆c,cc,cpp,h,hpp代码变成可运行的库或者程序,核心还是Makefile,CMakefiles.txt纯粹是为了降低不懂Unix体系的人去编译C/C++代码做的一个自动系统和环境检测和设置工具,同时帮你生成Makefile。

综上所述,大致可以总结 cmake, make, CMakeList.txt, Makefile 之间的关系,如下图所示:

CMake&CMakeList.txt_第1张图片

2、CMakeList.txt规则

鉴于目前在开发中,主要遇到的是使用CMake和CMakeList.txt使用的问题,重点介绍下CMakeList.txt的编写规则。

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

CMake的所有的语句都写在一个叫:CMakeLists.txt的文件中。当CMakeLists.txt文件确定后,可以用ccmake命令对相关 的变量值进行配置。这个命令必须指向CMakeLists.txt所在的目录。配置完成之后,应用cmake命令生成相应的makefile(在Unix like系统下)或者 project文件(指定用window下的相应编程工具编译时)

2.1 常用命令

2.11 指定 cmake 的最小版本

这行命令是可选的,可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。

cmake_minimum_required(VERSION 3.4.1)

2.12 设置项目名称

这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。

project(demo)

2.13 设置编译类型

add_executable(demo test.cpp) # 生成可执行文件

add_library(common STATIC test.cpp) # 生成静态库

add_library(common SHARED test.cpp) # 生成动态库或共享库

add_library 默认生成是静态库,通过以上命令生成文件名字

在 Linux /Mac下是:

  • demo

  • libcommon.a

  • libcommon.so

在 Windows 下是:

  • demo.exe

  • common.lib

  • common.dll

2.14 执行编译包含的源文件

2.14.1 明确指定包含哪些源文件

源文件少时可以这么做

add_library(demo demo.cpp test.cpp util.cpp)

2.14.2 搜索所有的 cpp 文件

aux_source_directory(dir VAR) 发现一个目录下所有的源代码文件并将列表存储在一个变量中。

aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件,存储在变量SRC_LIST中

add_library(demo ${SRC_LIST})

2.14.3 自定义搜索规则

file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp") #查找当前目录下、及protocol目录下所有cpp文件,保存在变量SEC_LIST中

add_library(demo ${SRC_LIST})

# 或者

file(GLOB SRC_LIST "*.cpp") #查找当前目录下所有cpp文件,保存在变量SRC_LIST中

file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")

add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

# 或者

file(GLOB_RECURSE SRC_LIST "*.cpp") #递归搜索

FILE(GLOB SRC_PROTOCOL RELATIVE "protocol" "*.cpp") # 相对protocol目录下搜索

add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

# 或者

aux_source_directory(. SRC_LIST)

aux_source_directory(protocol SRC_PROTOCOL_LIST)

add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

2.15 查找指定的库文件

ind_library(VAR name path)查找到指定的预编译库,并将它的路径存储在变量中。
默认的搜索路径为 cmake 包含的系统库,因此如果是 NDK 的公共库只需要指定库的 name 即可。

类似的命令还有 find_file()、find_path()、find_program()、find_package()。

find_library( # Sets the name of the path variable.

                log-lib

              # Specifies the name of the NDK library that

              # you want CMake to locate.

                log )

2.16 设置包含的目录

include_directories(

${CMAKE_CURRENT_SOURCE_DIR}

${CMAKE_CURRENT_BINARY_DIR}

${CMAKE_CURRENT_SOURCE_DIR}/include

)

Linux和Mac下还可以通过如下方式设置包含的目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")

2.17 设置链接库搜索目录

link_directories(

${CMAKE_CURRENT_SOURCE_DIR}/libs

)

Linux和Mac下还可以通过如下方式设置链接库搜索目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")

2.18 设置 target 需要链接的库

target_link_libraries( # 目标库

demo

# 目标库需要链接的库

# log-lib 是上面 find_library 指定的变量名

${log-lib} )

在 Windows 下,系统会根据链接库目录,搜索xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)

2.18.1 指定链接静态库或动态库

target_link_libraries(demo libface.a) # 链接libface.a

target_link_libraries(demo libface.so) # 链接libface.so

2.18.2 指定全路径

target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)

2.18.3 指定链接多个库

target_link_libraries(demo

${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a

boost_system.a

boost_thread

pthread)

2.19 设置变量

2.19.1 set直接设置变量的值

set(SRC_LIST main.cpp test.cpp) # 两个cpp文件,存储在变量SRC_LIST中

add_executable(demo ${SRC_LIST})

2.19.2 set追加设置变量的值

set(SRC_LIST main.cpp)

set(SRC_LIST ${SRC_LIST} test.cpp)

add_executable(demo ${SRC_LIST})

2.19.3 list追加或删除变量的值

set(SRC_LIST main.cpp)

list(APPEND SRC_LIST test.cpp)

list(REMOVE_ITEM SRC_LIST main.cpp)

add_executable(demo ${SRC_LIST})

2.110 条件控制

2.110.1 if…elseif…else…endif

逻辑判断和比较:

if (expression):expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真

if (not exp):与上面相反

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 dir):当 dir 是目录时为真

if (DEFINED var):如果变量被定义为真

if (var MATCHES regex):给定的变量或者字符串能够匹配正则表达式 regex 时为真,此处 var 可以用 var 名,也可以用 ${var}

if (string MATCHES regex)

数字比较:

if (variable LESS number):LESS 小于

if (string LESS number)

if (variable GREATER number):GREATER 大于

if (string GREATER number)

if (variable EQUAL number):EQUAL 等于

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)

2.1100.2 while .. endwhile

while(condition)

...

endwhile()

2.110.3 foreach…endforeach

foreach(loop_var RANGE start stop [step])

...

endforeach(loop_var)

foreach(i RANGE 1 9 2) # 从1到9,步长为2

message(${i})

endforeach(i)

# 输出:13579

2.111 打印信息

message(${PROJECT_SOURCE_DIR})

message("build with debug mode")

message(WARNING "this is warnning message")

message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败

2.112 包含其他cmake文件

include(./common.cmake) # 指定包含文件的全路径

include(def) # 在搜索路径中搜索def.cmake文件

set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径

2.2 常用变量

2.21 预定义变量

PROJECT_SOURCE_DIR:工程的根目录

PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build

PROJECT_NAME:返回通过 project 命令定义的项目名称

CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径

CMAKE_CURRENT_BINARY_DIR:target 编译目录

CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径

CMAKE_CURRENT_LIST_LINE:当前所在的行

CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块

EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置

LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

2.22 环境变量

使用环境变量

$ENV{Name}

写入环境变量

set(ENV{Name} value) # 这里没有“$”符号

2.23 系统变量

CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3

­CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4

­CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1

­CMAKE_SYSTEM:系统名称,比如 Linux-­2.6.22

­CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux

­CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22

­CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686

­UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin

­WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin

2.24 主要开关选项

BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库

CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加

CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加

add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔

3 实例搞起

3.1 单个源文件

cmaketest1.zip

以一段简单C程序为例

#include 

int main() {

printf("某程序员退休后决定练习书法,于是重金购买文房四宝。一日,饭后突生雅兴,一番研墨拟纸,并点上上好檀香。定神片刻,泼墨挥毫,郑重地写下一行字:\n");

printf("Hello World!\n");

return 0;

}

新建文件 CMakeLists.txt(命名必须是 CMakeLists.txt,注意大小写和不要漏字母)

编译可执行文件

project(HELLO)

add_executable(hello test.c) # 生成可执行文件

#add_library(common STATIC test.c) # 生成静态库

#add_library(common SHARED test.c) # 生成动态库或共享库

直接在当前文件夹执行cmake ..

弹出提示

 原来这个命令会去上级目录找CMakeLists.txt

一般会新建一个build目录,cd到build目录

先执行cmake ..

再执行make

CMake&CMakeList.txt_第2张图片 可以看到在build目录中产出了可执行文件,并且能执行成功

编译静态库

project(HELLO)

#add_executable(hello test.c) # 生成可执行文件

add_library(common STATIC test.c) # 生成静态库

#add_library(common SHARED test.c) # 生成动态库或共享库

CMake&CMakeList.txt_第3张图片

编译动态库

project(HELLO)

#add_executable(hello test.c) # 生成可执行文件

#add_library(common STATIC test.c) # 生成静态库

add_library(common SHARED test.c) # 生成动态库或共享库

但是发现编译出来的是Mac下的动态库文件格式,dylib

CMake&CMakeList.txt_第4张图片

如何才能编译出来so格式的动态库呢,需要把CMAKE_SYSTEM_NAME设置为Android

set(CMAKE_SYSTEM_NAME Android)

set(CMAKE_ANDROID_NDK "/Users/guozhenqian/Develop/android-ndk-r22b")

set(CMAKE_TOOLCHAIN_FILE "/Users/guozhenqian/Develop/android-ndk-r22b/build/cmake/android.toolchain.cmake")

set(ANDROID_TOOLCHAIN clang)

set(ANDROID_NATIVE_API_LEVEL 14)

set(ANDROID_ABI "armeabi-v7a")

set(CMAKE_BUILD_TYPE Release)

set(CMAKE_CROSSCOMPILING "TRUE")

set(CMAKE_CXX_STANDARD 11) #c++标准

project(HELLO)

#add_executable(hello test.c) # 生成可执行文件

#add_library(common STATIC test.c) # 生成静态库

add_library(common SHARED test.c) # 生成动态库或共享库

CMake&CMakeList.txt_第5张图片

3.2 复杂项目(多个目录,多个源文件,多个项目)

关于多目录+自定义编译选项的,参考这篇文章足够了

CMake入门-04-自定义编译选项_binglingziyu的博客-CSDN博客_cmake 自定义选项

但是这篇文章有个错误的地方,导致自定义编译选项不生效

option一定要在configure_file之前定义

cmaketest_3.zip

cmake_minimum_required(VERSION 3.15.0)

# 项目名

project(hello)

# 查找当前目录下的所有源文件,并将名称保存到 SRC_LIST 变量

aux_source_directory(. SRC_LIST)

# 是否使用自己的 MathFunctions 库

# 这里设置的变量 USE_MYMATH、中间的提示文字、默认值,在 ccmake 命令中会展示

# 这个option一定要在configure_file之前,不然生成的config.h文件默认是没有define的

option (USE_MYMATH

"Use provided math implementation"

ON

)

# 加入一个配置头文件,用于处理 CMake 对源码的设置

configure_file (

"${PROJECT_SOURCE_DIR}/config.h.in"

"${PROJECT_SOURCE_DIR}/config.h"

)

# 是否加入 MathFunctions 库

if (USE_MYMATH)

# 添加头文件路径

include_directories ("${PROJECT_SOURCE_DIR}/math")

# 添加 math 子目录 (math 目录里必须有 CMakeLists.txt)

add_subdirectory (math)

set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)

endif (USE_MYMATH)

# 指定生成目标

add_executable(hello ${SRC_LIST} ${MATH_SRC_LIST})

# 添加链接库

target_link_libraries(hello ${EXTRA_LIBS})

3.3 复杂项目编译动态库

在cmaketest_3的基础上升级,编译出来动态库

cmaketest_4.zip

cmake_minimum_required(VERSION 3.15.0)

# 设置Android环境

set(CMAKE_SYSTEM_NAME Android)

set(CMAKE_ANDROID_NDK "/Users/guozhenqian/Develop/android-ndk-r22b")

set(CMAKE_TOOLCHAIN_FILE "/Users/guozhenqian/Develop/android-ndk-r22b/build/cmake/android.toolchain.cmake")

set(ANDROID_TOOLCHAIN clang)

set(ANDROID_NATIVE_API_LEVEL 14)

set(ANDROID_ABI "armeabi-v7a")

set(CMAKE_BUILD_TYPE Release)

set(CMAKE_CROSSCOMPILING "TRUE")

set(CMAKE_CXX_STANDARD 11) #c++标准

# 项目名

project(hello)

# 查找当前目录下的所有源文件,并将名称保存到 SRC_LIST 变量

aux_source_directory(. SRC_LIST)

# 打印一下SRC_LIST的值看看

MESSAGE( STATUS "SRC_LIST contains = ${SRC_LIST}")

# 查找 math 目录下的所有源文件,并将名称保存到 MATH_SRC_LIST 变量

# aux_source_directory(${PROJECT_SOURCE_DIR}/math MATH_SRC_LIST)

# 是否使用自己的 MathFunctions 库

# 这里设置的变量 USE_MYMATH、中间的提示文字、默认值,在 ccmake 命令中会展示

# 这个option一定要在configure_file之前,不然生成的config.h文件默认是没有define的

option (

USE_MYMATH "Use provided math implementation" ON

)

# 加入一个配置头文件,用于处理 CMake 对源码的设置

configure_file (

"${PROJECT_SOURCE_DIR}/config.h.in"

"${PROJECT_SOURCE_DIR}/config.h"

)

# 是否加入 MathFunctions 库

if (USE_MYMATH)

# 添加头文件路径

include_directories ("${PROJECT_SOURCE_DIR}/math")

# 添加 math 子目录 (math 目录里必须有 CMakeLists.txt)

add_subdirectory (math)

# 把MathFunctions追加设置到EXTRA_LIBS变量中

set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)

MESSAGE( STATUS "EXTRA_LIBS contains = ${EXTRA_LIBS}")

endif (USE_MYMATH)

# 生成动态库

add_library(hello SHARED ${SRC_LIST})

# 添加链接库

target_link_libraries(hello ${EXTRA_LIBS})

码字不易,如果对您有帮助,还请赏个赞哇!

参考文章:

1、cmake, make, CMakeLists.txt, Makefile简介 cmake, make, CMakeLists.txt, Makefile简介_Northan的博客-CSDN博客_cmakelists.txt makefile

2、Makefile和CMakefiles.txt的那些事儿 https://www.jianshu.com/p/f51adf8430f0

3、CMakeLists.txt 语法介绍与实例演练(最后一个例子有问题) CMakeLists.txt 语法介绍与实例演练_阿飞__的博客-CSDN博客_cmakelist

4、cmake学习4--自定义编译选项 cmake学习4--自定义编译选项_小珊瑚的爸爸的博客-CSDN博客

5、CMake入门实战(这篇才正宗,别的都是抄的吧)CMake&CMakeList.txt

你可能感兴趣的:(C/C++引擎开发,Cmake)