CMake的使用简介和补充知识
内容概要
- 认识CMake及应用 应用,与Gcc,Makefile, Autotools比较的优势
- CMake语句的主题框架 cmake问题分析思路,主体的结构,语法的构成,及基本模块功能
- CMake的常用指令及变量 基本常用指令(安装,测试,调试),常用的CMake语法变量含义
- 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文件
- GCC
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
- project_lib
如何使用catkin
cd 项目文件夹名
catkin build #构建完毕生成三个文件夹
生成build, devel, log文件夹
- 对应lib生成在/devel下
- 对应bin生成在/build下
END…
是不是看的头晕,放心我写的也头晕,可以把这个当做工具册来看,写的都是伪代码,所以有些路径对不准,抱歉!!!也希望可以指正,O(∩_∩)O谢谢