学习了makefile的规则之后,感觉makefile的规则还是有些复杂的,要建立一个比较复杂的项目,makefile还是有一定的工作量的。但是现在已经有了很多工具来将makefile给封装起来,减少工作量。现在要介绍的一个就是cmake,很多开源的项目也是使用cmake的,还有一个autotool工具,实现类似的功能。下面的文章就是介绍cmake的使用的。
1.http://www.hahack.com/codes/cmake/
2.https://www.ibm.com/developerworks/cn/linux/l-cn-cmake/
autotools : http://blog.csdn.net/initphp/article/details/43705765
还有一篇介绍cmake的:
http://www.cnblogs.com/coderfenghc/archive/2013/01/20/2846621.html
http://www.cnblogs.com/52php/p/5681745.html 这一篇比较系统
官方文档指导:
https://cmake.org/cmake/help/latest/guide/tutorial/index.html#guide:CMake%20Tutorial
有时候有些项目需要依赖本地的库,所以编译的时候需要检测需要的库是否已经安装。cmake提供了库的查找功能,链接如下:https://blog.csdn.net/bytxl/article/details/50637277 cmake的官网也有相应的命令介绍。
find_library的介绍:https://www.cnblogs.com/coderfenghc/archive/2012/07/14/2591135.html
cmake文档的翻译:
http://www.cnblogs.com/coderfenghc/tag/cmake/ 这里很多函数介绍的比较详细。
总结:
一:我们从最顶层目录开始介绍:
比如我们要建立一个名字为demo的工程,在demo目录下,我们可以建立src目录来装source code,建立build目录来用于编译(因为编译会生成很多中间文件,我们不想中间文件和source code混合在一起,所以建立build目录来专门用于build)。
在build目录下执行如下命令:
cmake ../
make -jn
make install
或者也可以直接用cmake的命令来编译:
cmake --build .
编译某个target cmake --build . --target xx.so 要make clean的话,就--target clean 也可以install
src目录下放置我们的source code。在demo目录下创建最顶层的CMakelists.txt,定义一些全局的特性,比如说cflags等。还需要声明该project :project(demo)
用add_subdirectory()来指定下面需要编译的子目录。
最顶层的CMakeLists.txt会自动定义两个变量 : <projectname>_BINARY_DIR 以及 <projectname>_SOURCE_DIR
比如说前面写了 project (demo)
则这两个变量就是demo_BINARY_DIR
和 demo_SOURCE_DIR
。 同时cmake系统也帮助我们预定义了PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
变量,他们的值分别跟 demo_BINARY_DIR
与 demo_SOURCE_DIR
一致,所以为了统一,我们一般使用 PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
,就是指代的当前目录。
二:code目录基本的cmake语法
1.aux_source_directory(. SRC_LIST)
//会将该目录下的源文件名称列表赋值给SRC_LIST.
2. include_directories(/usr/local/include ${SRC_INCLUDE})
//可以分行,指定头文件的搜索路径
3.link_directories(${LIB_DIR})
//指定需要的库的目录
3. target_link_libraries(target_name xxlib xxxlib)
//要编译的目标target_name所需要的库的名称。
4. link_libraries(xxlib xxxlib)
//设置所有目标需要链接的库
5. add_library(target_name SHARED ${SRC_DIR})
//编译的目标的名称,表明目标是动态库,后面跟着源文件列表。如果编译静态库,为STATIC
6. add_executable(target_name ${binary_src})
//编译可执行文件,后面跟着源文件列表
7. target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
这个接口是说明
本target的头文件是放在本目录的,如果其他模块需要利用本library, 需要将本目录添加到include目录,
同时指定本target所需要的头文件的存储路径。
三.常用的默认变量:
1.除了常用的PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR之外,还定义了一系列常用的变量:
a.CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
b.CMAKE_C_COMPILER
指定C编译器,通常,CMake运行时能够自动检测C语言编译器。进行嵌入式系统开发时,通常需要设置此变量,指定交叉编译器。
c.CMAKE_CXX_COMPILER
指定C++编译器
d.CMAKE_C_FLAGS && CMAKE_CXX_FLAGS
指定编译C文件时编译选项,比如-g指定产生调试信息。也可以通过add_definitions命令添加编译选项。
e.EXECUTABLE_OUTPUT_PATH
指定可执行文件存放的路径。
f.LIBRARY_OUTPUT_PATH
指定库文件放置的路径
2.对变量的操作及赋值
a.前面我们已经提到了,使用${}
进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}
取值
b.使用set(HELLO_SRC main.c …) 来定义变量,并给变量赋值。
四.变量在目录间的传递:
方法一 set:
一般用set命令定义的变量能从父目录传递到子目录,但opencl与facedetect和facefeature在同级目录,所以用set定义的变量无法共享,要用set(variable value CACHE INTERNAL docstring )这种方式定义的变量会把变量加入到CMakeCache.txt然后各级目录共享会访问到这个变量
比如:
在opencl下的CMakeLists.txt中定义一个变量
set(ICD_LIBRARY "${PROJECT_BINARY_DIR}/lib" CACHE INTERNAL "ICD Library location" )
"ICD Library location"这个字符串相当于对变量的描述说明,不能省略,但可以自己随便定义
在facedetect下的CMakeLists.txt中读取这个一个变量
MESSAGE(STATUS "ICD_LIBRARY :${ICD_LIBRARY}")
每次运行cmake都会更新这个变量,你会在CMakeCache.txt中找到这个变量
//ICD Library location
ICD_LIBRARY:INTERNAL=J:/workspace/facecl.prj/lib
方法二 set_property/get_property:
使用set_property实现共享变量的方法,不会将变量写入CMakeCache.txt,应该是内存中实现的。
当用set_property定义的property时,第一个指定作用域(scope)的参数设为GLOBAL,这个property在cmake运行期间作用域就是全局的。然后其他目录下的CMakeLists.txt可以用get_property来读取这个property
比如:
在opencl下的CMakeLists.txt中定义一个名为INCLUDE_OPENCL_1_2 的global property
set_property(GLOBAL PROPERTY INCLUDE_OPENCL_1_2 "${CMAKE_CURRENT_LIST_DIR}/include/1.2" )
在facedetect下的CMakeLists.txt中读取这个一个property
先调用get_property将这个property读取到一个变量中(variable)INCLUDE_OPENCL
get_property(INCLUDE_OPENCL GLOBAL PROPERTY "INCLUDE_OPENCL_1_2" )
//显示INCLUDE_OPENCL
MESSAGE(STATUS "INCLUDE_OPENCL :${INCLUDE_OPENCL}")
上面的例子可以看出这种方式相比方法一在使用变量时多了一步,先要将先调用get_property将这个property读取到一个变量中(variable)才能使用。
总结:
两种方法相比,从使用便利性角度,方法一好一些,但方法一将变量保存在CMakeCache.txt,需要读写CMakeCache.txt文件,目前没有发现别的副作用,但记住这个区别是有好处的。
7。find_package 和 find_library 和find_path
find_package命令调用的是cmake已经提供的一些find函数,存在/usr/share/cmake/Modules/Findxxxx.cmake文件。这里包含了很多常规库文件的搜索,如果这里没有,可以自己写一个,放在此目录下或者放在其他目录,将存储目录设置给CMAKE_MODULE_PATH变量
帖子的开头有介绍find_package的链接,注意第一次运行cmake的时候,会在当前目录生成一个CMakeCache.txt文件,所以第一次运行的时候,find_package的结果会写入这个临时文件,后面搜索的时候会先查看这个文件,如果已经有了上次的搜索结果,则不会再搜索,所以,如果find_package也有变化,需要删除此cache文件,再重新运行cmake。
find_package会搜索头文件路径和库文件的位置,所以会生成 LUA_INCLUDE_DIR和 LUA_LIBRARIES(包括该库依赖的库)和LUA_LIBRARY(只包括本库)等变量。
find_library只会搜索库文件,不搜索头文件路径。我们可以参考findxxx.cmake,可以发现 find_package也是利用 find_library来实现库文件的搜索的。
find_library(<VAR> name1 [path1 path2 ...])
在指定的path列表中寻找name指定的库,如果找到,相应的路径会被放置到var变量中。和find_path类似。
find_path来搜索头文件的路径。介绍: https://www.cnblogs.com/coderfenghc/archive/2012/07/19/2599988.html
find_path会根据指定的文件名和搜索目录来寻找是否存在这个文件,如果找到,会把找到的目录存储在第一个变量参数中。
find_path(<VAR> name1 [path1 path2 ...])
- add_custom_command
add_custom_command(TARGET target PRE_BUILD | PRE_LINK | POST_BUILD COMMAND command1 [args]
COMMAND command2 [args] WORKING_DIRECTORY dir).
可以选择在编译完成后或者编译完成前执行command定义的命令。
例子:
add_custom_command(TARGET muxer POST_BUILD COMMAND ln -s muxer.so muxer-0.so WORKING_DIRECTORY ${LIBRARY_OUTPUT_PATH})
编译完成后就会调用ln命令建立一个软链接
9.编译完成之后安装到指定位置:
https://blog.csdn.net/guoyajie1990/article/details/78138636
9. cmake一般使用的环境参数,可以作为cmake -Dxxx=xxx来控制cmake的运行
CMAKE_PREFIX_PATH //find_package, find_program, find_library, find_file, find_path等命令的搜索路径
CMAKE_MODULE_PATH //存放其他cmake module的目录,比如上面find_packet需要的findxxx.cmake文件的存放目录
CMAKE_BUILD_TYPE //编译成Debug模式还是Release 模式
CMAKE_INSTALL_PREFIX //install目录前缀
CMAKE_TOOLCHAIN_FILE //toolchain 目录前缀
BUILD_SHARED_LIBS //add_library的时候可以制定编译为shared还是static,如果没有制定,可以通过这里参数制定
CMAKE_EXPORT_COMPILE_COMMANDS //Generate a compile_commands.json file for use with clang-based tools
使用上面的参数格式为
cmake .. -DCMAKE_BUILD_TYPE=Debug
-
cmake运行之后,会在build目录形成一个CMakeCache.txt文件,里面格式为key-value对,记录了各个变量的value, 可以打开查看。
-
配置config version
project(Tutorial VERSION 2.1)
configure_file(TutorialConfig.h.in TutorialConfig.h)
//因为cmake会根据TutorialConfig.h.in生成TutorialConfig.h,放置在build目录下,所以这里需要将build目录
//添加到include directories里面
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
我们要自己写一个TutorialConfig.h.in文件,在文件中定义如下:
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
这样,cmake之后就会自动生成TutorialConfig.h头文件,里面定义了宏如下:
#define Tutorial_VERSION_MAJOR 2
#define Tutorial_VERSION_MINOR 1
就是我们上面project(Tutorial VERSION 2.1)中传入的version。在source code中可以includeTutorialConfig.h头文件,
就可以使用这两个宏定义,这样就可以在code中实现版本控制。如下:
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
另外,如果我们有些自己的宏来控制程序流程,也可以添加到TutorialConfig.h.in中,另外在CMakeLists.txt文件中添加option. 这个option定义既可以在CMakeLists.txt中使用,也可以在cpp code
中使用,还是可配置的。
#CMakeLists.txt :
option(USE_MYMATH "use my math funciton" ON)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
#TutorialConfig.h.in
#cmakedefine USE_MYMATH
这样我们可以通过ccmake命令来通过ui来改变,或者cmake ../ -DUSE_MYMATH=off/on来指定
cmake命令之后,我们就会发现在TutorialConfig.h文件中就会生成这个宏(on),或者没有生成这个宏(off)
在程序文件cpp中,我们就可以根据这个宏是否被定义来分支
#ifdef USE_MYMATH
std::cout << "USE_MYMATH is defined." << std::endl;
#endif