这一部分需要在学习中不断熟悉,后期会不断完善自己对这块内容的理解。
UP主:计算机视觉life
内容概要:
1.认识CMake及其应用。
- 应用,与gcc,Makefile,Autotools相比较的优势?
2.CMake语句的主体框架。
- cmake问题分析思路,主体的结构,语法的构成,以及基本模块功能
3.Cmake的常用指令及变量。
- 基本常用指令(安装,测试,调试等),常用的Cmake语法变量含义
4.CMake的实践应用。
- 从简单的CMake文件说起——>生成链接库(静态&动态)——>如何引用链接库(内部&引用)——>更简单的组织CMake的编译方式。
1.认识CMake及其应用
CMake是什么?
- 全称Cross Platform Make,起初为了跨平台需求,后来不断完善并广泛使用。
- 一款优秀的工程构建工具
特点和优势:
- 开放源代码,具有BSD许可(BSD我没懂。。以后了解下)
- 跨平台,支持Linux,Mac,Windows等不同操作系统
- 编译语言简单,易用,简化编译构建过程和编译过程
- 编程效率高,可扩展
CMake与其他编译工具的对比
gcc:
- 由GUN开发的编程语言译器,C++/C,Java等语言的开发
- 当项目比较简单时,可以使用gcc/g++编译目标和项目
- 但项目比较复杂时,只用gcc组织编译构架变得极其困难
MakeFile:
- MakeFile是有条理的gcc编译命令文件,利用make工具来执行MakeFile文件的编译指令
- 当程序简单时,可以手写MakeFile
- 当程序复杂是,一般利用CMake和autotools来自动生成MakeFile
Autotools:
- autotools是一个工具集,具有灵活性大,对用户角度使用友好(cmake生成文件权限较多)
- 开发步骤太多,配置繁琐[autoscan+autoconf+automake]
- 通常编译的./configure文件,大多采用autotools构建的,最终生成MakeFile和config.h文件
CMake:
- Cmake类似Make工具功能,用来”读取“并执行CMakeList.txt文件的语句,最终生成MakeFile
- CMake语言开发相对简单,易于理解
- 目前很多项目正在抛弃Autotools,qmake等,转而采用cmake
谈到CMake,可能涉及到的问题:
- 如何组织一个项目的编译框架
- 最终输出目标由哪些(可执行文件,动态库,静态库等)
- 如何配置输出目标文件的指定编译参数(需要哪些编译参数及环境,需要哪些源文件)
- 如何为指定的输出目标链接参数(怎么配置内外部依赖的pkg及lib,怎么链接外部库)
2.CMake语法的主体框架
command(arg1 arg2 ...) #运行命令
set(var_name var_value) #定义变量,或者给已经存在的变量赋值
command(arg1 ${var_value}) #使用变量
主体框架:
工程编译部分:
工程名、编译调试模式、编译系统语言
cmake_minimum_required(VERSION num) #CMake最低版本号要求
project(cur_project_name) #项目信息
set(CMAKE_CXX_FLAGS "XXX") #设定编译器版本,如 -std=c++11
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_INCLUDE_DIRS}) #指定include路径,放在include_executable之前
add_executable(cur_project_name XXX.cpp) #指定生成目标
target_link_libraries(${std_lib_name_LIBARAIES} lib) #指定libaraies路径,放在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
IF(NOT var) #与上述条件相反
IF(var1 AND var2)
IF(var1 OR var2)
IF(COMMAND cmd) #当给定的cmd确实命令,并且以调用是为真
IF(EXISTS dir) #目录名存在
IF(EXISTS file) #文件名存在
IF(IS_DIRECTORY dirmane) #当dirname是目录
IF(file1 IS_NEWER_THAN file2) #当file1比file2新,为真
IF(variable MATCHES regex) #符合正则
循环(不太理解)
WHILE(condition)
COMMAND1(ARGS)
//...
ENDWHILE(condition)
注意:
- 对于${X},X表示变量名称,¥{X}表示变量值,if语句除外。
- 导入的功能函数,如SET的大小写均可,没有特殊限制
3. CMake的常用指令及变量
这些指令比较常见,在上面也有大概介绍。接下来介绍一些常用的:
ADD_DEFINITIONS(不太理解)
- 为源文件的编译添加由-D引入的宏定义。
- 命令格式为:add_definitions(-DFOO -DBAR ...),例如:add_definitions(-DWIN32)
OPTION(不太理解)
- 提供用户可以选择的选项。
- 命令格式为:option(<variable> "description" [initial value])
option( USE_MYMATH "Use tutorial provided math implementation" ON )
ADD_CUSTOM_COMMAND/TARGET(慢慢理解吧):
- [COMMAND]:为工程添加一条自定义的构建规则
- [TARGET]:用于给指定名称的目标执行指定的命令,该目标没有输出文件,并始终被构建。
-
#伪代码:为了说明生成自定义的命令 add_custom_command(TARGET ${CV_ADVANCE_NAME} PRE_BUILD COMMAND "伪代码 find_package std_msgs" ) #为了说明导入生成自定义结构的命令 add_custom_target((CV_ADVANCE) ALL DEPENDS ${CV_ADVANCE_NAME} #依赖add_custom_command输出的package包 COMMENT "ros package std_msgs" )
这个不太好理解,感觉ppt上讲的不准确,实践中再学习吧
ADD_DEPENDENCIES(不太理解):
- 用于解决链接依时的依赖问题,用target_link_libraries可以搞定么?
-
#添加执行文件 add_executable(cur_project_name XXX.cpp) #将库文件链接链接到当前可执行文件(注意:一定在add_executable之后) target_link_libraries(cur_project_name ${std_lib_name_LIBRARIES})
当定义的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)
#常规语法 cmake_minimum_required(VERSION 2.8) project(HelloCV) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs genmsg) #ros生成msg的语法 generate_message(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(只能我自己用)
SET_TARGET_PROPERTIES(不太理解)
- 设置目标的一些属性来改变他们构建的方式
- 命令格式为:
ENABLE_TESTING/ADD_TEST(不太理解)
3. CMake基本常用变量
4. 实践:从简单的CMake说起
实践一:生成一个简单的Hello_CV工程实例
头文件:hello_cv_1.h
#ifdef HELLO_CV_1_H_
#define HELLO_CV_1_H_
#include <string>
#include <iostream>
namespace hello_cv_1
{
class HelloCv_1
{
private:
std::string say_something;
public:
HelloCv_1();
~HelloCv_1();
std::string getMsgContent(std::string say_something);
};
}
#endif
源文件:hello_cv_1.cpp
#include "hello_cv_1.h"
#include <string>
#include <iostream>
namespace hello_cv_1
{
HelloCv_1::HelloCv_1()
{
}
HelloCv_1::~HelloCv_1()
{
}
std::string HelloCv_1::getMsgContent(std::string say_something)
{
return say_something;
}
}
主函数:main.cpp
#include "hello_cv_1.h"
#include <string>
#include <iostream>
int main()
{
hello_cv_1::HelloCv_1 helloCv_1;
std::cout<<helloCv_1.getMsgContent("Hello_OpenCV")<<std::endl
return 0;
}
工程文件目录
- 采用外部编译(理解下什么是外部编译):外部编译&&内部编译
- 文件位置存放规则:src&&include&&build
外部编译的目录结构如下
cmake_minimum_required(VERSION 2.6)
project(hello_cv_1)
add_compile_options(-std=c++11) #这个之前没用过
include_directories(include)
add_executable(hello_cv_1 src/main.cpp src/hello_cv_1.cpp)
下面是命令行编译:
mkdir build && cd build #建立build并进入到该文件夹
cmake .. #生成Makefile文件
make #编译Makefile,并生成bin文件
./hello_cv_1 #执行文件后输出"Hello_OpenCV"
果然实践中才能掌握得更好。。。
实践二:为Hello_CV实例添加一个库
CMakeList.txt如上所示
实践三:为实例同时添加一个动、静态库
生成指定路径的动态库
注意其中的设置库文件的额输出目录。
个人感觉动态库、静态库可以不同名吧?emmm。这样不就省事了?
不过这个up主用了另一种操作,看起来比较讲究:
注意一下这里讲究的操作,文件名替换~
然后思考,什么是静态库(.a)?什么是动态库(.so)?
这就要从程序的工作过程说起:
知识回顾:前三个实例干了啥?
1.怎样生成一个可执行文件
2.怎样去生成一些库
3.怎样生成一些动态库和静态库?如何生成同名动态库和静态库
接下来干啥?
怎么样调用库。
实践四:为实例添加一个内部的链接库。
怎样导入、调用一个内部的链接库?
nn
内部链接库,执行效率比较高。
实践五:为实例添加一个外部链接库
不太懂。。。以后慢慢理解