CMake(https://cmake.org/download/)具有跨平台性,并可生成 native 编译配置文件,在 Linux/Unix 平台,生成 makefile,在苹果平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件
一、工程的编译安装和清理
CMakeLists.txt文件是 cmake 的构建定义文件,文件名是大小写相关的。如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。
内部编译和外部编译
内部编译是指直接在CMakeLists.txt所在的工程目录,运行cmake .命令;这种方式会生成了一些无法自动删除的中间文件。采用内部编译时project指令生成的二个隐式变量都指向工程目录(CMakeLists.txt所在目录)。
外部编译是指单独建立一个build目录,然后在该目录下进行编译,外部编译的过程如下:
1)在工程目录中再建立一个 build 目录,当然可以在任何地方建立 build 目录,不一定必须在工程目录中。
2)进入 build 目录,运行 cmake ..命令。查看一下 build 目录,就会发现了生成了编译需要Makefile 以及其他的中间文件。
3)运行 make 构建工程,就会在当前目录(build 目录)中获得目标文件。
CMAKE_INSTALL_PREFIX
在编译时可以通过该变量来指定安装位置,比如
cmake -DCMAKE_INSTALL_PREFIX=/tmp/shuaifeng/test ..
但是如果直接cmake ..;make;make install,你会发现CMAKE_INSTALL_PREFIX 的默认定义是/usr/local
清理工程
清理工程使用make clean命令,cmake 并不支持 make distclean;因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟踪这些临时文件到底是哪些;因此,没有办法提供一个可靠的 make distclean 方案。
二、常用指令
指令格式为:指令(参数 1 参数 2...)。
其中参数使用括弧括起,参数之间使用空格或分号分开。另外指令是大小写无关的,而参数和变量是大小写相关的。
install指令
1)目标文件的安装
TARGETS参数后面跟的就是我们通过add_executable或者 add_library定义的目标文件,
可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态
库,RUNT#IME特指可执行目标二进制。
DESTINATION参数定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候默认
的变量CMAKE_INSTALL_PREFIX 其实就无效了。如果不使用以/开头的绝对路径,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
install(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]]
[...])
比如:
可执行二进制 myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 目录。
动态库 libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录。
静态库 libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录
install(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic)
2)普通文件的安装
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果
默认不定义权限 PERMISSIONS,安装后的权限为:OWNER_WRITE, OWNER_READ, GROUP_READ,和 WORLD_READ,
即 644 权限。
install(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>]
[OPTIONAL])
比如:
install(FILES hello.h DESTINATION include/hello)
3)非目标文件的可执行程序安装(比如脚本之类)
# 跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:OWNER_EXECUTE,
#GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限
install(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>]
[OPTIONAL])
4)目录的安装
# DIRECTORY 参数后面连接的是所在 Source 目录的相对路径,但务必注意abc 和 abc/有
很大的区别:如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,
代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
# PATTERN参数用于使用正则表达式进行过滤。
# PERMISSIONS参数用于指定 PATTERN 过滤后的文件权限。
install(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>] [EXCLUDE] [PERMISSIONS permissions...]]
[...])
比如:
#将icons目录安装到 <prefix>/share/myproj,
#将scripts/中的内容安装到<prefix>/share/myproj
#不包含目录名为 CVS 的目录,对于 scripts/*文件指定权限为 OWNER_EXECUTE
#OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
install(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
5)脚本的执行
#SCRIPT 参数用于在安装时调用 cmake 脚本文件(也就是<abc>.cmake 文件)
#CODE 参数用于执行 CMAKE 指令,必须以双引号括起来
install([[SCRIPT <file>] [CODE <code>]] [...])
比如:
install(CODE "MESSAGE(\"Sample install message.\")")
6)install示例
#make后安装 runhello.sh到runhello.sh 安装至/<prefix>/bin
install(PROGRAMS runhello.sh DESTINATION bin)
#make后安装 COPYRIGHT/README到/<prefix>/share/doc/cmake
install(FILES COPYRIGHT README DESTINATION share/doc/cmake)
#make后安装doc中的 hello.txt到/<prefix>/share/doc/cmake
install(DIRECTORY doc/ DESTINATION share/doc/cmake)
project指令
project(projectname [CXX] [C] [Java])
可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。
这个指令隐式的定义了两个 cmake 隐式变量:<projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR。同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR变量,他们的值分别跟
<projectname>_BINARY_DIR与<projectname>_SOURCE_DIR一致。为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。如果使用了<projectname>_SOURCE_DIR,修改工程名称后,需要同时修改这些变量。
若之后采用的是内部编译,两个隐式变量目前指的都是工程所目录;若通过外部编译进行工程构建,PROJECT_SOURCE_DIR 仍然指代工程目录,而PROJECT_BINARY_DIR 则指代编译路径
set指令
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
set指令可以用来显式的定义变量,比如我们用到的是 set(SRC_LIST main.c),也可以写成 set(SRC_LIST “main.c”)是没有区别的,但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。这时候就必须使用双引号,如果写成了set(SRC_LIST fu nc.c),就会出现错误,提示你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成:set(SRC_LIST “fu nc.c”)
如果有多个源文件,也可以定义成:set(SRC_LIST main.c t1.c t2.c)。
message指令
message([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:SEND_ERROR(产生错误,生成过程被跳过),SATUS(输出前缀为—的信息),FATAL_ERROR(立即终止所有 cmake 过程)。
add_subdirectory指令
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。最后一个EXCLUDE_FROM_ALL 可选参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)。
比如:
#src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin 目录。如果不进行 bin 目录的指定,
#那么外部编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,
#相当于在编译时将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录。
add_subdirectory(src bin)
这里需要提一下的是 subdirs(dir1 dir2...) 指令,但是这个指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。如果我们在上面的例子中将 add_subdirectory(src bin)修改为 subdirs(src)。那么在 build 目录中将出现一个 src 目录,生成的目标代码 hello 将存放在 src 目录中。
这里需要注意不论是 subdirs 还是add_subdirectory 指令(不论是否指定编译输出目录),我们都可以使用 set 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
add_executable指令
用来生成可执行文件
add_library指令
可构建类型有三种: SHARED(动态库)、STATIC(静态库)和MODULE(在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待)。
EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
比如:
#cmake && make后生成动态库libhello.so
add_library(hello SHARED ${LIBHELLO_SRC})
#cmake && make后生成静态库libhello_static.a,注意若仍使用hello,则不能生成hello.a,这是因为
#hello 作为一个 target 是不能重名的,所以,静态库构建指令无效,若想构建和动态库相同的名字的话
#就需要另一条指令set_target_properties了
add_library(hello_static STATIC ${LIBHELLO_SRC})
set_target_properties指令
set_target_properties(target1 target2 ... PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称(同时构建同名的动态库和静态库);对于动态库,还可以用来指定动态库版本和 API 版本。
与之对应的指令如下,可以获得该property,如果没有这个属性定义,则返回 NOTFOUND
get_target_property(VAR target property)
示例1:让静态库和动态库同名
还是接着add_library的例子:cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,因此避免在构建 libhello.a 时,就会清理掉 libhello.so可以设置如下
set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
#这样,我们就可以同时得到 libhello.so/libhello.a 两个名字相同的库了
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
get_target_property(OUTPUT_VALUE hello_static OUTPUT_NAME)
message(STATUS “This is the hello_static OUTPUT_NAME:”${OUTPUT_VALUE})
示例2:动态库的版本号
按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是
![](https://i-blog.csdnimg.cn/blog_migrate/62ec0f9ec261b663884ba989480e6d5d.png)
为了实现动态库的版本号,仍可以在src/CMakeLists.txt使用如下指令
#VERSION 指代动态库版本,SOVERSION 指代 API 版本
set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1)
include_directories指令
include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:
1) CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以
将添加的头文件搜索路径放在已有路径的前面。
2) 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。
include指令
用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块,载入的内容将在处理到 include 语句是直接执行
#OPTIONAL 参数的作用是文件不存在也不会产生错误。
#你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH 中搜索这个模块并载入。
include(file1 [OPTIONAL])
include(module [OPTIONAL])
find_package指令
用来调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块,你也可以自己定义 Find<name>模块,通过set(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用.
find_package(<name> [major.minor] [QUIET] [NO_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
find_path指令
#VAR 变量代表包含这个文件的路径
find_path(<VAR> name1 path1 path2 ...)
比如:
find_path(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
如果我们设置了环境变量 CMAKE_INCLUDE_PATH。即使没有指定路径,cmake 仍然可以帮我们找到 hello.h 存放的路径:find_path(myHeader hello.h)
#include_directories(/usr/include/hello)
find_path(myHeader hello.h)
if(myHeader)
include_directories(${myHeader})
endif(myHeader)
如果不使用 find_path指令,CMAKE_INCLUDE_PATH 环境变量的设置是没有作用的,你不能指望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>。
find_file指令
#VAR 变量代表找到的文件全路径,包含文件名
find_file(<VAR> name1 path1 path2 ...)
find_program指令
find_program(<VAR> name1 path1 path2 ...)
find_library指令
#VAR 变量表示找到的库全路径,包含库文件名
find_library(<VAR> name1 path1 path2 ...)
比如:
find_library(libX X11 /usr/lib)
if(NOT libX)
message(FATAL_ERROR “libX not found”)
endif(NOT libX)
link_directories和 target_link_libraries指令
为了将目标文件链接到共享库可以引入如下二条指令:
#添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,
#在编译时就需要指定一下这些共享库的路径。
link_directories(directory1 directory2 ...)
#可以用来为 target 添加需要链接的共享库。
target_link_libraries(target library1 <debug | optimized> library2 ...)
比如为可执行文件test添加需要链接的动态库libhello.so。执行cmak && make后,可以使用ldd命令来查看test的链接情况。
target_link_libraries(test hello)
或者
target_link_libraries(test libhello.so)
那如何链接到静态库呢?
target_link_libraries(test libhello.a)
add_definitions指令
向 C/C++编译器添加-D 定义,比如:
add_definitions(-DENABLE_DEBUG -DABC)
如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置。
add_dependencies指令
定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。
add_dependencies(target-name depend-target1 depend-target2 ...)
add_test和enable_testing指令
add_test(testname Exename arg1 arg2 ...)
testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开enable_testing()指令,任何 add_test 都是无效的。
enable_testing 指令用来控制 Makefile 是否构建 test 目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING(),一般情况这个指令放在工程的主CMakeLists.txt 中.
#生成 Makefile 后,就可以运行 make test 来执行测试了
add_test(mytest ${PROJECT_BINARY_DIR}/bin/main)
enable_testing()
aux_source_directory指令
aux_source_directory(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件,比如:
aux_source_directory(. SRC_LIST)
add_executable(main ${SRC_LIST})
当然可以通过forearh指令来处理这个 LIST
cmake_minimum_required指令
cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
比如
#如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止
cmake_minimum_required(VERSION 2.5 FATAL_ERROR)
exec_program指令
在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。这个指令可以帮助在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。
用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过
OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量.
exec_program(Executable [directory in which to run]
[ARGS <arguments to executable>]
[OUTPUT_VARIABLE <var>]
[RETURN_VALUE <var>])
比如要在 src 目录执行 ls 命令,并把结果和返回值存下来,可以直接在CMakeLists.txt 中添加如下指令。在 cmake 生成 Makefile 的过程中,就会执行 ls 命令,如果返回 0,则说明成功执行,那么就输出 ls *.c 的结果。
exec_program(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUELS_RVALUE)
if(not LS_RVALUE)
message(STATUS "ls result: " ${LS_OUTPUT})
endif(not LS_RVALUE)
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)
if和elseif指令
凡是出现 if的地方一定要有对应的endif。出现 elseif 的地方,endif 是可选的。
if(expression)
# THEN section.
COMMAND1(ARGS ...)COMMAND2(ARGS ...)
...
else(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endif(expression)
比如下述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,else(WIN32)之类的语句很容易引起歧义。这就用到了CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 开关。
if(WIN32)
message(STATUS “This is windows.”)
#作一些 Windows 相关的操作
else(WIN32)
message(STATUS “This is not windows”)
#作一些非 Windows 相关的操作
endif(WIN32)
通过如下命令后
set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
上述的if else就可以写成
IF(WIN32)
ELSE()
ENDIF()
while指令
while(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endwhile(condition)
foreach指令
使用方法有三种形式
列表
foreach(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endforeach(loop_var)
范围
FOREACH(loop_var RANGE total) #从 0 到 total 以1为步进
ENDFOREACH(loop_var)
范围和步进
FOREACH(loop_var RANGE start stop [step]) #从 start 开始到 stop 结束,以 step 为步进
ENDFOREACH(loop_var)
三、常用环境变量和CMake变量
1)CMake变量的引用方式
使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值。
![](https://i-blog.csdnimg.cn/blog_migrate/3c680ee30fcb735c2a03c802a670804b.png)
从上面的一段描述可以看出,if需要的是变量名,而不是变量值。
在if 控制语句,变量是直接使用变量名引用,而不需要${}。如果使用了${}去应用变量,其实if 会去判断名为${}所代表的值的变量,那当然是不存在的了。
2)CMake自定义变量的方式
主要有隐式定义和显式定义两种前面提到的 project 指令会隐式的定义<projectname>_BINARY_DIR 和<projectname>_SOURCE_DIR 两个变量。对于显式,使用 set 指令可以构建一个自定义变量了。
3)CMake常用变量
CMAKE_BINARY_DIR 和PROJECT_BINARY_DIR 和 <projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是内部编译,指得就是工程顶层目录,如果是外部编译,指的是工程编译发生的目录。
CMAKE_SOURCE_DIR 和 PROJECT_SOURCE_DIR 和 <projectname>_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径。
CMAKE_CURRRENT_BINARY_DIR
如果是内部编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是外部 编译,他指的是 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 指令定义的项目名称。
4)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 变量,使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),则可以通过这些变量就行弥补。
虽然可以使用include_directories指令,比如include_directories(/usr/include/hello)来告诉工程使用/usr/include/hello这个头文件目录,
但是为了将程序更智能一点,我们可以使用 CMAKE_INCLUDE_PATH 来进行,使用 bash 的方法为:
export CMAKE_INCLUDE_PATH=/usr/include/hello
5)系统信息
CMAKE_MAJOR_VERSION
CMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSION
CMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSION
CMAKE 补丁等级,比如 2.4.6 中的 6
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
6)主要的开关选项
CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
用来控制 if else 语句的书写方式
BUILD_SHARED_LIBS
这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,默认编译生成的库都是静态库。如果执行如下命令后,默认生成的为动态库。
set(BUILD_SHARED_LIBS ON)
CMAKE_C_FLAGS
设置 C 编译选项,也可以通过指令 add_definitions()添加。
CMAKE_CXX_FLAGS
设置 C++编译选项,也可以通过指令 add_definitions()添加
四、预定义Find 模块的使用以及自定义 Find 模块
系统中提供了其他各种模块,一般情况需要使用include指令显式的调用,find_package指令是一个特例,可以直接调用预定义的模块。
预定义Find模块的使用(以cmake提供的FindCURL模块为例)
在工程目录建CMakeLists.txt,其内容如下:
PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)
在工程目录建src/main.c,内容如下:
/*这段代码的作用是通过 curl 取回 www.linux-ren.org 的首页并写入/tmp/curl-test
文件中*/
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
int written = fwrite(ptr, size, nmemb, (FILE *)fp);
return written;
}
int main()
{
const char * path = “/tmp/curl-test”;
const char * mode = “w”;
fp = fopen(path, mode);
curl_global_init(CURL_GLOBAL_ALL);
CURLcode res;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
在工程目录建src/CMakeLists.txt,其内容如下:
#添加 curl 的头文件路径和库文件;使用 FindCURL 模块:
#可以通过<name>_FOUND变量来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、
#给出提醒或者中止编译,下面的例子就是报出致命错误并终止构建。
#如果<name>_FOUND 为真,则将<name>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES,
#将<name>_LIBRARY 加入 TARGET_LINK_LIBRARIES 中。
find_package(CURL)
if(CURL_FOUND)
include_directories(${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})
else(CURL_FOUND)
message(FATAL_ERROR ”CURL library not found”)
endif(CURL_FOUND)
ADD_EXECUTABLE(curltest main.c)
对于系统预定义的 Find<name>.cmake 模块,每一个模块都会定义以下几个变量:
<name>_FOUND
<name>_INCLUDE_DIR or <name>_INCLUDES
<name>_LIBRARY or <name>_LIBRARIES
自定义Find模块(比如自定义FindHELLO 模块)
在工程目录下建立src/main.c
#include <hello.h>
int main()
{
HelloFunc(); //比如可以放到hello.c/hello.h中
return 0;
}
在工程目录下建立 src/CMakeLists.txt 文件
find_package(HELLO)
if(HELLO_FOUND)
add_executable(hello main.c)
include_directories(${HELLO_INCLUDE_DIR})
target_link_libraries(hello ${HELLO_LIBRARY})
endif(HELLO_FOUND)
在工程目录下建立cmake/FindHELLO.cmake
find_path(HELLO_INCLUDE_DIR hello.h /usr/include/hello /usr/local/include/hello)
find_library(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib)
if (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
set(HELLO_FOUND TRUE)
endif (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
if (HELLO_FOUND)
if (NOT HELLO_FIND_QUIETLY)
message(STATUS "Found Hello: ${HELLO_LIBRARY}")
endif (NOT HELLO_FIND_QUIETLY)
else (HELLO_FOUND)
if (HELLO_FIND_REQUIRED)
message(FATAL_ERROR "Could not find hello library")
endif (HELLO_FIND_REQUIRED)
endif (HELLO_FOUND)
针对上面的模块让再来看一下 find_package 指令:
find_package(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS]
[componets...]])
其中的QUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO_FIND_QUIETLY,如果在使用find_package命令时不指定这个参数,就会执行:
message(STATUS "Found Hello: ${HELLO_LIBRARY}")。
REQUIRED 对应于FindHELLO.cmake 模块中的 HELLO_FIND_REQUIRED 变量。其含义是指这个共享库是否是工程必须的,如果在find_package命令中使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则整个 Makefile 生成过程被出错中止。
同样,在上面的模块中定义了 HELLO_FOUND, HELLO_INCLUDE_DIR, HELLO_LIBRARY 变量供开发者在 FIND_PACKAGE 指令中使用。
如何让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录)
在工程目录建立的CMakeLists.txt 中加入:
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)