cmake 已经开发了 5,6 年的时间,如果没有 KDE4,也许不会有人或者 Linux 发行版 本重视 cmake,因为除了 Kitware 似乎没有人使用它。通过 KDE4 的选型和开发,cmake 逐渐进入了人们的视线,在实际的使用过程中,cmake 的优势也逐渐的被大家所认识,至 少 KDE 的开发者们给予了 cmake 极高的评价,同时庞大的 KDE 项目使用 cmake 来作为构 建工具也证明了 cmake 的可用性和大项目管理能力。 所以,cmake 应该感谢 KDE,也正因为如此,cmake 的开发者投入了 KDE 从 autotools 到 cmake 的迁移过程中,并相当快速和顺利的完成了迁移,现在整个 KDE4 开 发版本全部使用 cmake 构建。 这也是促使我们学习 cmake 的原因,首先 cmake 被接受并成功应用,其次,cmake 的优势在实际使用中不断的体现出来。 我们为什么不来认识一下这款优秀的工程构建工具呢? 在 2006 年 KDE 大会,听 cmake 开发者当面介绍了 cmake 之后,我就开始关注 cmake,并将 cmake 纳入了 Everest 发行版,作为系统默认组件。最近 QT-4.3 也正式进 入了 Everest 系统,为 KDE4 构建完成了准备工作。 但是,在学习 cmake 的过程中,发现官方的文档非常的少,而且错误也较多,比如: 在介绍 Find模块编写的文档中,模块名称为 FOO,但是后面却出现了 Foo_FIND_QUIETLY 的定义,这显然是错误的,这样的定义永远不可能有效,正确的定义 是 FOO_FIND_QUIETLY。种种原因,促使我开始写一份“面向使用和实用”的 cmake 文档, 也就是本教程《cmake 实践》(Cmake Practice) 本文档是边学习边编写的成果,更像是一个学习笔记和 Tutorial,因此难免有失误 或者理解不够透彻的地方,比如,我仍然不能理解为什么绝大部分使用变量的情况要通过$ {}引用,而在 IF 语句中却必须直接使用变量名。也希望能够有 cmake 的高手来指点迷津。 补:从 cmake 的 maillist,我找到了一些答案,原文是: The IF(var)' or IF(NOT var)’ command expects var' to be the name of a variable. This is stated in CMake's manual. So, for your situation IF(${libX})’ is the same as IF(/usr/lib/xorg)' and then CMake will check the value of the variable named /usr/lib/xorg’.也就是说 IF 需要的是变量名而不是变量值 这个文档是开放的,开放的目的是为了让更多的人能够读到并且能够修改,任何人都 可以对它作出修改和补充,但是,为了大家都能够获得你关于 cmake 的经验和积累,如果 你现错误或者添加了新内容后,请务必 CC 给我一份,让我们共同把 cmake 掌握的更好。
cd /backup/cmake
mkdir t1
cd t1
在 t1 目录建立 main.c 和 CMakeLists.txt(注意文件名大小写):
main.c 文件内容:
//main.c
#include <stdio.h>
int main()
{
printf(“Hello World from t1 Main!\n”);
return 0;
}
CmakeLists.txt 文件内容:
PROJECT (HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello SRC_LIST)
2,开始构建
所有的文件创建完成后,t1 目录中应该存在 main.c 和 CMakeLists.txt 两个文件
接下来我们来构建这个工程,在这个目录运行:
cmake .(注意命令后面的点号,代表本目录)。
输出大概是这个样子:
-- Check for working C compiler:/usr/bin/gcc
-- Check for working C compiler:/usr/bin/gcc -- works
-- Check size of void*-- Check size of void*- done
-- Check for working CXX compiler:/usr/bin/c++-- Check for working CXX compiler:/usr/bin/c++-- works
-- This is BINARY dir /backup/cmake/t1
-- This is SOURCE dir /backup/cmake/t1
-- Configuring done
-- Generating done
-- Build files have been written to:/backup/cmake/t1
再让我们看一下目录中的内容:
你会发现,系统自动生成了:
CMakeFiles, CMakeCache.txt, cmake_install.cmake 等文件,并且生成了
Makefile.
现在不需要理会这些文件的作用,以后你也可以不去理会。最关键的是,它自动生成了
Makefile.
然后进行工程的实际构建,在这个目录输入 make 命令,大概会得到如下的彩色输出:
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.o
Linking C executable hello
[100%] Built target hello
如果你需要看到 make 构建的详细过程,可以使用 make VERBOSE=1 或者 VERBOSE=1
make 命令来进行构建。
这时候,我们需要的目标文件 hello 已经构建完成,位于当前目录,尝试运行一下:
./hello
得到输出:
Hello World from Main
恭喜您,到这里为止您已经完全掌握了 cmake 的使用方法。
3,简单的解释: 我们来重新看一下 CMakeLists.txt,这个文件是 cmake 的构建定义文件,文件名 是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个 CMakeLists.txt。(关于多目录构建,后面我们会提到,这里不作过多解释)。 上面例子中的 CMakeLists.txt 文件内容如下: PROJECT (HELLO) SET(SRC_LIST main.c) MESSAGE(STATUS "This is BINARY dir " H E L L O B I N A R Y D I R ) M E S S A G E ( S T A T U S " T h i s i s S O U R C E d i r " {HELLO_BINARY_DIR}) MESSAGE(STATUS "This is SOURCE dir " HELLOBINARYDIR)MESSAGE(STATUS"ThisisSOURCEdir"{HELLO_SOURCE_DIR}) ADD_EXECUTABLE(hello ${SRC_LIST}) PROJECT 指令的语法是: PROJECT(projectname [CXX] [C] [Java]) 你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的, 默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量: _BINARY_DIR 以及_SOURCE_DIR,这里就是 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR(所以 CMakeLists.txt 中两个 MESSAGE 指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所 在路径/backup/cmake/t1,后面我们会讲到外部编译,两者所指代的内容会有所不同。 同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR 变量,他们的值分别跟 HELLO_BINARY_DIR 与 HELLO_SOURCE_DIR 一致。 为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即 使修改了工程名称,也不会影响这两个变量。如果使用了 _SOURCE_DIR,修改工程名称后,需要同时修改这些变量。 SET 指令的语法是: SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]]) 现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。 比如我们用到的是 SET(SRC_LIST main.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 过程. 我们在这里使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR。 ADD_EXECUTABLE(hello S R C L I S T ) 定义了这个工程会生成一个文件名为 h e l l o 的可执行文件,相关的源文件是 S R C L I S T 中定义的源文件列表,本例中你也可以直接写成 A D D E X E C U T A B L E ( h e l l o m a i n . c ) 。在本例我们使用了 {SRC_LIST}) 定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中 定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.c)。 在本例我们使用了 SRCLIST)定义了这个工程会生成一个文件名为hello的可执行文件,相关的源文件是SRCLIST中定义的源文件列表,本例中你也可以直接写成ADDEXECUTABLE(hellomain.c)。在本例我们使用了{}来引用变量,这是 cmake 的变量应用方式,但是,有一些例外,比 如在 IF 控制语句,变量是直接使用变量名引用,而不需要 。如果使用了 {}。如果使用了 。如果使用了{}去应用变 量,其实 IF 会去判断名为 所代表的值的变量,那当然是不存在的了。将本例改写成一个最简化的 C M a k e L i s t s . t x t : P R O J E C T ( H E L L O ) A D D E X E C U T A B L E ( h e l l o m a i n . c ) 4 ,基本语法规则前面提到过, c m a k e 其实仍然要使用” c m a k e 语言和语法”去构建,上面的内容就是所谓的” c m a k e 语言和语法”,最简单的语法规则是: 1 ,变量使用 {}所代表的值的变量,那当然是不存在的了。 将本例改写成一个最简化的 CMakeLists.txt: PROJECT(HELLO) ADD_EXECUTABLE(hello main.c) 4,基本语法规则 前面提到过,cmake 其实仍然要使用”cmake 语言和语法”去构建,上面的内容就是所谓的 ”cmake 语言和语法”,最简单的语法规则是: 1,变量使用 所代表的值的变量,那当然是不存在的了。将本例改写成一个最简化的CMakeLists.txt:PROJECT(HELLO)ADDEXECUTABLE(hellomain.c)4,基本语法规则前面提到过,cmake其实仍然要使用”cmake语言和语法”去构建,上面的内容就是所谓的”cmake语言和语法”,最简单的语法规则是:1,变量使用{}方式取值,但是在 IF 控制语句中是直接使用变量名 2,指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或分号分开。 以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成: ADD_EXECUTABLE(hello main.c func.c)或者 ADD_EXECUTABLE(hello main.c;func.c) 3,指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。 上面的 MESSAGE 指令我们已经用到了这条规则: MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR}) 也可以写成: MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”) 这里需要特别解释的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的。 hello 定义了可执行文件的文件名,你完全可以写成: ADD_EXECUTABLE(t1 main.c) 编译后会生成一个 t1 可执行文件。 5,关于语法的疑惑 cmake 的语法还是比较灵活而且考虑到各种情况,比如 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”) 此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成 ADD_EXECUTABLE(t1 main),cmake 会自动的在本目录查找 main.c 或者 main.cpp 等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c 一个 main. 同时参数也可以使用分号来进行分割。 下面的例子也是合法的: ADD_EXECUTABLE(t1 main.c t1.c)可以写成 ADD_EXECUTABLE(t1 main.c;t1.c). 我们只需要在编写 CMakeLists.txt 时注意形成统一的风格即可。 6,清理工程: 跟经典的 autotools 系列工具一样,运行: make clean 即可对构建结果进行清理。 7,问题?问题! “我尝试运行了 make distclean,这个指令一般用来清理构建过程中产生的中间文件的, 如果要发布代码,必然要清理掉所有的中间文件,但是为什么在 cmake 工程中这个命令是 无效的?” 是的,cmake 并不支持 make distclean,关于这一点,官方是有明确解释的: 因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟 踪这些临时文件到底是哪些。因此,没有办法提供一个可靠的 make distclean 方案。 Some build trees created with GNU autotools have a “make distclean” target that cleans the build and also removes Makefiles and other parts of the generated build system. CMake does not generate a “make distclean” target because CMakeLists.txt files can run scripts and arbitrary commands; CMake has no way of tracking exactly which files are generated as part of running CMake. Providing a distclean target would give users the false impression that it would work as expected. (CMake does generate a “make clean” target to remove files generated by the compiler and linker.) A “make distclean” target is only necessary if the user performs an in-source build. CMake supports in-source builds, but we strongly encourage users to adopt the notion of an out-of-source build. Using a build tree that is separate from the source tree will prevent CMake from generating any files in the source tree. Because CMake does not change the source tree, there is no need for a distclean target. One can start a fresh build by deleting the build tree or creating a separate build tree. 同时,还有另外一个非常重要的提示,就是:我们刚才进行的是内部构建(in-source build),而 cmake 强烈推荐的是外部构建(out-of-source build)。
目标文件的安装: INSTALL(TARGETS targets… [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION
] [PERMISSIONS permissions…] [CONFIGURATIONS
[Debug|Release|…]] [COMPONENT ] [OPTIONAL] ] […])
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的
目标文件,可能是可执行二进制、动态库、静态库。 目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY
特指动态库,RUNTIME 特指可执行目标二进制。 DESTINATION
定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用
CMAKE_INSTALL_PREFIX 来 定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
C M A K E I N S T A L L P R E F I X / < D E S T I N A T I O N 定义的路径 > 举个简单的例子: I N S T A L L ( T A R G E T S m y r u n m y l i b m y s t a t i c l i b R U N T I M E D E S T I N A T I O N b i n L I B R A R Y D E S T I N A T I O N l i b A R C H I V E D E S T I N A T I O N l i b s t a t i c ) 上面的例子会将:可执行二进制 m y r u n 安装到 {CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径> 举个简单的例子: INSTALL(TARGETS myrun mylib mystaticlib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION libstatic ) 上面的例子会将: 可执行二进制 myrun 安装到 CMAKEINSTALLPREFIX/<DESTINATION定义的路径>举个简单的例子:INSTALL(TARGETSmyrunmylibmystaticlibRUNTIMEDESTINATIONbinLIBRARYDESTINATIONlibARCHIVEDESTINATIONlibstatic)上面的例子会将:可执行二进制myrun安装到{CMAKE_INSTALL_PREFIX}/bin 目录 动态库 libmylib
安装到
C M A K E I N S T A L L P R E F I X / l i b 目录静态库 l i b m y s t a t i c l i b 安装到 {CMAKE_INSTALL_PREFIX}/lib 目录 静态库 libmystaticlib 安装到 CMAKEINSTALLPREFIX/lib目录静态库libmystaticlib安装到{CMAKE_INSTALL_PREFIX}/libstatic 目录 特别注意的是你不需要关心 TARGETS
具体生成的路径,只需要写上 TARGETS 名称就可以 了。 普通文件的安装: INSTALL(FILES files…
DESTINATION
[PERMISSIONS permissions…] [CONFIGURATIONS
[Debug|Release|…]] [COMPONENT ] [RENAME ]
[OPTIONAL]) 可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果 默认不定义权限
PERMISSIONS,安装后的权限为: OWNER_WRITE, OWNER_READ, GROUP_READ,和
WORLD_READ,即 644 权限。 非目标文件的可执行程序安装(比如脚本之类): INSTALL(PROGRAMS
files… DESTINATION
[PERMISSIONS permissions…]
[CONFIGURATIONS [Debug|Release|…]] [COMPONENT ] [RENAME
] [OPTIONAL]) 跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为: OWNER_EXECUTE,
GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限 目录的安装: INSTALL(DIRECTORY
dirs… DESTINATION
[FILE_PERMISSIONS permissions…]
[DIRECTORY_PERMISSIONS permissions…] [USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|…]] [COMPONENT ]
[[PATTERN | REGEX ] [EXCLUDE] [PERMISSIONS
permissions…]] […]) 这里主要介绍其中的 DIRECTORY、PATTERN 以及 PERMISSIONS
参数。 DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必注意: abc 和 abc/有很大的区别。
如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,
代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。 PATTERN 用于使用正则表达式进行过滤,PERMISSIONS
用于指定 PATTERN 过滤后的文件 权限。 我们来看一个例子: INSTALL(DIRECTORY icons scripts/
DESTINATION share/myproj PATTERN “CVS” EXCLUDE PATTERN “scripts/*”
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE
GROUP_READ) 这条指令的执行结果是: 将 icons 目录安装到 /share/myproj,将
scripts/中的内容安装到 /share/myproj 不包含目录名为 CVS 的目录,对于
scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE
GROUP_READ. 安装时 CMAKE 脚本的执行:
ENDIF.出现 ELSEIF 的地方,ENDIF 是可选的。 表达式的使用方法如下: IF(var),如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或 _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) 当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如: IF(“hello” MATCHES “ell”) MESSAGE(“true”) ENDIF(“hello” MATCHES “ell”) IF(variable LESS number) IF(string LESS number) IF(variable GREATER number) IF(string GREATER number) IF(variable EQUAL number) 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) 按照字母序的排列进行比较. IF(DEFINED variable),如果变量被定义,为真。 一个小例子,用来判断平台差异: IF(WIN32) MESSAGE(STATUS “This is windows.”) #作一些 Windows 相关的操作 ELSE(WIN32) MESSAGE(STATUS “This is not windows”) #作一些非 Windows 相关的操作 ENDIF(WIN32) 上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服, ELSE(WIN32)之类的语句很容易引起歧义。 这就用到了我们在“常用变量”一节提到的 CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS 开 关。 可以 SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) 这时候就可以写成: IF(WIN32) ELSE() ENDIF() 如果配合 ELSEIF 使用,可能的写法是这样: IF(WIN32) #do something related to WIN32 ELSEIF(UNIX) #do something related to UNIX ELSEIF(APPLE) #do something related to APPLE ENDIF(WIN32) 2,WHILE WHILE 指令的语法是: WHILE(condition) COMMAND1(ARGS …) COMMAND2(ARGS …) … ENDWHILE(condition) 其真假判断条件可以参考 IF 指令。 3,FOREACH FOREACH 指令的使用方法有三种形式: 1,列表 FOREACH(loop_var arg1 arg2 …) COMMAND1(ARGS …) COMMAND2(ARGS …) … ENDFOREACH(loop_var) 像我们前面使用的 AUX_SOURCE_DIRECTORY 的例子 AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(F S R C L I S T ) M E S S A G E ( {SRC_LIST}) MESSAGE( SRCLIST)MESSAGE({F}) ENDFOREACH(F) 2,范围 FOREACH(loop_var RANGE total) ENDFOREACH(loop_var) 从 0 到 total 以1为步进 举例如下: FOREACH(VAR RANGE 10) MESSAGE( V A R ) E N D F O R E A C H ( V A R ) 最终得到的输出是: 012345678910 3,范围和步进 F O R E A C H ( l o o p v a r R A N G E s t a r t s t o p [ s t e p ] ) E N D F O R E A C H ( l o o p v a r ) 从 s t a r t 开始到 s t o p 结束,以 s t e p 为步进,举例如下 F O R E A C H ( A R A N G E 5153 ) M E S S A G E ( {VAR}) ENDFOREACH(VAR) 最终得到的输出是: 0 1 2 3 4 5 6 7 8 9 10 3,范围和步进 FOREACH(loop_var RANGE start stop [step]) ENDFOREACH(loop_var) 从 start 开始到 stop 结束,以 step 为步进, 举例如下 FOREACH(A RANGE 5 15 3) MESSAGE( VAR)ENDFOREACH(VAR)最终得到的输出是:0123456789103,范围和步进FOREACH(loopvarRANGEstartstop[step])ENDFOREACH(loopvar)从start开始到stop结束,以step为步进,举例如下FOREACH(ARANGE5153)MESSAGE({A}) ENDFOREACH(A) 最终得到的结果是: 5 8 11 14 这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。 小结: 本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句 等,特别需要注意的是,在控制语句条件中使用变量,不能用KaTeX parse error: Expected 'EOF', got '#' at position 522: …c/main.c,内容如下: #̲include <curl/c…{CURL_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY}) ELSE(CURL_FOUND) MESSAGE(FATAL_ERROR ”CURL library not found”) ENDIF(CURL_FOUND) 对于系统预定义的 Find.cmake 模块,使用方法一般如上例所示: 每一个模块都会定义以下几个变量 • _FOUND • _INCLUDE_DIR or _INCLUDES • _LIBRARY or _LIBRARIES 你可以通过_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭 某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。 如果_FOUND 为真,则将_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES, 将_LIBRARY 加入 TARGET_LINK_LIBRARIES 中。 我们再来看一个复杂的例子,通过_FOUND 来控制工程特性: SET(mySources viewer.c) SET(optionalSources) SET(optionalLibs) FIND_PACKAGE(JPEG) IF(JPEG_FOUND) SET(optionalSources ${optionalSources} jpegview.c) INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} ) SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} ) ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT) ENDIF(JPEG_FOUND) IF(PNG_FOUND) SET(optionalSources ${optionalSources} pngview.c) INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} ) SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} ) ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT) ENDIF(PNG_FOUND) ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} ) TARGET_LINK_LIBRARIES(viewer ${optionalLibs} 通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。 二,编写属于自己的 FindHello 模块。 我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。 接下来,我们在 t6 示例中演示如何自定义 FindHELLO 模块并使用这个模块构建工程: 请在建立/backup/cmake/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己 定义的 FindHELLO.cmake 模块,同时建立 src 目录,用于存放我们的源文件。 1,定义 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( [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets…]]) 前面的 CURL 例子中我们使用了最简单的 FIND_PACKAGE 指令,其实他可以使用多种参数, QUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO_FIND_QUIETLY,如果不指定 这个参数,就会执行: MESSAGE(STATUS "Found Hello: KaTeX parse error: Expected 'EOF', got '#' at position 254: …rc/main.c,内容为: #̲include <hello.…{HELLO_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY}) ENDIF(HELLO_FOUND) 为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录) 我们在主工程文件 CMakeLists.txt 中加入: SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 三,使用自定义的 FindHELLO 模块构建工程 仍然采用外部编译的方式,建立 build 目录,进入目录运行: cmake … 我们可以从输出中看到: Found Hello: /usr/lib/libhello.so 如果我们把上面的 FIND_PACKAGE(HELLO)修改为 FIND_PACKAGE(HELLO QUIET),则 不会看到上面的输出。 接下来就可以使用 make 命令构建工程,运行: ./src/hello 可以得到输出 Hello World。 说明工程成功构建。 四,如果没有找到 hello library 呢? 我们可以尝试将/usr/lib/libhello.x 移动到/tmp 目录,这样,按照 FindHELLO 模块 的定义,就找不到 hello library 了,我们再来看一下构建结果: cmake … 仍然可以成功进行构建,但是这时候是没有办法编译的。 修改 FIND_PACKAGE(HELLO)为 FIND_PACKAGE(HELLO REQUIRED),将 hello library 定义为工程必须的共享库。 这时候再次运行 cmake … 我们得到如下输出: CMake Error: Could not find hello library. 因为找不到 libhello.x,所以,整个 Makefile 生成过程被出错中止。 小结: 在本节中,我们学习了如何使用系统提供的 Find模块并学习了自己编写 Find模块以及如何在工程中使用这些模块。 后面的章节,我们会逐渐学习更多的 cmake 模块使用方法以及用 cmake 来管理 GTK 和 QT4 工程。
文章浏览阅读372次。 First, some general definitions (specific to iOS):Static library - a unit of code linked at compile time, which does not change. However, iOS static libraries are not allowed to conta..._ios static dynamic