CMake中的字符串及文件操作
write by 九天雁翎(JTianLing) -- www.jtianling.com
以前写过一篇关于CMake的基本用法的文章(现代软件构建系统的使用 cmake简介...),在最近的使用中,发现一些更复杂情况的使用还有待学习,并且相关的资料也比较少,真正可以依赖的也就(CMake 实践)一文.所以,最近准备将一些非常重要,但是网上资料较少的用法--字符串,文件操作,好好整理一下.目标是,实现一个目录下的多个工程的一次性工程文件生成,而不是为每个工程都建立一个CMake文件,这个需求在我下载一些图书的配套源码时经常碰到,可能大部分的图书作者都习惯于仅仅提供原始源代码文件而不提供工程文件.
特别声明的是,本文并不是一个完备3的CMake教程(以前写的简介也不是),需要相关入门教程的应该去寻找(CMake 实践),那才是你想要的.本文仅仅是作为一些缺失资料的收集,也包括我自己对CMake字符串,文件处理的探索.(真是纯个人探索,网上即使是英文的相关资料也不多,可能毕竟CMake还是不怎么流行吧),仅适合需要的人搜索到这里查看相关信息.
本文内容:
实际上,本文研究的仅仅包括CMake的以下两部分,已经一些if,else中对字符串的判断.在本文中,由于目的不同,主要将会把CMake作为一种普通的编程语言来对待,(其实它本来就是)甚至很多地方根本不建工程,请注意.
string: String operations.
string(REGEX MATCH (regular_expression)
(output variable) (input) [(input)...])
string(REGEX MATCHALL (regular_expression)
(output variable) (input) [(input)...])
string(REGEX REPLACE (regular_expression)
(replace_expression) (output variable)
(input) [(input)...])
string(REPLACE (match_string)
(replace_string) (output variable)
(input) [(input)...])
string(COMPARE EQUAL (string1) (string2) (output variable))
string(COMPARE NOTEQUAL (string1) (string2) (output variable))
string(COMPARE LESS (string1) (string2) (output variable))
string(COMPARE GREATER (string1) (string2) (output variable))
string(ASCII (number) [(number) ...] (output variable))
string(CONFIGURE (string1) (output variable)
[@ONLY] [ESCAPE_QUOTES])
string(TOUPPER (string1) (output variable))
string(TOLOWER (string1) (output variable))
string(LENGTH (string) (output variable))
string(SUBSTRING (string) (begin) (length) (output variable))
string(STRIP (string) (output variable))
string(RANDOM [LENGTH (length)] [ALPHABET (alphabet)]
(output variable))
file: File manipulation command.
file(WRITE filename "message to write"... )
file(APPEND filename "message to write"... )
file(READ filename variable [LIMIT numBytes] [OFFSET offset] [HEX])
file(STRINGS filename variable [LIMIT_COUNT num]
[LIMIT_INPUT numBytes] [LIMIT_OUTPUT numBytes]
[LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUM numBytes]
[NEWLINE_CONSUME] [REGEX regex]
[NO_HEX_CONVERSION])
file(GLOB variable [RELATIVE path] [globbing expressions]...)
file(GLOB_RECURSE variable [RELATIVE path]
[FOLLOW_SYMLINKS] [globbing expressions]...)
file(REMOVE [file1 ...])
file(REMOVE_RECURSE [file1 ...])
file(MAKE_DIRECTORY [directory1 directory2 ...])
file(RELATIVE_PATH variable directory file)
file(TO_CMAKE_PATH path result)
file(TO_NATIVE_PATH path result)
file(DOWNLOAD url file [TIMEOUT timeout] [STATUS status] [LOG log])
以上列表来自于CMake最新的文档:http://www.cmake.org/cmake/help/cmake2.6docs.html
实验方式:
完全依靠CMake的GUI工具,通过configure的输出来获取实验结果.最终的结果就是为我的博客所有源代码重建工程,以后在博客源代码中仅保留CMake的配置文件,不再保留工程, 保留工程很有局限性,比如Linux下没有makefile是很有问题的,个人建工程的时候用的是VS2008,这样VS2005的使用者也会很郁闷(虽然改个版本号就可以用),再加上以前刚开始学习OpenGL的时候非常短视,为了方便自己,将GLUT等库都装在系统目录下,这样的确是方便了自己,但是任何人clone的源代码都无法顺利运行,必须首先装一套配套的库才行,这可不是太好的工程管理方法,这次顺便解决.原来的源代码目录完全按照日期分目录,每天又分很多示例,很有典型意义.
实验过程
到
aux_source_directory(./ SRC_LIST)
的进步.通过aux_source_directory来实现.
现在的问题是,单个工程这个函数很好用了,对于多个工程多级目录的情况呢?答案是file()一族的函数.
Check for working C compiler: cl -- works
Detecting C compiler ABI info
Detecting C compiler ABI info - done
Check for working CXX compiler: cl
Check for working CXX compiler: cl -- works
Detecting CXX compiler ABI info
Detecting CXX compiler ABI info - done
F:/MySrc/blog/2009-10-12/SimpleRectangle/SimpleRectangle.cpp
F:/MySrc/blog/2009-10-14/GLDrawCircle/GLDrawCircle.cpp
F:/MySrc/blog/2009-10-14/GLDrawFourCircle/GLDrawFourCircle.cpp
F:/MySrc/blog/2009-10-14/GLFourCircleAnimation/GLFourCircleAnimation.cpp
F:/MySrc/blog/2009-10-14/GLShadeAnimation/GLShadeAnimation.cpp
F:/MySrc/blog/2009-10-14/WinDrawCircle/WinDrawCircle.cpp
F:/MySrc/blog/2009-10-18/glInterleavedArrays/glInterleavedArrays.cpp
F:/MySrc/blog/2009-10-18/glRectWithArrayDraw/glRectWithArrayDraw.cpp
F:/MySrc/blog/2009-10-18/glRectWithDrawElements/glRectWithDrawElements.cpp
F:/MySrc/blog/2009-10-18/glRectWithMultiDrawElements/glRectWithMultiDrawElements.cpp
F:/MySrc/blog/2009-10-18/glVertexArray/glVertexArray.cpp
F:/MySrc/blog/2009-10-18/glVertexArrayWithColor/glVertexArrayWithColor.cpp
F:/MySrc/blog/2009-10-20/OpenGL/debug/moc_opengl.cpp
F:/MySrc/blog/2009-10-20/OpenGL/main.cpp
F:/MySrc/blog/2009-10-20/OpenGL/opengl.cpp
F:/MySrc/blog/2009-10-21/gl3DCoordinate/gl3DCoordinate.cpp
F:/MySrc/blog/2009-10-21/glSmoothColorPyramid/glSmoothColorPyramid.cpp
F:/MySrc/blog/2009-10-21/glWirePyramid/glWirePyramid.cpp
F:/MySrc/blog/2009-10-25/glCullFace/glCullFace.cpp
F:/MySrc/blog/2009-10-25/glPolygonFace/glPolygonFace.cpp
F:/MySrc/blog/2009-10-25/glViewingTrans/glViewingTrans.cpp
F:/MySrc/blog/2009-10-26/glComposModelTrans/glComposModelTrans.cpp
F:/MySrc/blog/2009-10-26/glComposModelTrans2/glComposModelTrans2.cpp
F:/MySrc/blog/2009-10-26/glScaleSample/glScaleSample.cpp
F:/MySrc/blog/2009-10-28/glOrthoSample/glOrthoSample.cpp
F:/MySrc/blog/2009-10-28/glPerspective/glPerspective.cpp
F:/MySrc/blog/2009-10-29/JTFourTangram/JTTangram.cpp
F:/MySrc/blog/2009-11-11/glHalfTrans/glHalfTrans.cpp
F:/MySrc/blog/2009-11-12/glDepthTest/glDepthTest.cpp
F:/MySrc/blog/2009-11-12/glFogSample/glFogSample.cpp
F:/MySrc/blog/2009-11-9/glSimpleColorPyramid/glSimpleColorPyramid.cpp
F:/MySrc/blog/2009-11-9/LightSimple/LightSimple.cpp
F:/MySrc/blog/2009-9-27/Win32OpenGLTemplate/Win32OpenGLTemplate.cpp
F:/MySrc/blog/2009-9-28/RotateRect/RotateRect.cpp
F:/MySrc/blog/2009-9-29/TestOpenGL/mvarray.cpp
F:/MySrc/blog/JTTangram/JTTangram.cpp
Configuring done
获取了SRC_LIST后,就可以开始处理了
通过STRING( REGEX REPLACE命令来获取到所有的.cpp后缀的文件,因为我的工程中所有的工程都仅包含一个cpp,所以可以这样做
string(REGEX REPLACE (regular_expression)
(replace_expression) (output variable)
的语法不算太好用,学会以后,就主要靠你的正则表达式理解能力了,几乎无所不能.CMake的语法中用()来表示正则表达式的匹配捕获,
用"1"的方式在替换中表示捕获到的字符串(会正则的理解起来应该不难).但是,特别需要注意的是,CMake中需要用/1的形式来传递参数,就像你在C++中需要做的那样.
ADD_EXECUTABLE( ${prjName} ${src} )
以每个cpp的文件名来作为工程名,每个源文件作为此工程需要的文件.
SET_TARGET_PROPERTIES(${prjName} PROPERTIES WIN32_EXECUTABLE "true")
用于指定我的这些工程都是Win32的工程(调用WinMain),默认时时命令行的工程.
上面这么几句就完成了为我10多个工程的一次工程文件生成
此代码中有几个特别点:
2009-10-20那个工程是一个Linux的工程,不要生成VS下Win32的工程,我通过
IF(NOT (src MATCHES ".*2009-10-20.*")) 的形式过滤掉了
IF (NOT (prjName STREQUAL mvarray) )语句用于过滤mvarray所在的工程,因为它是一个命令行的工程(使用Glut管理窗口)
以下是完整的文件(CMake的高亮很漂亮吧:)见我以前fachc2c),完成文件也可在我博客的示例代码中获取.
PROJECT( JTianLingBLog )
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
INCLUDE_DIRECTORIES(./dependes/windows/include/)
LINK_DIRECTORIES(../dependes/windows/lib/)
FILE(GLOB_RECURSE SRC_LIST "*.cpp")
FOREACH(src ${SRC_LIST})
IF(NOT (src MATCHES ".*2009-10-20.*"))
MESSAGE( ${src} Finded)
STRING( REGEX REPLACE ".*/(.*)/.cpp$" "/1" prjName ${src} )
MESSAGE( ${prjName} Added)
ADD_EXECUTABLE( ${prjName} ${src} )
IF (NOT (prjName STREQUAL mvarray) )
SET_TARGET_PROPERTIES(${prjName} PROPERTIES WIN32_EXECUTABLE "true")
ENDIF(NOT (prjName STREQUAL mvarray) )
ENDIF(NOT (src MATCHES ".*2009-10-20.*"))
ENDFOREACH(src)
进一步的应用
PROJECT( IntroToD3D9 )
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
FILE(GLOB_RECURSE srcList "*.cpp")
# fliter duplicates projects
FOREACH(src ${srcList})
MESSAGE("finded: " ${src})
STRING( REGEX REPLACE "(.*/.*)/.*/.cpp$" "/1" dirName ${src} )
LIST(APPEND dirNameList ${dirName})
MESSAGE("directory Finded: " ${dirName} )
ENDFOREACH(src ${srcList})
MESSAGE("..................................................................")
LIST(REMOVE_DUPLICATES dirNameList)
FOREACH(dir ${dirNameList})
MESSAGE( ${dir} " Process begin.....")
STRING( REGEX REPLACE ".*/(.*)" "/1" prjName ${dir} )
FILE(GLOB prjSrcList "${dir}/*.cpp" "${dir}/*.h")
FOREACH(prjSrc ${prjSrcList})
MESSAGE("src glob: " ${prjSrc})
ENDFOREACH(prjSrc)
MESSAGE("before replace Project: " ${prjName} )
STRING( REPLACE " " "" prj ${prjName})
ADD_EXECUTABLE( ${prj} ${prjSrcList} )
MESSAGE("Added Project: " ${prj} )
SET_TARGET_PROPERTIES(${prj} PROPERTIES WIN32_EXECUTABLE "true")
TARGET_LINK_LIBRARIES(${prj} d3d9 d3dx9 winmm)
ENDFOREACH(dir ${dirName})
已经比较复杂了,基本上,看到这里,你会认可其实CMake就是一个专门用于管理工程的脚本语言-_-!
这里讲一下代码中的几个要点,就不多说了:
用FILE(GLOB_RECURSE srcList "*.cpp" 遍历所有.cpp文件(带完整路径)
用STRING( REGEX REPLACE "(.*/.*)/.*/.cpp$" "/1" dirName ${src} )获取到目录名
用LIST(APPEND dirNameList ${dirName})
Can CMake set the Debugging/Working Directory property in Visual Studio projects?
No. The value of this property is not stored in the project files.
It is stored in extra files created by the IDE when a solution is
loaded (VS .NET 2003 uses a hidden .suo file next to the .sln solution
file). The format of these files is not known to CMake and cannot be
generated. In some versions of VS the files are binary and not human
readable.
事实上,我一直以为VS中将工作路径放在suo文件中而不是工程文件是MS干过的最愚蠢的事情之一,导致工作中的使用从来没有达到过从
源代码库中获取源代码及工程后,直接正常编译运行的情况,工作路径必须的额外设置,并且,要知道,是每个工程的工作路径!也因为这个原因,还因为suo是一个只有MS才懂的二进制文件,CMake无法自动改变此值,也就是说,虽然我已经通过CMake为你生成了几十个工程,但是凡事需要用到资源的地方,你都得手动改变工作路径,真无聊啊!!!!还好资源文件都不一样,我将其全部移到projects目录下,这样,只要你在projects目录下生成工程,默认的运行工作路径就是当前工作路径,省去了这样的操作,但是带来的资源及projects混合的情况,那就没有办法了.你们去骂MS吧,我已经尽力了.
事实上,虽然用CMake完成上述工作后,一次就建立了所有需要的工程,几十个几十个的生成,会感觉很爽,其实,花去的时间(主要用于学习CMake诡异特别的语法)要远超过自己一个一个手动生成工程.但是,实在无法忍受手动生成几十个工程的枯燥,习惯重复的工作只能使人越来越懒,没有任何收获...................
enjoy them all!
博客本身完整源代码获取说明
由于篇幅限制,本文一般仅贴出代码的主要关心的部分,代码带工程(或者makefile)完整版(如果有的话)都能用Mercurial在Google Code中下载。文章以博文发表的日期分目录存放,请直接使用Mercurial克隆下库:
https://blog-sample-code.jtianling.googlecode.com/hg/
Mercurial使用方法见《分布式的,新一代版本控制系统Mercurial的介绍及简要入门》
要是仅仅想浏览全部代码也可以直接到google code上去看,在下面的地址:
http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code
原创文章作者保留版权 转载请注明原作者 并给出链接
write by 九天雁翎(JTianLing) -- www.jtianling.com
Posted By 九天雁翎 at 九天雁翎的博客 on 2010年01月15日