我们在很多SLAM 的源码里都能看到CMake 的使用,这个CMake 到底是什么呢有什么用呢?
CMake ( Cross platform Make ) 是一个开源的跨平台自动化建构系统,用来管理程序构建,不依赖于特定编译器。CMake 可以自动化编译源代码、创建库、生成可执行二进制文件等,为开发者节省了大量的时间,可以说是工程实践的必备工具。
CMake 通过使用简单的配置文件 CMakeLists.txt,自动生成不同平台的构建文件(如 Makefile、Ninja 构建文件、Visual Studio 工程文件等),简化了项目的编译和构建过程。CMake 本身不是构建工具,而是生成构建系统的工具,它生成的构建系统可以使用不同的编译器和工具链。
1.开源。
2.跨平台使用,根据目标用户的平台进一步生成所需的本地化Makefile 和工程文件,如UNIX 的Makefile 或Windows 的Visual Studio 工程。
3.可管理大型项目,如OpenCV 、Caffe 和MySQL Server。
4. 自动化构建编译,构建项目效率非常高。
5.CMake 支持多种语言,如C 、C ++ 和Java 等。
1.需要根据CMake 专用语言和语法自己编写CMakeLists. txt 文件(注意这里的大小写)。
2. 如果项目已经有非常完备的工程管理工具,并且不存在维护问题,则没有必要使用CMake 。
CMakeLists.txt 文件: CMake 的配置文件,用于定义项目的构建规则、依赖关系、编译选项等。每个 CMake 项目通常包含一个或多个 CMakeLists.txt 文件。
构建目录: 为了保持源代码的整洁,CMake 鼓励使用独立的构建目录(Out-of-source 构建)。这样,构建生成的文件与源代码分开存放。
1.编写 CMakeLists.txt 文件: 定义项目的构建规则和依赖关系。
2.生成构建文件: 使用 CMake 生成适合当前平台的构建系统文件(例如 Makefile、Visual Studio 工程文件)。
3.执行构建: 使用生成的构建系统文件(如 make
、ninja
、msbuild
)来编译项目。
CMake 的安装方法很简单。这里分别介绍CMake 在Windows 系统和Linux 系统下的安装方法:
cmake下载官网:
Download CMake
我的是.16版本,截至目前已经更新到.31了。
下载时需要梯子才不会出现卡顿,如果下载不了我已将资源进行了绑定请直接下载就行。
学习slam这里就默认你是ubuntu系统
sudo apt-get install cmake
如果想在Linux 系统下使用图形化界面,则用如下代码:
sudo apt-get install cmake—gui
但是ubuntu源里的版本可能比较低。
利用下面的命令检查你的cmake版本
cmake --version
访问 CMake 官方网站下载源码包。
解压源码包,进入解压后的目录。
执行以下命令编译和安装:
./bootstrap
make
sudo make install
用CMake 构建项目非常方便,以计算机视觉领域最常用的开源库OpenCV 为例,展示一下CMake 的魅力。早期我在Windows 系统下学习OpenCV 时,每次配置环境都非常头疼,准备工作烦琐、问题明显。
1.手动添加环境变量。
2.在项目中手动添加包含路径。
3.在项目中手动添加库路径。
4.在项目中手动添加链接库名。
5.在Debug 和Release下分别配置对应的库。
1.方法不通用,对于不同的OpenCV 版本,库的名称也不一样,在手动添加时需要修改库名称。
2.构建好的项目不能直接移植到其他平台上, 需要重新配置,代码的移植成本很高。
3.整个过程非常烦琐,并且非常容易出错。
但是自从我开始使用CMake自动构建项目,以上烦恼都消失啦!使用CMake 只需要简单几步,即可自动化完成项目构建。
CMake 一般有两种使用方式, 一种是命令行方式, 一般在Linux系统下使用比较多; 另一种是图形化界面, 一般在Windows 系统下比较常用。
我们这里以ubuntu系统为例。还以编译OpenCV 为例,假设我们已经提前下载好了OpenCV 的某个版本(这里用的是OpenCV3.4.6) 的源码,解压后的文件夹名字为opencv-3.4.6。如果用命令行来构建工程, 则先在该文件夹同级目录下打开一个终端,执行如下命令即可成功编译。
//在Ubuntu系统终端里编译OpenCV,假设解压后的义件夹名字为opencv-3.4.6
cd opencv-3.4.6 //进人opencv-3.4.6义件夹内
mkdir build //新建build 文件夹
cd build //进人build 文件夹内
cmake .. //编译上层目录的CMakeLists.txt 文件,生成Makefile 等文件
make //调用编译器编译源文件
sudo make install //将编译后的文件安装到系统中
为什么要新建一个build 文件夹呢?
新建build 文件夹是为了存放使用cmake 命令生成的中间文件,这些中间文件是在编译时产生的临时文件,在发布代码时并不需要将它们一起发布,最好删除掉。如果不新建build 文件夹,那么这些中间文件会混在代码文件中, 一个一个手动删除会非常麻烦。build 是大家常用的文件夹名, 当然,你也可以改成任意名字。
cmake 命令后面的两个点是什么意思呢?
一个点(.)代表当前目录, 两个点(..) 代表上一级目录。因为CMakeLists. txt 和build 文件夹位于同一层级目录,在进入build文件夹后, CMakeLists.txt 相对于当前位置在上一级目录中,所以在使用cmake命令的时候需要用两个点,否则会报错,提示找不到Makefile 文件。
cmake图形化界面这里暂时用不到所以先不学习。
CMakeLists. txt 文件,其内部巳经帮我们自动化地处理好了工程文件之间的复杂依赖关系。对于现成的第三方库或别人编写好的项目, CMakeLists.txt 文件都已经编写好了,我们只需要像前面那样简单操作就能自动构建好工程。但是,如果我们要自己搭建一个项目,就需要自己编写CMakeLists. txt 文件了,这需要一定的学习时间。下面我们来学习编写CMakeLists.txt 文件时常用的指令。
CMake 中有很多指令,我们可以在官网上查询到每个指令的介绍。
cmake指令查找工具
为方便学习,这里介绍几个常用的、比较重要的指令。
cmake_minimum_required(VERSION )
例如:
#指定要求最小的CMake 版本,如果版本小于该要求,则程序终止
cmake_mininum_required(VERSION 2.8)
project( [...])
使用的编程语言可以省略。
例如:
#设置当前项目名称为test
project(test)
add_executable( ...)
例如:
add_executable(MyExecutable main.cpp other_file.cpp)
add_library( ...)
例如:
add_library(MyLibrary STATIC library.cpp)
静态库(STATIC)适合独立运行的应用,无需额外的 .so
或 .dll
文件。
动态库(SHARED)适合大型项目或插件系统,多个程序可共享库,更新方便。
target_link_libraries( ...)
例如:
target_link_libraries(MyExecutable MyLibrary)
include_directories(...)
例如:
#指定头文件的搜索路径,方便编译器查找相应头文件
include_directories
#例子:文件main.cpp中使用到路径/usr/local/include/opencv/cv.h中的这个文件
#那么需要在CMakeLists.txt中添加include_directories{"/usr/local/include"}
#这样使用时在main.cpp 开头写上#include"opencv/cv.h",编译器即可自动搜索到该头文件
set( ...)
例如:
#例子: set (SRC_LST main.cpp other.cpp) 表示定义SRC_LST代替后面的两个.cpp文件
CMake 可以通过 find_package() 指令自动检测和配置外部库和包。
常用于查找系统安装的库或第三方库。
#搜索第三方库
find_package(packageName version EXACT/QUIET/REQUIRED)
# version: 指定查找库的版本号。EXACT: 要求该版本号必须精确匹配。QUIET: 禁止显示没有找到时的
#警告信息
# REQUIRED 选项表示如果包没有找到,则CMake 的过程会终止,并输出警告信息
#当find_package 找到一个库时,以下变量会自动初始化, NAME 表示库的名称
# _FOUND : 显示是否找到库的标记
# _INCLUDE_DIRS 或 _INCLUDES:头文件路径
# _LIBRARIES 或_LIBS: 库文件
#打印输出信息
message(mode "message text")
#mode包括FATAL ERROR 、WARNING 、STATUS 、DEBUG 等,双引号内是打印的内容
message(FATAL_ERROR "This is a fatal error!") # 立即终止 CMake
message(WARNING "This is a warning!") # 显示警告信息
message(STATUS "Project is being configured...") # 常规状态信息
message(DEBUG "Debug info: Var = ${MY_VAR}") # 仅调试模式下显示
CMake 使用变量来存储和传递信息,这些变量可以在 CMakeLists.txt 文件中定义和使用。
变量可以分为普通变量和缓存变量。
定义变量
set(MY_VAR "Hello World")
使用变量
message(STATUS "Variable MY_VAR is ${MY_VAR}")
缓存变量
缓存变量存储在 CMake 的缓存文件中,用户可以在 CMake 配置时修改这些值。缓存变量通常用于用户输入的设置,例如编译选项和路径。
定义缓存变量
set(MY_CACHE_VAR "DefaultValue" CACHE STRING "A cache variable")
使用缓存变量
message(STATUS "Cache variable MY_CACHE_VAR is ${MY_CACHE_VAR}")
#向当前工程中添加文件的子目录,目录可以是绝对路径或相对路径
add_subdirectory(source_dir)
#在目求下查找所有源义件
aux_source_directory(dir varname)
#列表操作(读、搜索、修改、排片)
list
#追加例子: LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR)/cmake_modules)
#判断语句,使用方法和C语言一致
if, elseif, endif
#循环指令,使用方法类似c语言中的for 循环
foreach , endforeach
CMake中的一些常用的、预定义的变量如下:
PROJECT_NAME: 工程名称,替代project(name) 中的name
PROJECT_SOURCE_DIR: 工程路径,通常是包含project 指令的CMakeLists.txt 文件所在的路径
EXECUTABLE_OUTPUT_PATH : 可执行文件输出路径
LIBRARY_OUTPUT_PATH: 库文件输出路径
CMAKE_BINARY_DIR : 默认是build 文件夹所在的绝对路径
CMAKE_SOURCE_DIR : 源文件所在的绝对路径
一个重要的指令:find _package
如果我们当前待编译的工程需要使用第三方库, 则需要知道3 件事,即第三方库的名称、去哪里找库的头文件、去哪里找库文件。要解决这些问题,就可以使用find _package 指令。比如需要一个第三方库Pangolin, 如果不使用find _package 指令, 则需要根据库的安装路径在CMakeLists.txt 中指定头文件和库文件的路径。像这样:
#在不使用find_package 指令的情况下需要手动指定路径
#下面的yourpathl 需要替换为Pangolin 头文件在当前计算机上的路径
include_directiories(yourpathl/Pangolin)
#下面的yourpath2 需要替换为生成的库文件在当前计算机上的路径
target_link_libraries (mydemo yourpath2/Pangolin.so)
而如果使用find _package 指令, 则在CMakeLists.txt中这样写:
#在使用find_package 指令的情况下自动指定路径
#查找计算机中已经安装的Pangolin 库
find_package(Pangolin REQUIRED)
#自动将找到的Pangolin库中头文件的路径添加到工程中
include_directories(${Pangolin_INCLUDE_DIRS})
#自动将找到的Pangolin 库文件链接到工程中
target_link_libraries (mydemo ${Pangolin_LIBRARIES})
单从代码来看,第二种方式确实多了一行代码,但是实际上比第一种方式有极大的灵活性和自动化性, 主要体现在以下方面:
1.不需要手动修改每个库文件的实际路径。每个人的计箕机环境不同, 库安装路径也不同,如果使用第一种方式,那么当项目给其他人用时, 每个使用者都需要手动修改每个库的位置,不仅烦琐,还容易出错。
2.格式化表达头文件和库文件名。这样我们在构建CMakeLists.txt 文件时会非常方便,尤其是库文件互相依赖或需要同时编译多个可执行文件时,按照格式化的方法来写头文件和库文件名即可,从而可以在不同平台和环境下维护同一个CMakeLists.txt 文件。
正是有了以上优势,我们在使用第三方库时基本不需要改动作者写好的第三方库里的CMakeLists.txt 文件,直接编译即可。
CMake指令可以全用大写或全用小写, 甚至大小写混用也可以,自己统一风格即可。比如下面两个指令表示的意义相同。其中,以#开头的行表示注释。
#指令不区分大小写
#指令add_executable 可以用小写字母表示
add_executable (hello main. cpp)
#指令ADD_EXECUTABLE 也可以用大写字母表示
ADD_ EXECUTABLE (hello main.cpp)
参数和变量名称只能用字母、数字、下画线、破折号中的一个或多个组合,并且严格区分大小写。引用变址的形式为${} 。如果有多个参数, 则中间应该使用
空格间隔, 示例如下。
#参数和变量名称严格区分大小写
#将OpenCV 库和 Sophus 库一起命名为THIRD_PARTY_LIBS
#${OpenCV_LIBS}表示引用Opencv的所有的库
#注意${OpenCV_LIBS}和${Sophus_LIBRARIES}之间需要用空格间隔
set (THIRD_PARTY LIBS ${OpenCV _LIBS} ${Sophus_LIBRARIES})
#添加可执行文件名称为test_Demo
add_executable(test_Demo test.cpp)
#为可执行文件添加链接库,注意这里的test_Demo 必须和上面的大小写一致
#${THIRD_PARTY_LIBS}表示引用前面定义的变量THIRD_PARTY_LIBS
target_link_libraries(test_Demo ${THIRD_PARTY_LIBS})
一般来说,我们的工程是存在多个目录的。使用CMakeLists.txt 构建工程有两种方法。
第一种:工程存在多个目录,只用一个CMakeLists. txt 文件来管理。典型的结构如下:
// include 文件夹
include
incl.h
inc2.h
// source 文件夹
source
srcl.cpp
src2.cpp
// app 为主函数文件夹
app
main.cpp
// CMakeLists.txt 和include 、source 及app 位于同级目录下
CMakeLists.txt
一个典型的案例就是ORB-SLAM2 代码, 它只在最外层使用了一个CMakeLists.txt来构建整个工程。我们来看看它是如何链接不同目录下的文件的。
#指定了该项目最低要求的 CMake 版本是 2.8。
cmake_minimum_required(VERSION 2.8)
#指定了项目的名称,项目名为 ORB_SLAM2。
project(ORB_SLAM2)
#这部分检查是否已设置 CMake 的构建类型 (CMAKE_BUILD_TYPE)。
#如果没有指定,默认设置构建类型为Release。
#常见的构建类型
#Release:用于发布的优化版本,通常启用优化选项。
#Debug:用于调试的版本,包含调试符号和调试信息,关闭优化。
#RelWithDebInfo:包含调试信息的优化版本。
#MinSizeRel:优化并尽量减小代码尺寸的版本。
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE Release)
ENDIF()
MESSAGE("Build type: " ${CMAKE_BUILD_TYPE})
#设置C编译器的编译选项。
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3 -march=native ")
#设置C++编译器的编译选项。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3 -march=native")
# Check C++11 or C++0x support
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_definitions(-DCOMPILEDWITHC11)
message(STATUS "Using flag -std=c++11.")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
add_definitions(-DCOMPILEDWITHC0X)
message(STATUS "Using flag -std=c++0x.")
else()
message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)
#查找 OpenCV 3.0 及以上版本。如果找不到 OpenCV 3.0,QUIET 参数表示不会输出错误信息,
#而是继续查找其他版本。
find_package(OpenCV 3.0 QUIET)
if(NOT OpenCV_FOUND)
find_package(OpenCV 2.4.3 QUIET)
if(NOT OpenCV_FOUND)
message(FATAL_ERROR "OpenCV > 2.4.3 not found.")
endif()
endif()
find_package(Eigen3 3.1.0 REQUIRED)
find_package(Pangolin REQUIRED)
include_directories(
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/include
${EIGEN3_INCLUDE_DIR}
${Pangolin_INCLUDE_DIRS}
)
#设置CMake输出共享库文件的目录,这里指定为项目的lib目录。
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
add_library(${PROJECT_NAME} SHARED
src/System.cc
src/Tracking.cc
src/LocalMapping.cc
src/LoopClosing.cc
src/ORBextractor.cc
src/ORBmatcher.cc
src/FrameDrawer.cc
src/Converter.cc
src/MapPoint.cc
src/KeyFrame.cc
src/Map.cc
src/MapDrawer.cc
src/Optimizer.cc
src/PnPsolver.cc
src/Frame.cc
src/KeyFrameDatabase.cc
src/Sim3Solver.cc
src/Initializer.cc
src/Viewer.cc
)
target_link_libraries(${PROJECT_NAME}
${OpenCV_LIBS}
${EIGEN3_LIBS}
${Pangolin_LIBRARIES}
${PROJECT_SOURCE_DIR}/Thirdparty/DBoW2/lib/libDBoW2.so
${PROJECT_SOURCE_DIR}/Thirdparty/g2o/lib/libg2o.so
)
# Build examples
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/RGB-D)
add_executable(rgbd_tum
Examples/RGB-D/rgbd_tum.cc)
target_link_libraries(rgbd_tum ${PROJECT_NAME})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/Stereo)
add_executable(stereo_kitti
Examples/Stereo/stereo_kitti.cc)
target_link_libraries(stereo_kitti ${PROJECT_NAME})
add_executable(stereo_euroc
Examples/Stereo/stereo_euroc.cc)
target_link_libraries(stereo_euroc ${PROJECT_NAME})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/Monocular)
add_executable(mono_tum
Examples/Monocular/mono_tum.cc)
target_link_libraries(mono_tum ${PROJECT_NAME})
add_executable(mono_kitti
Examples/Monocular/mono_kitti.cc)
target_link_libraries(mono_kitti ${PROJECT_NAME})
add_executable(mono_euroc
Examples/Monocular/mono_euroc.cc)
target_link_libraries(mono_euroc ${PROJECT_NAME})
第二种: 工程存在多个目录, 每个源文件目录都使用一个CMakeLists.txt文件来管理。典型的结构如下:
// include 文件夹
include
incl.h
inc2.h
// source 文件夹下除了源文件,还有CMakeLists.txt 文件
source
srcl.cpp
src2.cpp
CMakeLists.txt
// app 为主函数文件夹,其下除了源文件,还有CMakeLists.txt 文件
app
main.cpp
CMakeLists.txt
// CMakeLists.txt和include 、source 及app 位于同级目录F
CMakeLists.txt
一个典型的案例就是《视觉SLAM 十四讲:从理论到实践》里的源代码。因为两种方法本质上没有什么不同,选择其中一种即可,故这里不做过多赘述。
以上就是我学习到的内容,如果对您有帮助请多多支持我,如果哪里有问题欢迎大家在评论区积极讨论,我看到会及时回复。