CMake的使用简介和补充知识

内容概要

  1. 认识CMake及应用 应用,与Gcc,Makefile, Autotools比较的优势
  2. CMake语句的主题框架 cmake问题分析思路,主体的结构,语法的构成,及基本模块功能
  3. CMake的常用指令及变量 基本常用指令(安装,测试,调试),常用的CMake语法变量含义
  4. CMake的实践应用 从简单的CMake文件说起 -> 生成链接库(静态&动态) -> 如何引用链接库(内部&引用) -> 更简单的组织Cmake的编译方式

 

认识CMake及应用

  • CMake是什么?
    • 全称Cross Platform Make, 起初为了跨平台需求,而后不断完善并广泛使用
    • 一款优秀的工程构件工具
  • 特点及优势:
    • 开放源代码,具有BSD许可
    • 跨平台,支持Linux、Mac和Windows
    • 编译语言简单,易用,简化编译构建过程和编译过程
    • 编程高效可扩展
  • 与其他编译工具的对比
    • GCC
      • 由GNU开发的编程语言译器,C/C++,Java等语言的开发
      • 项目简单时,可以gcc/g++编译目标和项目
      • 但复杂时,只用gcc组织编译架构就变得很困难
    • Makefile
      • Makefile是有条理的gcc编译命令的文件,利用make工具来执行Makefile文件的编译指令
      • 项目简单时,可以手写Makefile
      • 项目复杂时,一般利用CMake和autotools自动生成Makefile
    • Autotools
      • autotools是一个工具集,灵活性较大,生成权限较少
      • 但开发步骤多,配置繁琐[autoscan+autoconf+automake]
      • 一般./configure文件都是由autotools构建的,生成Makefile和config.h文件
    • CMake
      • Cmake类似Make工具功能,用来“读取”并执行CMakeLists.txt文件的语句,生成Makefile文件

 

CMake的主题框架

CMake需要考虑的问题: 1)如何组织一个项目的编译框架;2)输出的目标有什么(可执行程序、动态/静态库); 3)如何配置目标文件的指定编译参数、环境、源文件;4)如何为输出目标指定链接参数。 主体框架:
  • 工程配置部分
    • 工程名、编译调试模式、编译系统语言
  • 依赖执行部分
    • 工程包,头文件、依赖库等
  • 其他辅助部分
    • 参数打印、遍历目录等
  • 判断控制部分
    • 条件判断、函数定义、条件执行等
 
command(arg1 arg2 ...)									//运行命令
set(var_name var_value)									//定义变量,或者给已存在的变量赋值
command(arg1 ${var_name})								//使用变量

//************************************工程配置部分*****************************************
cmake_minimum_required (VERSION num)					//CMake最低版本号要求
project (cur_project_name)								//项目信息
set(CMAKE_CXX_FLAGS "XXX")								//设定编译器版本,如:C++17
set(CMAKE_BUILD_TYPE "XXX")								//设定编译模式,如Debug/Release

//************************************依赖执行部分*****************************************
find_package(std_lib_name VERSION REQUIRED)				//引入外部依赖
add_library(<name> [lib_type] source1)					//生成库类型(动态,静态)
include_directories(${std_lib_name_INCLUEDE_DIRS})		//指定include路径,放在add_executable前
add_executable(cur_project_name XXX.cpp)				//指定生成目标
target_link_libraries(${std_lib_name_LIBRARIES})		//指定libraries路径,放在add_executable后

//************************************其他辅助部分*****************************************
function(function_name arg)								//定义一个函数
add_subdirectory(dir)									//添加一个子目录
AUX_SOURCE_DIRECTORY(. SRC_LIST)						//查找当前目录所有文件,保存到SRC_LIST变量中
FOREACH(one_dir ${SRC_LIST})
	MESSAGE(${one_dir})									//使用message进行打印
ENDFOREACH(onedir)

//************************************判断控制部分*****************************************
if(expression)
	COMMAND1(ARGS)
ELSE(expression)
	COMMAND2(ARGS)
ENDIF(expression)

//expression
IF(var)	//不是空,0,N,NO,OFF,FALSE,NOTFOUND 或 <var> NOTFOUND时,为真
IF(NOT var)
IF(var1 AND var2)	//与
IF(var1 OR var2)	//或
IF(COMMAND cmd)	//当给定的cmd是命令并可以调用是真
IF(EXISTS dir)	//目录名存在
IF(EXISTS file)	//文件名存在
IF(IS_DIRECTORY dirname)	//当dirname是目录
IF(file1 IS_NEWER_THAN file2)	//当file1比file2新,为真
IF(variable MATCHES regex)	//符合正则表达式

//循环
WHILE(condition)
	COMMAND1(ARGS)
	// ...
ENDWHILE(condition)
  附: 正则表达式 嗖~
 

CMake的常用指令及变量

常用指令

  •  PROJECT
  •  ADD_EXECUTABLE
  •  ADD_SUBDIRECTORY
  •  INCLUDE_DIRECTORIES
  •  LINK_DIRECTORIES
  •  TARGET_LINK_LIBRARIES
  •  ADD_LIBRARY
  •  AUX_SOURCE_DIRECTORY
  •  FOREACH
  •  MESSAGE
  •  IF ELSE ENDIF
  •  WHILE ENDWHILE
  •  FIND_PACKAGE
  •  SET
  •  ADD-DEFINITIONS
    •  为源文件的编译添加由-D引入的 宏定义
    •  命令格式为:add_definitions(-DFOO -DBAR ...)
    •  例:add_definitions(-DWIN32)
    •  OpenCV中:
 
cmake -D CMAKE_BUILD_TYPE=RELEASE \		//定义一个宏,以release形式build
-D CMAKE_INSTALL_PREFIX=/usr/local \	//下载目录
-D WITH_CUDA=ON \						//打开CUDA加速
-D ENABLE_FAST_MATH=1 \
-D WITH_CUBLAS=1 \
-D INSTALL_C_EXAMPLES=OFF \
-D INSTALL PYTHON EXAMPLES=ON \		   //下载PYTHON示例
  OPTION
  •  提供用户可以选择的选项
  •  命令格式为:option(<variable> "description [initial value])
 
option(
	   USE_MYMATH
	   "Use tutorial provided math implementation"
	   ON
	   )	//不赋给初值,就是ON,description是描述
    ADD_CUSTOM_COMMAND/TARGET
  •  [COMMAND]:为工程添加一条自定义的构建规则
  •  [TARGET]:用于给指定名称的目标执行指定的命令,该目标没有输出文件,并始终被构建
 
//伪代码:为了说明生成自定义的命令
add_custom_command(TARGET ${CV_ADVANCE_NAME}
	PRE_BUILD
	COMMAD "伪代码 find_package std_msgs"
	)
//导入自定义构建的命令
add_custom_target(CV_ADVANCE) ALL
	DEPENDS ${CV_ADVANCE_NAME}	//依赖add_custom_command输出的package包
	COMMENT "ros package std_msgs"
	)
  ADD_DEPENDENCIES
  •  当定义的target依赖另一个target,为了确保源码编译本target之前,其他的target已经被构建,使用该语句
 
//添加一条自定义构建的规则
add_custom_target(CV_ADVANCE DEPENDS ${CV_ADVANCE_NAME})
//为项目添加一个可执行文件
add_executable(${PROJECT_NAME} ${SRC_LIST})
//链接了一个标准的库文件
target_link_libraries(${PROJECT_NAME} ${std_lib)name_LIBRARIES})
//为项目链接一个依赖文件,项目程序依赖CV_ADVANCE
add_dependencies(${PROJECT_NAME} CV_ADVANCE)
  ROS1.0在该指令上的应用(工程实例)
//常规语法
cmake_minimum_required (VERSION 2.8.3)
project(HelloCV)
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs genmsg)
//ros生成msg的语法
generate_messages(DEPENDENCIES std_msgs)
add_message_files(FILES HelloCvMsg.msg)
//声明是catkin包
catkin_package()
//创建工程实例
include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(greet src/greet.cpp)
target_link_libraries(greet ${catkin_LIBRARIES})
//添加greet依赖,依赖std_msgs的变量
add_dependencies(greet HelloCV_generate_messages_cpp)
  INSTALL
  •  用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
  •  常用的如OpenCV一般情况下安装到系统目录,即/usr/lib,/usr/bin和/usr/include [Ubuntu]
 
INSTALL(
		TARGETS myrun mylib mystaticlib
		RUNTIME DESTINATION bin
		LIBRARY DESTINATION lib
		ARCHIVE DESTINATION libstatic
		)
  TARGET_INCLUDE_DIRECTORIES
  •  设置include文件查找的目录,具体包含头文件应用形式,安装位置等。
  •  命令格式为:target_include_directories(<target>[SYSTEM][BEFORE] <INTERFACE|PUBLIC|PRIVATE>[items])
  •  INTERFACE 对外接口 / PUBLIC 公开 / PRIVATE 私有
 
//外部链接时include的位置,如果不指明外部依赖无法找到对应的include库
target_include_directories(hello_cv_2_add_static_lib PUBLIC
						   $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>	//源文件安装位置
						   $<INSTALL_INTERFACE:include>)	//安装文件夹位置
 
  •  SET_TARGET_PROPERTIES
    •  设置目标的一些属性来改变它们构建的方式
    •  命令格式为:set_target_properties(target1 target2 ... PROPERTIES prop1 value1 prop2 value2 ...)
  •  ENABLE_TESTING/ADD_TEST
    •  [enable_testing]:用来控制Makefile是否构建test目标
    •  [add_test]:一般需要和enable_testing()配合使用
    •  命令格式:ADD_TEST(testname Exename arg1 arg2 ...)
    •  生成makefile后可用make test执行测试
 

CMake常用变量

    (第一次接触的时候,上面这些我也看傻了;现在我还记不住qwq) (不过可以当成工具,用到翻翻看) (现在我们从最简单的开始实践)  

实践:从简单CMake说起

(这里做个铺垫!构建了三个最最最最简单的C++例程,这里就不贴了,一个头文件(cv.h)内部声明了一个类,两个cpp文件(cv.cpp,main.cpp)一个定义了类,另一个main.cpp,两个cpp文件都include了h文件) 采用外部编译(简单说就是在CMakeLists.txt同级目录下建立build文件夹,将生成的执行文件都存放在build内)   关系图(高亮为文件夹): build CMakeLists.txt include
  • cv.h
src
  • cv.hpp
  • main.hpp
 

Round One

CMakeLists.txt内容  
cmake_minimum_required(VERSION 2.8.3)
project(cv)

add_compile_options(-std=c++11)

include_directories(include)	//include/cv.h
add_executable(cv src/main.cpp src/cv.cpp)
  接下来shell里  
mkdir build && cd build		#建立build并进入该文件夹
cmake ..					#生成makefile文件
make						#编译makefile,并生成bin(二进制可执行)文件
./cv						#执行文件
  ..代表上一级目录 (补充知识)include_directories和find_package的区别 include_directories() 是用来寻找头文件路径的,总不能在主函数上写上#include <&.?!@#%$*/cv.h>的吧; find_package(<Name>) 命令是用来在路径中Find<name>.cmake,找到.cmake,里面就会定义。  
<NAME>_FOUND
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDE
<NAME>_LIBRARIES or <NAME>LIBS
<NAME>_DEFINITIONS
  这时就可以给这些变量赋路径。最后一定要将找到的库连接到可执行文件上target_link_libraries(项目名 ${路径}) include_directories => find_package => target_link_libraries 三步走!  

Round two

为CV实例添加一个库  
cmake_minimum_required(VERSION 2.8.3)
project(cv_add_static_lib)

add_compile_options(-std=c++11)

include_directories(include)	//cv.h的路径
add_library(cv_add_static_lib STATIC src/cv.cpp)
//STATIC静态链接库 SHARED动态链接库 MODULE(不常用)函数目标链接
 

Round Three

生成一个静态库 set给变量赋值  
cmake_minimum_required(VERSION 2.8.3)
project(cv_add_shared_lib)

add_compile_option(-std=c++11)

//设置库文件的输出目录,默认输出到执行目录空间下
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib}	//工程的根目录	如ROS/practicecode/lib
include_directories(include)

//静态库名为XXX.a 动态库名为XXX.so
add_library(cv_add_shared_lib SHARED src/cv.hpp)
 
  • Windows中静态库.lib后缀,动态库.dll
  • Linux中静态库.a后缀,动态库.so
这里需要注意add_library(NAME SHARED …)和add_library(NAME STATIC …)同时使用时由于名字相同会报错。 同时添加一个动态库和静态库  
cmake_minimum_required(VERSION 2.8.3)
project(cv_add_both_lib)

add_compile_option(-std=c++11)

//设置库文件的输出目录,默认输出到执行目录空间下
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib}	//工程的根目录	如ROS/practicecode/lib
include_directories(include)

//静态库名为XXX.a 动态库名为XXX.so
add_library(cv_add_both_lib SHARED src/cv.hpp)

add_library(cv_add_static_lib STATIC src/cv.hpp)
//设置文件名替换
set_target_properties(cv_add_static_lib PROPERTIES OUTPUT_NAME "cv_add_both_lib")
  补充知识:  
  • 静态库: 简单来说,就是在链接时,里面的所有东西都嗖的变成机器码,然后copy到可执行文件里,这也叫静态链接。可以参考这个玩意儿。但是静态库在内存中可能多次拷贝重新编译,耗费内存,编译时执行
  • 动态库: 程序执行时才会链进来,简单说就是要啥取啥,增量更新,不需要改动一丢丢就全盘改动重新编译。动态库使用时很像指针,简单说在内存中只存在一次拷贝,避免了浪费。但是动态链接存在一个问题对库的依赖性很强,所有的东西都要在。
 

Round Four

为实例添加一个内部的链接库  
cmake_minimum_required(VERSION 2.8.3)
project(cv_target_link_lib)

add_compile_options(-std=c++11)

//设置库文件的输出目录,默认输出到执行目录空间下
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(LIBRARY_OUTPUT_NAME ${PROJECT_NAME})

add_library(cv_lib SHARED src/cv.cpp)
add_executable(${PROJECT_NAME} src/main.cpp)

target_link_libraries(${PROJECT_NAME} cv_lib)
  添加内部链接库的原因是,当一个解决方案中除main.cpp外存在多个.cpp文件用来定义函数和方法,直接生成可执行文件,并没有动态链接库效率高,占用内存小。  

Round Five

添加一个外部链接库
  • 绝对路径导入
  • install的方式导入
  • find_package方式导入
  补充说明: (这里说明一下头文件和库文件的差别) 简单来说,头文件就是日常见到的.h文件,是文本文件可供阅读的,而库文件(可以理解为不含main的.cpp文件)是二进制文件;头文件在编译时使用,库文件在链接时使用;头文件含有函数声明、宏定义、内联函数等,库文件则是函数的实现。  
//绝对路径导入
cmake_minimum_required(VERSION 2.8.3)
project(cv_target_link_lib)
add_compiler_options(-std=c++11)

set(pkg_lib /home/fengyixiao/ROS/project/src/cv_static_lib)
include_directories(${pkg_lib}/include)
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} ${pkg_lib}/lib/libcv_static_lib.a)
 
//INSTALL导入
//库文件安装
cmake_minimum_required(VERSION 2.8.3)
project(cv_target_link_lib)
add_compiler_options(-std=c++11)
include_directories(include)	//头文件
add_library(cv_install_lib STATIC src/cv.cpp)	//库文件

//将头文件和库文件安装到/usr目录下
set(CMAKE_INSTALL_PREFIX /usr)	//CMAKE_INSTALL_PREFIX上面提到,构建install的路径
//如不指定则默认/usr/include
message(${CMAKE_INSTALL_PREFIX})
install(FILES include/cv.h DESTINATION include)	//头文件,目的地include
install(TARGETS cv_install_lib	//目的生成这个lib
		ARCHIVE DESTINATION lib	//ARCHIVE有档案的意思,应该是保存形式
		)

//库文件导入
cmake_minimum_required(VERSION 2.8.3)
project(cv_outer_lib)
add_compiler_options(-std=c++11)
include_directories(/usr/include)	//导入头文件

add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME}
		/usr/lib/libcv_static_lib.a
		)	//导入库文件
  find_package原理
  • 一般分为模块模式module和配置模式config
  • 指令按照优先级在指定路径查找FindXXX.cmake和XXXConfig.cmake
    模块模式  
//find_package导入
//模块模式
find_path(cv_install_lib_INCLUDE_DIR
	NAMES cv.h
	//必须绝对路径,推荐安装在"/usr/share/cmake/Modules/"目录下
	PATHS "/usr/fengyixiao/ROS/practicecode/src/cv_install_lib/install/include/")	//这点可能有点乱了,放上安装到的路径

find_library(cv_install_library
	//names也可以制定具体的文件名.a
	NAMES cv_install_lib
	PATHS "/usr/fengyixiao/ROS/practicecode/src/cv_install_lib/install/lib/")

IF(cv_install_lib_INCLUDE_DIR AND cv_install_library)
	SET(cv_install_lib_FOUND TRUE)
ENDIF(cv_install_lib_INCLUDE_DIR AND cv_install_library)
IF(cv_install_lib_FOUND)
	//制定了QUIET选项,如果没有找到包配置文件,会生成一个warnning
	IF(NOT cv_install_lib_FIND_QUIETLY)
		MESSAGE(STATUS "Found cv_install_lib:
				${cv_install_lib_LIBRARY}")
	ENDIF(NOT cv_install_lib_FIND_QUIETLY)
ELSE(cv_install_lib_FOUND)
	//制定REQUIRED选项,如果没有找到,会显示编译错误
	IF(cv_install_lib_FIND_REQUIRED)
		MESSAGE(FATAL_ERROR "Coule not find cv_install_lib")
	ENDIF(cv_install_lib_FINE_REQUIRED)
ENDIF(cv_install_lib_FOUND)
 
//模块模式INSTALL
cmake_minimum_required(VERSION 2.8.3)
project(cv_install_lib)
add_compiler_options(-std=c++11)
include_directories(include)	//头文件
add_library(cv_install_lib STATIC src/cv.cpp)	//库文件

set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install)
message(${CMAKE_MODULE_PATH})
message(${CMAKE_INSTALL_PREFIX})
install(FILES cmake/Findcv_install_lib.cmake DESTINATION cmake)
install(FILES include/cv.h DESTINATION include)
install(TARGETS cv_install_lib
		//把静态库安装在${CMAKE_INSTALL_PREFIX}/lib下
		ARCHIVE DESTINATION lib
		)
  message() 就是用来打印变量的  
//库文件的导入
cmake_minimum_required(VERSION 2.8.3)
project(cv_find_outer_lib)
add_compile_options(-std=c++11)

set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/.../install/cmake/)
find_package(cv_install_lib REQUIRED)
if(cv_install_lib_FOUND)
	include_directories(${cv_install_lib_INCLUDE_DIR})	//头文件
	add_executable(${PROJECT_NAME} src/main.cpp)//主函数
	target_link_libraries(${PROJECT_NAME} 
						  ${cv_install_library})	//链接库文件
else(cv_install_lib_FOUND)
	message(FATAL_ERROR "cv_install_lib library not found")
endif(cv_install_lib_FOUND)
  配置模式  
//库文件的生成
cmake_minimum_required(VERSION 2.8.3)
project(cv_add_static_lib)
add_compile_options(-std=c++11)
include_directories(include)
add_library(cv_add_static_lib STATIC src/cv.cpp)

set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURSE_DIR}/install)

target_include_directories(cv_add_static_lib PUBLIC	//设置库的属性
						   $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>	//源文件安装位置
						   $<INSTALL_INTERFACE:include>)	//安装文件夹位置

//设置目标include的头文件的属性
set_target_properties(cv_add_static_lib
					  PROPERTIES
					  PUBLIC_HEADER "include/cv.h")

install(TARGETS cv_add_static_lib
		EXPORT cv_add_static_lib-targets	//供外部库使用的声明
		PUBLIC_HEADER DESTINATION include
		ARCHIVE DESTINATION lib		//将静态库安装到${CMAKE_INSTALL_PREFIX}/lib下
		)
install(EXPORT cv_add_static_lib-targets
		NAMESPACE cv_add_static_lib::
		//外部要使用find_package一定要命名XX-config.cmake
		FILE cv_add_static_lib-config.cmake
		DESTINATION lib/cmake/cv_add_static_lib
		)
 
//库文件的导入
cmake_minimum_required(VERSION 2.8.3)
project(cv_target_link_outer_lib)
add_compile_options(-std=c++11)

set(CMAKE_PREFIX_PATH ${PROJECT_SOURCE_DIR}/.../cv_add_static_lib/install)
find_package(cv_add_static_lib REQUIRED)
if(cv_add_static_lib_FOUND)
	add_executable(${PROJECT_NAME} src/main.cpp)
	target_link_libraries(${PROJECT_NAME}
						  cv_add_static_lib::cv_add_static_lib)
else(cv_add_static_lib_FOUND)
	message(FATAL_ERROR "cv_add_static_lib library not found")
endif(cv_add_static_lib_FOUND)
 
ROS catkin
基于cmake语法的上层编译工具
利用指令catkin create --pkg XXX自动生成CMakeLists和package文件。(include头文件所在文件夹,src源文件)   生成库
cmake_minimum_required(VERSION 2.8.3)
project(cv_add_static_lib)
add_compile_options(-std=c++11)
find_package(catkin REQUIRED)
catkin_package(
				INCLUDE_DIRS include
				LIBRARIES cv_add_static_lib
				//DEPENDS system_lib //如果依赖其他库添加
			  )	
include_directories(include ${catkin_INCLUDE_DIRS})
add_library(${PROJECT_NAME} STATIC src/cv.cpp)
  比起前面的CMakeLists.txt这里还多了一个xml文件  
<?xml version="1.0"?>
<package format="2">
	<name>cv_add_static_lib</name>
	<version>0.0.0</version>
	<description>The cv_add_static_lib package</description>

	<maintainer> email="xiaofengbh@163.com">xiaofeng</maintainer>

	<license>TODO</license>

	<buildtool_depend>catkin</buildtool_depend>

	<export>

	</export>
</package>
  导入库  
cmake_minimum_required(VERSION 2.8.3)
project(cv_target_link_outer_lib)
add_compile_options(-std=c++11)
find_package(catkin REQUIRED
			 cv_add_static_lib)
include_directories(include ${catkin_INCLUDE_DIRS})	//添加头文件
add_executable((${PROJECT_NAME} src/main.cpp)	//生成可执行文件
target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES})	//链接库文件
 
<?xml version="1.0"?>
<package format="2">
	<name>cv_add_static_lib</name>
	<version>0.0.0</version>
	<description>The cv_add_static_lib package</description>

	<maintainer> email="xiaofengbh@163.com">xiaofeng</maintainer>

	<license>TODO</license>

	<buildtool_depend>catkin</buildtool_depend>
	
	<depend>cv_add_static_lib</depend>

	<export>

	</export>
</package>
 

利用catkin管理项目实例

  pkg目录结构 项目文件夹名
  • src
    • project_lib
      • CMakeLists.txt
      • include
      • src
      • package.xml
  如何使用catkin
cd 项目文件夹名
catkin build	#构建完毕生成三个文件夹
  生成build, devel, log文件夹
  • 对应lib生成在/devel下
  • 对应bin生成在/build下
 

END…

是不是看的头晕,放心我写的也头晕,可以把这个当做工具册来看,写的都是伪代码,所以有些路径对不准,抱歉!!!也希望可以指正,O(∩_∩)O谢谢