日期:2015年2月13日 10:02 星期五 农历 甲午 马年 十二月廿五
修改记录:
2015-02-26 补充10-12条
2015-03-31 补充第13条
正文:
想学Cmake有很长一段时间了,但是一直没有找到好的学习契机。根据以往的经验,在实际项目中去学习一个东西往往是最快的,在实际项目中通过问题与触发学习一个东西往往是印象最深刻的。最近发现了BullseyeCoverage这个可以在VC下就看代码覆盖率的小工具,但是打开这个工具就会产生"compiler out of heap"的告警,逼得我不得不修改编译器的Zm参数,经过多次尝试,无论是用Replace替换还是直接用set指定,抑或是直接在执行cmake命令的时候指定,都没有达到我想要的效果,几经折腾,终于发现cmake还有cache这个东西存在,最终攻克了这个问题。经过这次折腾,我觉得我对cmake已经有了一定的了解,该是对它做一个系统的学习的时候了,于是便有了这篇总结。在写的同时,我希望能自己独立完成一个工程的cmake编写,这样才是真正入门了。
1. 指定cmake的最低版本,这个是必须的,否则cmake将无法执行
cmake_minimum_required(VERSION 2.8)
2. 创建一个空的工程
Project(Gtest)
3. 指定这个工程应该包含哪些头文件,把工程所依赖的所有的头文件全都添加进来即可。
INCLUDE_DIRECTORIES(
"${CMAKE_CURRENT_SOURCE_DIR}/3th/include"
"${CMAKE_CURRENT_SOURCE_DIR}/code/include"
"${CMAKE_CURRENT_SOURCE_DIR}/code/include/base"
)
4. 指定工程所包含的源文件
这个步骤可以有两种方法,一种是通过set命令把源文件列表都放到一个变量中去
set(all_files
"${CMAKE_CURRENT_SOURCE_DIR}/code/src/Library.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/LibraryTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp"
)
还有一种方法,比这个稍微简单一点点,就是利用FILE方法递归获取所有的文件
FILE(GLOB_RECURSE all_files
code/*.cpp
test/*.cpp
)
如果仅仅是生成最简工程,只添加cpp或者c文件就已经足够了,但是很多工程在指定源文件的时候也把各种头文件添加进去了,因为如果你不添加进去,头文件不会显示在工程的list中,如果要查看,只能在External Dependencies中查看,不是很方便。
FILE(GLOB_RECURSE all_files
code/*.cpp
code/*.h
test/*.cpp
test/*.h
3th/*.h
)
把头文件加上以后,所有的头文件就自动添加到"Header Files"中了
5. 指定工程生成的可执行程序的名称以及指定可执行程序依赖的源文件
add_executable(Gtest ${all_files})
====================================================================================================
===================== 通过以上几步,已经可以生成一个最简单的工程了 =================================
====================================================================================================
6. 如果程序依赖其它的外部库的话,还需要指定库的位置,并把库加入链接
link_directories("${CMAKE_CURRENT_SOURCE_DIR}/3th/lib") #这个操作必须放到第5步之前,否则链接的时候会提示找不到库
target_link_libraries(Gtest gtest-vc10) #这个操作必须放在第5步之后,因为它的第一个参数就是第5步产生的
7. 第4步中生成的源文件与头文件都是平铺开放在工程中的,没有按实际的目录结构放置。如果想按目录结构还放置应该怎么办呢?
cmake提供了一个SOURCE_GROUP命令来把文件按照路径归类,别人提供了一个宏,我摘录于此。
MACRO(source_group_by_dir source_files)
SET(sgbd_cur_dir ${CMAKE_CURRENT_SOURCE_DIR})
FOREACH(sgbd_file ${${source_files}})
STRING(REGEX REPLACE ${sgbd_cur_dir}/\(.*\) \\1 sgbd_fpath ${sgbd_file})
STRING(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
STRING(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
IF(MSVC)
string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
ENDIF(MSVC)
IF(sgbd_nogroup)
SET(sgbd_group_name "\\")
ENDIF(sgbd_nogroup)
SOURCE_GROUP(${sgbd_group_name} FILES ${sgbd_file})
ENDFOREACH(sgbd_file)
ENDMACRO(source_group_by_dir)
在add_executable之前调用这个宏进行分组即可。
source_group_by_dir(all_files)
8. cmake是支持跨平台的,对于windows与linux需要区别对待的地方应该怎么做呢?通常这种情况,我们需要在makefile中定义一些预编译宏来解决这个问题,cmake同样是支持的,首先,它内部有一些预编译宏,例如:
if(WIN32)
link_directories("C:\\Program Files\\Microsoft SDKs\\Windows\\v7.0A\\Lib\\")
else()
link_directories(${CMAKE_SOURCE_DIR}/third_party/gcov)
endif()
自己也可以定义一些预编译宏,例如:
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-D_XGW_CSS_SIMU_VC)
add_definitions(-D_XGW_CSS_SIMU_GNU)
add_definitions(-D_SIMU_VC)
add_definitions(-D_HSGW_SIMU_VC)
add_definitions(-D_PHY_BOARD=_BT_PC)
add_definitions(-D_SVR_NAME=_SN_PSCTRL)
这些自定义的预编译宏在代码中就可以直接使用了,因为他们最终会传递到makefile中去
9. cmake对变量的赋值与对变量的引用
cmake通过set命令对变量进行赋值,例如:
set(all_files
"${CMAKE_CURRENT_SOURCE_DIR}/code/src/Library.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/LibraryTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp"
)
后面的参数都用空格或者回车隔开,最终的值放在第一个参数中,是一个list
对变量的引用用$符号,例如上例中的${CMAKE_CURRENT_SOURCE_DIR},但是如果变量用在if条件中,直接使用变量名即可
10. cmake提供了一些内置变量,这些内部变量大多以CMAKE_或者PROJECT_开头,可以通过set命令修改这些变量的默认值,例如
CMAKE_CURRENT_SOURCE_DIR:表示当前的CmakeList.txt所在的目录
CMAKE_SOURCE_DIR与PROJECT_SOURCE_DIR表示工程顶层目录
CMAKE_C_FLAGS与CMAKE_CXX_FLAGS分别表示C与C++编译时的编译选项
11. cmake可以通过message命令打印字符串或者参数的值,例如:
message(STATUS "CMake project files for xgwFT")
message(STATUS "CMake C compile: ${CMAKE_C_COMPILER}")
12. cmake存在一个cache机制,因为这种机制的存在,导致cmake的set命令有一些陷阱,通过cmake的帮助文件可以看出,一般情况下,用户只能对cache中不存在的变量进行set操作,如果对cache中存在的命令进行set操作是不生效的,因为cmake会直接从cache中取这个变量的值。例如说CMAKE_C_FLAGS与CMAKE_CXX_FLAGS等一些cmake的内置变量,它们在cache肯定是存在的,所以直接对它们进行set是不会生效的,需要强制写入cache才会生效。我曾经在这个上面吃了亏,修改编译选项总是修改不成功,折腾了两天才发现这个东东,还是没仔细读帮忙文件啊!看来无论用什么工具,都要学会阅读自带的帮助文件。例如:
set(CMAKE_C_FLAGS " /DWIN32 /D_WINDOWS /W0 /Zm100 /Gm") ----不生效
set(CMAKE_C_FLAGS " /DWIN32 /D_WINDOWS /W0 /Zm100 /Gm" CACHE FORCE "") ----生效
13. 如果想在cmake中调用外部命令,可以通过add_custom_command命令来实现。
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[IMPLICIT_DEPENDS <lang1> depend1 ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM] [APPEND])
add_custom_command(TARGET target
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM])
它的详细用法如上,第一种是根据已有文件生成另外一个文件,在构建时就会调用。第二中是在编译前后可以增加一下外部命令的调用,当使用这种方式时,TARGET一定要填对,一般是ProjectName. 之所以如此,是因为cmake提供的命令,有一些是在执行cmakelist文件的时候执行的,另外有些命令是用于执行makefile的时候执行的,这样它就既能控制makefile的生成,又能控制makefile的执行。
修改记录:
2015-02-26 补充10-12条
2015-03-31 补充第13条
正文:
想学Cmake有很长一段时间了,但是一直没有找到好的学习契机。根据以往的经验,在实际项目中去学习一个东西往往是最快的,在实际项目中通过问题与触发学习一个东西往往是印象最深刻的。最近发现了BullseyeCoverage这个可以在VC下就看代码覆盖率的小工具,但是打开这个工具就会产生"compiler out of heap"的告警,逼得我不得不修改编译器的Zm参数,经过多次尝试,无论是用Replace替换还是直接用set指定,抑或是直接在执行cmake命令的时候指定,都没有达到我想要的效果,几经折腾,终于发现cmake还有cache这个东西存在,最终攻克了这个问题。经过这次折腾,我觉得我对cmake已经有了一定的了解,该是对它做一个系统的学习的时候了,于是便有了这篇总结。在写的同时,我希望能自己独立完成一个工程的cmake编写,这样才是真正入门了。
1. 指定cmake的最低版本,这个是必须的,否则cmake将无法执行
cmake_minimum_required(VERSION 2.8)
2. 创建一个空的工程
Project(Gtest)
3. 指定这个工程应该包含哪些头文件,把工程所依赖的所有的头文件全都添加进来即可。
INCLUDE_DIRECTORIES(
"${CMAKE_CURRENT_SOURCE_DIR}/3th/include"
"${CMAKE_CURRENT_SOURCE_DIR}/code/include"
"${CMAKE_CURRENT_SOURCE_DIR}/code/include/base"
)
4. 指定工程所包含的源文件
这个步骤可以有两种方法,一种是通过set命令把源文件列表都放到一个变量中去
set(all_files
"${CMAKE_CURRENT_SOURCE_DIR}/code/src/Library.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/LibraryTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp"
)
还有一种方法,比这个稍微简单一点点,就是利用FILE方法递归获取所有的文件
FILE(GLOB_RECURSE all_files
code/*.cpp
test/*.cpp
)
如果仅仅是生成最简工程,只添加cpp或者c文件就已经足够了,但是很多工程在指定源文件的时候也把各种头文件添加进去了,因为如果你不添加进去,头文件不会显示在工程的list中,如果要查看,只能在External Dependencies中查看,不是很方便。
FILE(GLOB_RECURSE all_files
code/*.cpp
code/*.h
test/*.cpp
test/*.h
3th/*.h
)
把头文件加上以后,所有的头文件就自动添加到"Header Files"中了
5. 指定工程生成的可执行程序的名称以及指定可执行程序依赖的源文件
add_executable(Gtest ${all_files})
====================================================================================================
===================== 通过以上几步,已经可以生成一个最简单的工程了 =================================
====================================================================================================
6. 如果程序依赖其它的外部库的话,还需要指定库的位置,并把库加入链接
link_directories("${CMAKE_CURRENT_SOURCE_DIR}/3th/lib") #这个操作必须放到第5步之前,否则链接的时候会提示找不到库
target_link_libraries(Gtest gtest-vc10) #这个操作必须放在第5步之后,因为它的第一个参数就是第5步产生的
7. 第4步中生成的源文件与头文件都是平铺开放在工程中的,没有按实际的目录结构放置。如果想按目录结构还放置应该怎么办呢?
cmake提供了一个SOURCE_GROUP命令来把文件按照路径归类,别人提供了一个宏,我摘录于此。
MACRO(source_group_by_dir source_files)
SET(sgbd_cur_dir ${CMAKE_CURRENT_SOURCE_DIR})
FOREACH(sgbd_file ${${source_files}})
STRING(REGEX REPLACE ${sgbd_cur_dir}/\(.*\) \\1 sgbd_fpath ${sgbd_file})
STRING(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
STRING(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
IF(MSVC)
string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
ENDIF(MSVC)
IF(sgbd_nogroup)
SET(sgbd_group_name "\\")
ENDIF(sgbd_nogroup)
SOURCE_GROUP(${sgbd_group_name} FILES ${sgbd_file})
ENDFOREACH(sgbd_file)
ENDMACRO(source_group_by_dir)
在add_executable之前调用这个宏进行分组即可。
source_group_by_dir(all_files)
8. cmake是支持跨平台的,对于windows与linux需要区别对待的地方应该怎么做呢?通常这种情况,我们需要在makefile中定义一些预编译宏来解决这个问题,cmake同样是支持的,首先,它内部有一些预编译宏,例如:
if(WIN32)
link_directories("C:\\Program Files\\Microsoft SDKs\\Windows\\v7.0A\\Lib\\")
else()
link_directories(${CMAKE_SOURCE_DIR}/third_party/gcov)
endif()
自己也可以定义一些预编译宏,例如:
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-D_XGW_CSS_SIMU_VC)
add_definitions(-D_XGW_CSS_SIMU_GNU)
add_definitions(-D_SIMU_VC)
add_definitions(-D_HSGW_SIMU_VC)
add_definitions(-D_PHY_BOARD=_BT_PC)
add_definitions(-D_SVR_NAME=_SN_PSCTRL)
这些自定义的预编译宏在代码中就可以直接使用了,因为他们最终会传递到makefile中去
9. cmake对变量的赋值与对变量的引用
cmake通过set命令对变量进行赋值,例如:
set(all_files
"${CMAKE_CURRENT_SOURCE_DIR}/code/src/Library.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/LibraryTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp"
)
后面的参数都用空格或者回车隔开,最终的值放在第一个参数中,是一个list
对变量的引用用$符号,例如上例中的${CMAKE_CURRENT_SOURCE_DIR},但是如果变量用在if条件中,直接使用变量名即可
10. cmake提供了一些内置变量,这些内部变量大多以CMAKE_或者PROJECT_开头,可以通过set命令修改这些变量的默认值,例如
CMAKE_CURRENT_SOURCE_DIR:表示当前的CmakeList.txt所在的目录
CMAKE_SOURCE_DIR与PROJECT_SOURCE_DIR表示工程顶层目录
CMAKE_C_FLAGS与CMAKE_CXX_FLAGS分别表示C与C++编译时的编译选项
11. cmake可以通过message命令打印字符串或者参数的值,例如:
message(STATUS "CMake project files for xgwFT")
message(STATUS "CMake C compile: ${CMAKE_C_COMPILER}")
12. cmake存在一个cache机制,因为这种机制的存在,导致cmake的set命令有一些陷阱,通过cmake的帮助文件可以看出,一般情况下,用户只能对cache中不存在的变量进行set操作,如果对cache中存在的命令进行set操作是不生效的,因为cmake会直接从cache中取这个变量的值。例如说CMAKE_C_FLAGS与CMAKE_CXX_FLAGS等一些cmake的内置变量,它们在cache肯定是存在的,所以直接对它们进行set是不会生效的,需要强制写入cache才会生效。我曾经在这个上面吃了亏,修改编译选项总是修改不成功,折腾了两天才发现这个东东,还是没仔细读帮忙文件啊!看来无论用什么工具,都要学会阅读自带的帮助文件。例如:
set(CMAKE_C_FLAGS " /DWIN32 /D_WINDOWS /W0 /Zm100 /Gm") ----不生效
set(CMAKE_C_FLAGS " /DWIN32 /D_WINDOWS /W0 /Zm100 /Gm" CACHE FORCE "") ----生效
13. 如果想在cmake中调用外部命令,可以通过add_custom_command命令来实现。
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[IMPLICIT_DEPENDS <lang1> depend1 ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM] [APPEND])
add_custom_command(TARGET target
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM])
它的详细用法如上,第一种是根据已有文件生成另外一个文件,在构建时就会调用。第二中是在编译前后可以增加一下外部命令的调用,当使用这种方式时,TARGET一定要填对,一般是ProjectName. 之所以如此,是因为cmake提供的命令,有一些是在执行cmakelist文件的时候执行的,另外有些命令是用于执行makefile的时候执行的,这样它就既能控制makefile的生成,又能控制makefile的执行。