一、CmakeList的编写和参数详解
在linux 下进行开发很多人选择编写makefile 文件进行项目环境搭建,而makefile 文件依赖关系复杂,工作量很大,搞的人头很大。采用自动化的项目构建工具cmake 可以将程序员从复杂的makefile 文件中解脱出来。cmake 根据内置的规则和语法来自动生成相关的makefile 文件进行编译,同时还支持静态库和动态库的构建,我把工作中用到的东东总结在此,方便忘记时随时查看,具体cmake的介绍和详细语法还是参考官方文档(http://www.cmake.org/),有一篇中文的cmake 实践 写的不错,可以google一下。
使用cmake 很简单,只需要执行cmake, make 两个命令即可,用我工作中的一个工程举例说明。
假设当前的项目代码在src 目录。 src 下有子目录:server, utility, lib, bin, build
server ----- 存放项目的主功能类文件
utility ----- 存放项目要用到相关库文件,便已成为库文件存放到子目录lib 中
lib ----- 存放utility 生成的库
bin ----- 存放association 生成的二进制文件
build ----- 编译目录,存放编译生成的中间文件
cmake 要求工程主目录和所有存放源代码子目录下都要编写CMakeLists.txt 文件,注意大小写(cm 大写,list中l 大写且落下s).
src/CMakeLists.txt 文件如下:
相关解释:
1. CMakeLists.txt 文件中不区分大小写
2. PROJECT(project_name) 定义工程名称
语法:project(projectname [cxx] [c] [java])
可以指定工程采用的语言,选项分别表示:C++, C, java, 如不指定默认支持所有语言
3. MESSAGE(STATUS, "Content") 打印相关消息
输出消息,供调试CMakeLists.txt 文件使用。
4. SET(CMAKE_BUILE_TYPE DEBUG) 设置编译类型debug 或者release。 debug 版会生成相关调试信息,可以使用GDB 进行
调试;release不会生成调试信息。当无法进行调试时查看此处是否设置为debug.
5. SET(CMAKE_C_FLAGS_DEBUG "-g -Wall") 设置编译器的类型
CMAKE_C_FLAGS_DEBUG ---- C 编译器
CMAKE_CXX_FLAGS_DEBUG ---- C++ 编译器
6. ADD_SUBDIRECTORY(utility) 添加要编译的子目录
为工程主目录下的存放源代码的子目录使用该命令,各子目录出现的顺序随意。
如上便是工程server_project 主目录src 下的CMakeLists.txt 文件,下一篇我们解释子目录utiltiy中的CMakeLists.txt 文件。
子目录utility 下的CMakeLists.txt 文件如下:
相关解释:
1. SET(SOURCE_FILES .....)
表示要编译的源文件,所有的源文件都要罗列到此处。set 设置变量,变量名SOURCE_FILES自定义。
2. INCLUDE_DIRECTORY(...)
include头文件时搜索的所有目录
变量PROJECT_SOURCE_DIR 表示工程所在的路径,系统默认的变量
3. LINK_DIRECTORIES(...)
库文件存放的目录,在程序连接库文件的时候要再这些目录下寻找对应的库文件
4. ADD_LIBRARY(...)
表示生成静态链接库libassociaiton.a,由${PROJECT_SOURCE_DIR}代表的文件生成。
语法:ADD_LIBRARY(libname [SHARED|STATIC]
SHARED 表示生成动态库, STATIC表示生成静态库。
5. TARGET_LINK_LIBRARY(association core)
表示库association 依赖core库文件
6. SET_TARGET_PROPERTIES
设置编译的库文件存放的目录,还可用于其他属性的设置。如不指定,
生成的执行文件在当前编译目录下的各子目录下的build目录下,好拗口!简单一点:
如指定在: ./src/lib 下
不指定在: ./src/build/utility/build 目录下
生成的中间文件在./src/build/utilty/build 目录下,不受该命令额影响
子目录server 下的CMakeLists.txt 文件:
相关解释:
1. ADD_EXECUTABLE() #指定要生成的执行文件的名称server
其他用法同utilty/CMakeLists.txt
2. SET_TARGET_PROPERTIES
设置生成的执行文件存放的路径,
注意:
执行文件server 依赖的子目录utility 子目录生成的静态库libutility.a,在指定的时候要写成:
TARGET_LINK_LIBRARIES(server utility)
而不能写成:
TARGET_LINK_LIBRARIES(server libutility.a)
否则编译总会提示找不到libutility库文件。
但使用第三方的库却要指定成具体的库名,如:libACE-6.0.0.so
这一点很诡异,暂时还没找到原因。
完成对应的CMakeLists.txt 文件编写后,便可以进行编译了。
编译:
进入 ./src/build
执行cmake ..
make
cmake 的使用很简单,更高级的应用好比版本信息,打包,安装等相关基本的应用后面会一一介绍,
复杂的语法使用要参考官方文档。
二、Android Studio NDK CMake 指定so输出路径以及生成多个so的案例与总结
前文
一直想用Android Studio的新方式Cmake来编译JNI 代码,之前也尝试过,奈何有两个难题挡住了我
1. 只能生成一个 so库,不能一次性生成多个 so库,之前的mk是可以有子模块的。
2. 每次生成的so所在的目录不是在 jniLibs下,虽然知道如果打包,会将它打包进去,但就是觉得看不见它,想提供给别人用,还要去某个目录找。
经过尝试,这两个问题都可以解决了。
生成单个so的案例
demo下载地址: http://download.csdn.net/detail/b2259909/9766081
直接看CMakeLists.txt文件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
其中 各个设置都有说明。主要看这个:
- 1
它将会把生成的so库按照你在 build.gradle 指定的 abi分别放置在 jniLibs下
非常好,先解决了第二个问题了。
生成多个so案例
还是上面那个demo,重新建一个module。
cpp的目录结构:
直接看CMakeLists.txt文件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
不同的地方是改为添加子目录:
- 1
- 2
- 3
这样就会先去跑到子目录下的 one 和 two 的CmakeLists.txt,执行成功再返回。
此时子目录one下的CmakeLists.txt:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
子目录two下的CmakeLists.txt:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
最后生成了以下两个so文件,并自动按照abi分别放置在了 jniLibs下:
第一个问题也成功了。
总结
最后,除了 设定abiFilters 必须在 build.gradle
主要是发现CmakeLists.txt里 其实可以指定很多东西:
1. so输出路径 CMAKE_LIBRARY_OUTPUT_DIRECTORY
2. .a 静态库输出路径 CMAKE_ARCHIVE_OUTPUT_DIRECTORY
2. 获取当前编译的abi , ANDROID_ABI
3. 编译选项:
CMAKE_C_FLAGS
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG/CMAKE_CXX_FLAGS_RELEASE
4. 子目录编译: ADD_SUBDIRECTORY
5. #设置.c文件集合的变量
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
6._执行自定义命令:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
最后,因为很多时候,JNI的参数还要转为C的方式,当我们在JAVA层写了native方法,android IDE自动提示红色,这时按下 ALT + ENTER 可以自动生成JNI下的方法声明,并且入参也帮我们转换好了。不过有时候这个插件不生效。
所以我写了一个JNI 的入参转为 C/C++的代码插件: JNI-Convert-Var,直接在 plugin 的仓库搜就有了。
最近尝试实现android studio的ALT + ENTER 可以自动生成JNI下的方法声明,结果发现好多IntelliJ IDEA的接口不熟悉。 只能先放弃了,以下是我的逻辑:
当鼠标点击在 Native声明方法上时:
1. 检查文件类型,如果为java就继续
2. 获取当前行的上下共三行字符串数据,使用正则表达式获取native声明的完整方法。
3. 检查当前模块目录下的jni或者cpp目录下的.c或者.cpp文件。
4. 如果没有文件,弹窗让用户创建一个C/C++文件,并追加转换后(如何转换会有一个专门的类)的Java2C方法在文件末尾. 在IDE打开此文件。
5. 如果JNI或者cpp目录有一个以上的C/C++文件, 弹窗让用户选择一个C/C++文件或者创建,之后打开文件追加转换后(如何转换会有一个专门的类)的Java2C方法在文件末尾. 。 在IDE打开此文件。
上面逻辑中:
- 文件类型 ,IntelliJ IDEA 的plugin开发API中可以获取到
- 获取当前行的上下共三行字符串数据 ,IntelliJ IDEA 的plugin开发API中可以获取到
- 模块目录的API暂时没找到
- 在IDE打开C/C++文件,不知道用什么接口
三、CMake之CMakeLists.txt编写入门
自定义变量
主要有隐式定义和显式定义两种。
隐式定义的一个例子是PROJECT
指令,它会隐式的定义< projectname >_BINARY_DIR
和< projectname >_SOURCE_DIR
两个变量;显式定义使用SET
指令构建自定义变量,比如:SET(HELLO_SRCmain.c)
就可以通过${HELLO_SRC}
来引用这个自定义变量了。
变量引用方式
使用${}
进行变量的引用;在IF
等语句中,是直接使用变量名而不通过${}
取值。
常用变量
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
< projectname >_BINARY_DIR
这三个变量指代的内容是一致的,如果是in-source编译,指得就是工程顶层目录;如果是out-of-source编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR
跟其它指令稍有区别,目前可以认为它们是一致的。
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
< projectname >_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在in-source编译时,他跟CMAKE_BINARY_DIR
等变量一致。PROJECT_SOURCE_DIR
跟其它指令稍有区别,目前可以认为它们是一致的。
(out-of-source build与in-source build相对,指是否在CMakeLists.txt所在目录进行编译。)
CMAKE_CURRENT_SOURCE_DIR
当前处理的CMakeLists.txt所在的路径,比如上面我们提到的src子目录。
CMAKE_CURRRENT_BINARY_DIR
如果是in-source编译,它跟CMAKE_CURRENT_SOURCE_DIR
一致;如果是out-of-source编译,指的是target编译目录。使用ADD_SUBDIRECTORY(src bin)
可以更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)
并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的CMakeLists.txt的完整路径
CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行
CMAKE_MODULE_PATH
这个变量用来定义自己的cmake模块所在的路径。如果工程比较复杂,有可能会自己编写一些cmake模块,这些cmake模块是随工程发布的,为了让cmake在处理CMakeLists.txt时找到这些模块,你需要通过SET指令将cmake模块路径设置一下。比如SET(CMAKE_MODULE_PATH,${PROJECT_SOURCE_DIR}/cmake)
这时候就可以通过INCLUDE指令来调用自己的模块了。
EXECUTABLE_OUTPUT_PATH
新定义最终结果的存放目录
LIBRARY_OUTPUT_PATH
新定义最终结果的存放目录
PROJECT_NAME
返回通过PROJECT
指令定义的项目名称。
cmake调用环境变量的方式
使用$ENV{NAME}指令就可以调用系统的环境变量了。比如MESSAGE(STATUS "HOME dir: $ENV{HOME}")
设置环境变量的方式是SET(ENV{变量名} 值)。
-
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})
-
CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
将工程提供的头文件目录始终置于系统头文件目录的前面,当定义的头文件确实跟系统发生冲突时可以提供一些帮助。 -
CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
系统信息
CMAKE_MAJOR_VERSION
,CMAKE主版本号,比如2.4.6中的2CMAKE_MINOR_VERSION
,CMAKE次版本号,比如2.4.6中的4CMAKE_PATCH_VERSION
,CMAKE补丁等级,比如2.4.6中的6CMAKE_SYSTEM
,系统名称,比如Linux-2.6.22CMAKE_SYSTEM_NAME
,不包含版本的系统名,比如LinuxCMAKE_SYSTEM_VERSION
,系统版本,比如2.6.22CMAKE_SYSTEM_PROCESSOR
,处理器名称,比如i686UNIX
,在所有的类Unix平台为TRUE
,包括OSX和cygwinWIN32
,在所有的Win32平台为TRUE
,包括cygwin
主要的开关选项
-
CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
用来控制IF ELSE
语句的书写方式。 -
BUILD_SHARED_LIBS
这个开关用来控制默认的库编译方式。如果不进行设置,使用ADD_LIBRARY
并没有指定库类型的情况下,默认编译生成的库都是静态库;如果SET(BUILD_SHARED_LIBSON)
后,默认生成的为动态库。 -
CMAKE_C_FLAGS
设置C编译选项,也可以通过指令ADD_DEFINITIONS()
添加。 -
MAKE_CXX_FLAGS
设置C++编译选项,也可以通过指令ADD_DEFINITIONS()
添加。
cMake常用指令
这里引入更多的cmake指令,为了编写的方便,将按照cmakeman page 的顺序介绍各种指令,不再推荐使用的指令将不再介绍。
基本指令
PROJECT(HELLO)
指定项目名称,生成的VC项目的名称,使用${HELLO_SOURCE_DIR}
表示项目根目录。
INCLUDE_DIRECTORIES
指定头文件的搜索路径,相当于指定gcc的-I参数
INCLUDE_DIRECTORIES(${HELLO_SOURCE_DIR}/Hello) #增加Hello为include目录
TARGET_LINK_LIBRARIES
添加链接库,相同于指定-l参数
TARGET_LINK_LIBRARIES(demoHello) #将可执行文件与Hello连接成最终文件demo
LINK_DIRECTORIES
动态链接库或静态链接库的搜索路径,相当于gcc的-L参数
LINK_DIRECTORIES(${HELLO_BINARY_DIR}/Hello)#增加Hello为link目录
ADD_DEFINITIONS
向C/C++编译器添加-D定义,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG-DABC)
参数之间用空格分割。如果代码中定义了:
- 1
- 2
- 3
这个代码块就会生效。如果要添加其他的编译器开关,可以通过CMAKE_C_FLAGS
变量和CMAKE_CXX_FLAGS
变量设置。
ADD_DEPENDENCIES*
定义target依赖的其它target,确保在编译本target之前,其它的target已经被构建。ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
ADD_EXECUTABLE
ADD_EXECUTABLE(helloDemo demo.cxx demo_b.cxx)
指定编译,好像也可以添加.o文件,将cxx编译成可执行文件
ADD_LIBRARY
ADD_LIBRARY(Hellohello.cxx) #将hello.cxx编译成静态库如libHello.a
ADD_SUBDIRECTORY
ADD_SUBDIRECTORY(Hello) #包含子目录
ADD_TEST
ENABLE_TESTING
ENABLE_TESTING
指令用来控制Makefile是否构建test目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING()
一般放在工程的主CMakeLists.txt中。
ADD_TEST
指令的语法是:ADD_TEST(testnameExename arg1 arg2 …)
testname是自定义的test名称,Exename可以是构建的目标文件也可以是外部脚本等等,后面连接传递给可执行文件的参数。
如果没有在同一个CMakeLists.txt中打开ENABLE_TESTING()
指令,任何ADD_TEST
都是无效的。比如前面的Helloworld例子,可以在工程主CMakeLists.txt中添加
- 1
- 2
生成Makefile后,就可以运行make test
来执行测试了。
AUX_SOURCE_DIRECTORY
基本语法是:AUX_SOURCE_DIRECTORY(dir VARIABLE)
,作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表,因为目前cmake还不能自动发现新添加的源文件。比如:
- 1
- 2
可以通过后面提到的FOR EACH
指令来处理这个LIST。
CMAKE_MINIMUM_REQUIRED
语法为CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
,
比如:CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果cmake版本小与2.5,则出现严重错误,整个过程中止。
EXEC_PROGRAM
在CMakeLists.txt处理过程中执行命令,并不会在生成的Makefile中执行。具体语法为:
- 1
用于在指定的目录运行某个程序,通过ARGS添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLE
和RETURN_VALUE
分别定义两个变量。
这个指令可以帮助在CMakeLists.txt处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。举个简单的例子,我们要在src目录执行ls命令,并把结果和返回值存下来,可以直接在src/CMakeLists.txt中添加:
- 1
- 2
- 3
- 4
在cmake生成Makefile过程中,就会执行ls命令,如果返回0,则说明成功执行,那么就输出ls *.c
的结果。关于IF
语句,后面的控制指令会提到。
FILE指令
文件操作指令,基本语法为:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
INCLUDE指令
用来载入CMakeLists.txt文件,也用于载入预定义的cmake模块。
- 1
- 2
OPTIONAL参数的作用是文件不存在也不会产生错误,可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入,载入的内容将在处理到INCLUDE语句是直接执行。
INSTALL指令
FIND_指令
FIND_系列指令主要包含一下指令:
- 1
- 2
- 3
- 4
- 5
FIND_LIBRARY示例:
- 1
- 2
- 3
- 4
控制指令
IF指令,基本语法为:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
另外一个指令是ELSEIF
,总体把握一个原则,凡是出现IF的地方一定要有对应的ENDIF
,出现ELSEIF
的地方,ENDIF
是可选的。表达式的使用方法如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
IF(string MATCHES regex)
当给定的变量或者字符串能够匹配正则表达式regex
时为真。比如:
- 1
- 2
- 3
- 1
- 2
- 3
- 4
- 5
- 6
数字比较表达式
- 1
- 2
- 3
- 4
- 5
- 6
按照字母序的排列进行比较。
IF(DEFINED variable)
,如果变量被定义,为真。
一个小例子,用来判断平台差异:
- 1
- 2
- 3
- 4
- 5
上述代码用来控制在不同的平台进行不同的控制,但是阅读起来却并不是那么舒服, ELSE(WIN32)
之类的语句很容易引起歧义。
这就用到了我们在 常用变量 一节提到的CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
开关。可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTSON)
,这时候就可以写成:
- 1
- 2
- 3
- 4
- 5
如果配合ELSEIF使用,可能的写法是这样:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
WHILE指令
WHILE指令的语法是:
- 1
- 2
- 3
- 4
- 5
其真假判断条件可以参考IF指令。
FOREACH指令
FOREACH指令的使用方法有三种形式:
(1)列表
- 1
- 2
- 3
- 4
- 5
像我们前面使用的AUX_SOURCE_DIRECTORY
的例子
- 1
- 2
- 3
- 4
(2)范围
- 1
- 2
- 3
从0到total以1为步进,举例如下:
- 1
- 2
- 3
最终得到的输出是:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
(3)范围和步进
- 1
- 2
- 3
从start开始到stop结束,以step为步进。举例如下:
- 1
- 2
- 3
最终得到的结果是:
- 1
- 2
- 3
- 4
这个指令需要注意的是,直到遇到ENDFOREACH
指令,整个语句块才会得到真正的执行。
复杂的例子:模块的使用和自定义模块
这里将着重介绍系统预定义的Find模块的使用以及自己编写Find模块,系统中提供了其他各种模块,一般情况需要使用INCLUDE
指令显式的调用,FIND_PACKAGE
指令是一个特例,可以直接调用预定义的模块。
其实纯粹依靠cmake本身提供的基本指令来管理工程是一件非常复杂的事情,所以cmake设计成了可扩展的架构,可以通过编写一些通用的模块来扩展cmake。
首先介绍一下cmake提供的FindCURL
模块的使用,然后基于前面的libhello
共享库,编写一个FindHello.cmake
模块。
(一)使用FindCURL模块
在/backup/cmake目录建立t5目录,用于存放CURL的例子。
建立src目录,并建立src/main.c,内容如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
这段代码的作用是通过curl取回www.linux-ren.org
的首页并写入/tmp/curl-test
文件中
建立主工程文件CmakeLists.txt
,如下:
- 1
- 2
建立src/CmakeLists.txt
- 1
现在自然是没办法编译的,我们需要添加curl的头文件路径和库文件。
方法1:
直接通过INCLUDE_DIRECTORIES
和TARGET_LINK_LIBRARIES
指令添加:
我们可以直接在src/CMakeLists.txt
中添加:
- 1
- 2
然后建立build目录进行外部构建即可。
现在要探讨的是使用cmake提供的FindCURL模块。
方法2:
使用FindCURL
模块。向src/CMakeLists.txt
中添加:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
对于系统预定义的Find<name>.cmake
模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量:
- 1
- 2
- 3
可以通过<name>_FOUND
来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。
如果<name>_FOUND
为真,则将<name>_INCLUDE_DIR
加入INCLUDE_DIRECTORIES
,将<name>_LIBRARY
加入TARGET_LINK_LIBRARIES
中。
我们再来看一个复杂的例子,通过<name>_FOUND
来控制工程特性:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
通过判断系统是否提供了JPEG库来决定程序是否支持JPEG功能。
(二)编写属于自己的FindHello模块
接下来在t6示例中演示如何自定义FindHELLO
模块并使用这个模块构建工程。在/backup/cmake/
中建立t6目录,并在其中建立cmake目录用于存放我们自己定义的FindHELLO.cmake
模块,同时建立src目录,用于存放我们的源文件。
1.定义cmake/FindHELLO.cmake
模块
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
针对上面的模块让我们再来回顾一下FIND_PACKAGE
指令:
- 1
前面的CURL例子中我们使用了最简单的FIND_PACKAGE
指令,其实它可以使用多种参数:
QUIET
参数,对应与我们编写的FindHELLO
中的HELLO_FIND_QUIETLY
,如果不指定这个参数,就会执行:
- 1
REQUIRED
参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于FindHELLO.cmake
模块中的HELLO_FIND_REQUIRED
变量。
同样,我们在上面的模块中定义了HELLO_FOUND
,HELLO_INCLUDE_DIR
, HELLO_LIBRARY
变量供开发者在FIND_PACKAGE
指令中使用。
下面建立src/main.c
,内容为:
- 1
- 2
- 3
- 4
- 5
- 6
建立src/CMakeLists.txt
文件,内容如下:
- 1
- 2
- 3
- 4
- 5
- 6
为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的cmake目录)我们在主工程文件 CMakeLists.txt 中加入:
- 1
(三)使用自定义的FindHELLO模块构建工程
仍然采用外部编译的方式,建立build目录,进入目录运行:
- 1
我们可以从输出中看到:
- 1
如果我们把上面的FIND_PACKAGE(HELLO)
修改为FIND_PACKAGE(HELLO QUIET)
,
不会看到上面的输出。接下来就可以使用make命令构建工程,运行:
- 1
可以得到输出
- 1
说明工程成功构建。
(四)如果没有找到hellolibrary呢?
我们可以尝试将/usr/lib/libhello.x
移动到/tmp目录,这样按照FindHELLO
模块的定义,找不到hellolibrary
了,我们再来看一下构建结果:
- 1
仍然可以成功进行构建,但是这时候是没有办法编译的。
修改FIND_PACKAGE(HELLO)
为FIND_PACKAGE(HELLO REQUIRED)
,将hellolibrary
定义为工程必须的共享库。
这时候再次运行
- 1
我们得到如下输出:
- 1
因为找不到libhello.x,所以,整个Makefile生成过程被出错中止。
一些问题
1.怎样区分debug、release版本
建立debug/release两目录,分别在其中执行cmake -D CMAKE_BUILD_TYPE=Debug(或Release)
,需要编译不同版本时进入不同目录执行make
即可:
- 1
- 2
另一种设置方法——例如DEBUG
版设置编译参数DDEBUG
- 1
- 2
- 3
在执行cmake
时增加参数即可,例如cmake -D DEBUG_mode=ON
2.怎样设置条件编译
例如debug
版设置编译选项DEBUG
,并且更改不应改变CMakelist.txt
使用option command
,eg:
- 1
- 2
- 3
- 4
使其生效的方法:首先cmake
生成makefile
,然后make edit_cache
编辑编译选项;Linux下会打开一个文本框,可以更改,改完后再make生成目标文件——emacs不支持make edit_cache
;
局限:这种方法不能直接设置生成的makefile,而是必须使用命令在make前设置参数;对于debug、release版本,相当于需要两个目录,分别先cmake一次,然后分别makeedit_cache一次;
期望的效果:在执行cmake时直接通过参数指定一个开关项,生成相应的makefile
。
四、cmake 基本命令 & 交叉编译配置 & 模块的编写
cmake 基本命令:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
常用 find_package 找 boost 库和头文件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
cmake marco & function
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
//常用变量
CMAKE_SOURCE_DIR ( 相当于工程根目录 )
this is the directory, from which cmake was started, i.e. the top level source directoryCMAKE_CURRENT_SOURCE_DIR
this is the directory where the currently processed CMakeLists.txt is located inPROJECT_SOURCE_DIR ( =CMAKE_SOURCE_DIR 相当于工程根目录 )
contains the full path to the root of your project source directory, i.e. to the nearest directory where CMakeLists.txt contains the PROJECT() commandCMAKE_PREFIX_PATH (用于找 Findxxx.cmake文件,找 库 和 头文件)
Path used for searching by FIND_XXX(), with appropriate suffixes added.CMAKE_INSTALL_PREFIX ( 安装目录 )
Install directory used by install.
If “make install” is invoked or INSTALL is built, this directory is prepended onto all install directories. This variable defaults to /usr/local on UNIX and c:/Program Files on Windows.
例如:cmake .. -DCMAKE_INSTALL_PREFIX=/my/paht/to/install
cmake 配置交叉编译环境:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
注意: 在交叉编译的时候,如果某些 FindXXX.cmake 模块中有类似 pkg_search_module
或者 pkg_check_modules
等命令,则会有点问题:
FindXXX.cmake modules, which rely on executing a binary tool like pkg-config may have problems, since the pkg-config of the target platform cannot be executed on the host. Tools like pkg-config should be used only optional in FindXXX.cmake files.
可以找到相应的模块的 FindXXX.cmake 替换其 pkg-config
如果不想让 pkg-config 被执行,可以试着:
- 1
如果 cmake cache了一些变量,需要重新运行cmake,只需要删除 CMakeCache.txt 文件即可
关于如何编写自己的 Findxxx.cmake 文件:
尊重原作,以下部分复制了该作者的部分文件内容,see link
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70