参考引用
- CMake与Make最简单直接的区别
- 通过例子学习CMake
- LIO-SAM
1. 引言
1.1 什么是 CMake
1.1.1 CMake 背景
1.1.2 CMake 定义
- CMake 是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译)过程。能够输出各种各样的 makefile 或者 project 文件,CMake 组态档名为 CMakeLists.txt,也就是在 CMakeLists.txt 这个文件中写 CMake 代码。一句话:CMake 就是将多个 cpp、hpp 文件组合构建为一个大工程的语言
1.1.3 CMake 与 Make 区别
- CMake 是一个跨平台的构建自动化工具,可以生成适合不同编译器、操作系统和构建环境的构建文件。CMake 通过解析 CMakeLists.txt 文件来生成 Makefile 或其他构建脚本,在执行 make 命令前,需要先使用 cmake 命令生成构建脚本
- Make 是一个基于规则的构建工具,可以自动化编译源代码并生成可执行文件。Make 通过读取 Makefile 中的规则来控制编译过程,并自动检测修改后的文件进行重新编译
1.2 CMake 语法特性
- 基本语法格式:指令(参数 1 参数 2…)
- 参数使用括号括起
- 参数之间使用空格或分号分开
- 指令是大、小写无关的,参数和变量是大、小写相关的
- 变量使用 ${} 方式取值,但是在 if 控制语句中则是直接使用变量名
1.3 CMake 常用指令与变量
- cmake_minimum_required
- 指定 CMake 的最小版本要求
# CMake最小版本要求为2.8.3 # cmake_minimum_required(VERSION versionNumber [FATAL_ERROR]) cmake_minimum_required(VERSION 2.8.3)
- project
- 定义工程名称,并可指定工程支持的语言
# 指定工程名为 HELLOWORLD # project(projectname [CXX] [C] [Java]) project(HELLOWORLD)
- set
- 显式的定义变量
# 定义 SRC 变量,其值为 sayhello.cpp hello.cpp # set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]]) set(SRC sayhello.cpp hello.cpp) # 普通变量 # set(<variable> <value>... [PARENT_SCOPE]) # 1、变量值作用域默认属于整个 CMakeLists.txt (一个工程可能有多个 CMakeLists.txt) # 2、当加入 PARENT_SCOPE 后,表示要设置的变量是父目录中的 CMakeLists.txt 设置的变量 # 3、执行父级 CMakeLists.txt 时,会输出子目录内容,而在执行子目录 CMakeLists.txt 时只会输出自己的内容 # 缓存变量 # set(<variable> <value>... CACHE <type> <docstring> [FORCE]) # 1、CACHE 变量在运行 cmake 时,变量值可能会被缓存到 build 命令下的 CMakeCache.txt # 当重新运行 cmake 时,变量会默认使用这个缓存里的值。CACHE 是全局变量,整个 cmake 工程都可以使用 # 2、加上 FORCE 关键字,这个被写入文件的值会覆盖之前文件中存在的同名变量(默认不覆盖) # 3、cmake GUI 选择一个窗口,让用户设置值,<type> 有 5 种选项,其中 STRING 表示弹出提示消息 # 环境变量 # set(ENV{<variable>} [<value>]) # 1、通过调用 $ENV{<varible>} 返回此新值 # 2、此命令仅影响当前的 cmake 进程,不影响调用 cmake 的进程,也不影响整个系统环境,也不影响后续构建或测试过程的环境 # 3、如果 ENV{} 为空字符串或没有参数值 ``,则此命令将清除环境变量的任何现有值
- include_directories
- 向工程添加多个特定的头文件搜索路径
# 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径 # include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...) include_directories(/usr/include/myincludefolder ./include)
- link_directories
- 向工程添加多个特定的库文件搜索路径
# 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径 # link_directories(dir1 dir2 ...) link_directories(/usr/lib/mylibfolder ./lib)
- add_library
- 生成库文件
# 通过变量 SRC 生成 libhello.so 共享库 # add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]source1 source2 ... sourceN) add_library(hello SHARED ${SRC})
- add_compile_options
- 添加编译参数
# 添加编译参数 -Wall -std=c++11 -O2 add_compile_options(-Wall -std=c++11 -O2)
- add_executable
- 生成可执行文件
# 编译 main.cpp 生成可执行文件 main # add_executable(exename source1 source2 ... sourceN) add_executable(main main.cpp)
- target_link_libraries
- 为 target 添加需要链接的库
# 将 hello 动态库文件链接到可执行文件 main # target_link_libraries(target library1<debug | optimized> library2...) target_link_libraries(main hello)
- add_subdirectory
- 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
# 添加 src 子目录,src 中需有一个 CMakeLists.txt # add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) add_subdirectory(src)
- aux_source_directory
- 发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
# 定义 SRC 变量,其值为当前目录下所有的源代码文件 aux_source_directory(. SRC) # 编译 SRC 变量所代表的源代码文件,生成 main 可执行文件 add_executable(main ${SRC})
- CMAKE_CXX_FLAGS
- g++ 编译选项
# 在 CMAKE_CXX_FLAGS 编译选项后追加 -std=c++11,采用 C++11 标准 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
- CMAKE_BUILD_TYPE
- 编译类型(Debug, Release)
# 设定编译类型为 debug,调试时需要选择 debug set(CMAKE_BUILD_TYPE Debug) # 设定编译类型为 release,发布时需要选择 release set(CMAKE_BUILD_TYPE Release)
- CMAKE_BINARY_DIR、PROJECT_BINARY_DIR、_BINARY_DIR
- 这三个变量指代的内容是一致的
- 如果是 in source build,指的就是工程顶层目录
- 如果是 out-of-source 编译,指的是工程编译发生的目录
- PROJECT_BINARY_DIR 跟其他指令稍有区别,不过现在可理解为他们是一致的
- CMAKE_SOURCE_DIR、PROJECT_SOURCE_DIR、_SOURCE_DIR
- 这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录
- 也就是在 in source build时,他跟 CMAKE_BINARY_DIR 等变量一致
- PROJECT_SOURCE_DIR 跟其他指令稍有区别,不过现在可理解为他们是一致的
- CMAKE_CXX_COMPILER
- 指定 C++ 编译器
- EXECUTABLE_OUTPUT_PATH
- 可执行文件输出的存放路径
- LIBRARY_OUTPUT_PATH
- 库文件输出的存放路径
2. CMake 基本例程
2.1 hello-CMake
- 工作空间
$ mkdir -p cmake_ws/demo01 $ cd cmake_ws/demo01
- main.cpp
#include <iostream> int main(int argc, char *argv[]) { std::cout << "Hello CMake!" << std::endl; return 0; }
- CMakeLists.txt
# 设置 CMake 最低版本 cmake_minimum_required(VERSION 3.5) # 设置工程名 # 工程名 hello_cmake 和生成的可执行文件 hello_cmake 没有任何关系,可以相同,也可以不相同 project(hello_cmake) # 生成可执行文件 hello_cmake add_executable(hello_cmake main.cpp) # add_executable(${PROJECT_NAME} main.cpp) # 同上句等价
-
project(hello_cmake) 解析
- CMake 构建包含一个项目名称,该命令会自动生成一些变量,在使用多个项目时引用某些变量会更加容易。比如生成了 PROJECT_NAME 这个变量:PROJECT_NAME 是变量名,${PROJECT_NAME} 是变量值,值为 hello_cmake
-
add_executable() 解析
- 指定某些源文件生成可执行文件,本例中源文件是 main.cpp
- 第一个参数是可执行文件名,第二个参数是要编译的源文件列表
-
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./hello_cmake
- 文件 tree
├── build ... ├── CMakeLists.txt └── main.cpp
2.2 包含头文件
- 工作空间
$ cd ~/cmake_ws $ mkdir demo02 && cd demo02
- Hello.h
// 声明了 Hello 类,Hello 的方法是 print() #ifndef __HELLO_H__ #define __HELLO_H__ class Hello { public: void print(); }; #endif
- Hello.cpp
// 实现了 Hello::print() #include <iostream> #include "Hello.h" void Hello::print() { std::cout << "Hello Headers!" << std::endl; }
- main.cpp
#include "Hello.h" int main(int argc, char *argv[]) { Hello hi; hi.print(); return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) # 设置 CMake 最低版本 project(hello_headers) # 设置工程名 add_executable(hello_headers src/Hello.cpp src/main.cpp) # 用所有的源文件生成一个可执行文件 target_include_directories(hello_headers PRIVATE ${PROJECT_SOURCE_DIR}/include ) # 设置可执行文件 hello_headers 需要包含的库的路径 # PROJECT_SOURCE_DIR 指工程顶层目录 # PROJECT_Binary_DIR 指编译目录 # PRIVATE 指定了库的范围
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. # 运行 make 命令时,输出仅显示构建状态 $ make # 要查看用于调试目的的完整输出,可以使用 make VERBOSE=1 命令 #$ make VERBOSE=1 # 执行 $ ./hello_headers
- 文件 tree
├── build ... ├── CMakeLists.txt ├── include │ └── Hello.h └── src ├── Hello.cpp └── main.cpp
2.3 包含静态库
-
工作空间
$ cd ~/cmake_ws $ mkdir demo03 && cd demo03
-
Hello.h
- 同 2.2
-
Hello.cpp
// 实现了 Hello::print() #include <iostream> #include "static/Hello.h" void Hello::print() { std::cout << "Hello Static Library!" << std::endl; }
-
main.cpp
#include "static/Hello.h" int main(int argc, char *argv[]) { Hello hi; hi.print(); return 0; }
-
CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(hello_library) # 库的源文件 Hello.cpp 生成静态库 hello_library,默认生成在构建文件夹 add_library(hello_library STATIC src/Hello.cpp ) # 为一个目标(可能是一个库library,也可能是可执行文件)添加头文件路径 target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include ) # PRIVATE - 目录被添加到目标(库)的包含路径中 # INTERFACE - 目录没有被添加到目标(库)的包含路径中,而是链接了这个库的其他目标(库或者可执行程序)包含路径中 # PUBLIC - 目录既被添加到目标(库)的包含路径中,同时添加到链接了这个库的其他目标(库或者可执行程序)的包含路径中 # 对于公共的头文件,最好在 include 文件夹下建立子目录 # 传递给函数 target_include_directories() 的目录,应该是所有包含目录的根目录 # 然后在这个根目录下建立不同的文件夹,分别写头文件 # 链接源文件 src/main.cpp 生成可执行文件 hello_binary add_executable(hello_binary src/main.cpp ) # 链接静态库文件 hello_library 到可执行文件 hello_binary # 被链接的库如果有 INTERFACE 或 PUBLIC 属性的包含目录,也会被传递给这个可执行文件 target_link_libraries(hello_binary PRIVATE hello_library ) # 由于 hello_binary 不是库,所以不会被链接。直接 PRIVATE 自己用这个库就行 # PRIVATE、PUBLIC、INTERFACE 范围关键字解析 # PUBLIC 是说这个工程如果被 link 了,那 target_link_libraries 指定的 libs 也会被 link # PRIVATE 是说 link 的这些 libs 不会被暴露出去
-
编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./hello_binary
-
文件 tree
├── CMakeLists.txt ├── include │ └── static │ └── Hello.h └── src ├── Hello.cpp └── main.cpp
2.4 包含动态库
动态库和静态库区别
- 静态库的扩展名一般为 *.a 或 *.lib
- 动态库的扩展名一般为 *.so 或 *.dll
- 静态库在编译时会直接整合到目标文件中,编译成功的可执行文件可独立运行
- 动态库在编译时不会整合到目标文件中,可执行程序无法单独运行,需要有动态库文件
- 工作空间
$ cd ~/cmake_ws $ mkdir demo04 && cd demo04
- Hello.h
- 同 2.2
- Hello.cpp
// 实现了 Hello::print() #include <iostream> #include "shared/Hello.h" void Hello::print() { std::cout << "Hello Shared Library!" << std::endl; }
- main.cpp
#include "shared/Hello.h" int main(int argc, char *argv[]) { Hello hi; hi.print(); return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(hello_library) # 库的源文件 Hello.cpp 生成动态库 hello_library add_library(hello_library SHARED src/Hello.cpp ) # 给动态库 hello_library 起一个别名 hello::library add_library(hello::library ALIAS hello_library) # 为这个库目标添加头文件路径,PUBLIC 表示包含了这个库的目标也会包含这个路径 target_include_directories(hello_library PUBLIC ${PROJECT_SOURCE_DIR}/include ) # 链接源文件 src/main.cpp 生成可执行文件 hello_binary add_executable(hello_binary src/main.cpp ) # 链接动态库文件 hello_library 到可执行文件 hello_binary # 使用的是这个库的别名 target_link_libraries(hello_binary PRIVATE hello::library )
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./hello_binary
- 文件 tree
├── build ... ├── CMakeLists.txt ├── include │ └── shared │ └── Hello.h └── src ├── Hello.cpp └── main.cpp
2.5 设置构建类型
- 工作空间
$ cd ~/cmake_ws $ mkdir demo05 && cd demo05
- main.cpp
#include <iostream> int main(int argc, char *argv[]) { std::cout << "Hello Build Type!" << std::endl; return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) # 如果没有指定则设置默认编译方式 if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) # 在命令行中输出 message 里的信息 message("Setting build type to 'RelWithDebInfo' as none was specified.") # 强制在缓存文件中覆盖 CMAKE_BUILD_TYPE 这个变量,将这个变量设置为 RelWithDebInfo set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE) # 当使用 cmake-gui 时,设置构建级别的四个可选项 set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") # Release —— 不可以打断点调试,程序开发完成后发行使用的版本,占的体积小。对代码做了优化,速度很快 # 在编译器中使用命令:-O3 -DNDEBUG 可选择此版本 # Debug —— 调试的版本,体积大 # 在编译器中使用命令:-g 可选择此版本 # MinSizeRel —— 最小体积版本 # 在编译器中使用命令:-Os -DNDEBUG 可选择此版本 # RelWithDebInfo —— 既优化又能调试 # 在编译器中使用命令:-O2 -g -DNDEBUG 可选择此版本 # 在命令行运行 cmake 时, 使用 cmake 命令行的 -D 选项配置编译类型 # cmake .. -DCMAKE_BUILD_TYPE=Release endif() project(build_type) add_executable(build_type main.cpp)
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./build_type
- 文件 tree
├── build ... ├── CMakeLists.txt ├── main.cpp
2.6 设置编译方式
- 工作空间
$ cd ~/cmake_ws $ mkdir demo06 && cd demo06
- main.cpp
#include <iostream> int main(int argc, char *argv[]) { std::cout << "Hello Compile Flags!" << std::endl; // only print if compile flag set #ifdef EX2 std::cout << "Hello Compile Flag EX2!" << std::endl; #endif #ifdef EX3 std::cout << "Hello Compile Flag EX3!" << std::endl; #endif return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) # 1、设置默认编译标志(不推荐) # 强制设置默认 C++ 编译标志变量为缓存变量,该缓存变量相当于全局变量,源文件中也可以使用该变量 # (1) 设置 C 编译标志:CMAKE_C_FLAGS 设置链接标志:CMAKE_LINKER_FLAGS # (2) "${CMAKE_CXX_FLAGS} -DEX2":保留原有 CMAKE_CXX_FLAGS 中的参数,额外添加了一个 EX2 参数 # (3) CACHE STRING "Set C++ Compiler Flags" FORCE:强制将 CMAKE_CXX_FLAGS 变量放到 CMakeCache.txt 中 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE) project (compile_flags) add_executable(compile_flags main.cpp) # 2、现代 cmake 中设置 C++ 标志的推荐方法是专门针对某个目标(target)设置标志 # 为可执行文件添加私有编译定义 target_compile_definitions(compile_flags PRIVATE EX3 ) <# 对于编译器选项,还可使用 target_compile_options() 函数 target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) target:由 add_executable()产生的可执行文件或 add_library() 添加进来的库 #> # 在 cmake 命令行中设置全局 CXX 编译器标志 # cmake .. -DCMAKE_CXX_FLAGS="-DEX3"
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./compile_flags
- 文件 tree
├── build ... ├── CMakeLists.txt ├── main.cpp
2.7 包含第三方库
- 工作空间
$ cd ~/cmake_ws $ mkdir demo07 && cd demo07
- main.cpp
#include <iostream> #include <boost/shared_ptr.hpp> #include <boost/filesystem.hpp> /* Boost 库是为 C++ 语言标准库提供扩展的一些 C++ 程序库的总称 Boost 库可以与 C++ 标准库完美共同工作,并且为其提供扩展功能 */ int main(int argc, char *argv[]) { std::cout << "Hello Third Party Include!" << std::endl; // use a shared ptr boost::shared_ptr<int> isp(new int(4)); // trivial use of boost filesystem boost::filesystem::path path = "/usr/share/cmake/modules"; if(path.is_relative()) { std::cout << "Path is relative" << std::endl; } else { std::cout << "Path is not relative" << std::endl; } return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(third_party_include) # 使用库文件系统和系统查找 boost install # filesystem system 是第三方库,而不是自己生成的静态动态库 find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system) # Boost - 库名称,用于查找模块文件 FindBoost.cmake 的一部分 # 1.46.1 - 需要的 Boost 库最低版本 # REQUIRED - 告诉模块这是必需的,如果找不到会报错 # COMPONENTS - 要查找的库列表。从后面的参数代表的库里找 Boost # 大多数被包含的包将设置变量 XXX_FOUND,该变量可用于检查软件包在系统上是否可用 if(Boost_FOUND) message("boost found") else() message(FATAL_ERROR "Cannot find Boost") endif() add_executable(third_party_include main.cpp) # 1、别名/导入的目标 # 与 Boost::filesystem 链接将自动添加 Boost::boost 和 Boost::system 依赖关系 target_link_libraries(third_party_include PRIVATE Boost::filesystem ) # 2、非别名目标(与上述 4 行代码等价) # 大多数现代库都使用导入的目标,但并非所有模块都已更新。如果未更新库,则通常会发现以下可用变量 # xxx_INCLUDE_DIRS - 指向库的包含目录的变量 # xxx_LIBRARY - 指向库路径的变量 <# target_include_directories( third_party_include PRIVATE ${Boost_INCLUDE_DIRS} ) target_link_libraries(third_party_include PRIVATE ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ) #>
- 编译执行
# 外部构建与编译 $ mkdir build $ cd build/ $ cmake .. $ make # 执行 $ ./third_party_include
- 文件 tree
├── build ... ├── CMakeLists.txt ├── main.cpp
2.8 使用 clang 编译工程
gcc/g++ 和 clang/clang++ 都是 Linux 下常用的 C/C++ 编译器
- 工作空间
$ cd ~/cmake_ws $ mkdir demo08 && cd demo08
- main.cpp
#include <iostream> int main(int argc, char *argv[]) { std::cout << "Hello CMake!" << std::endl; return 0; }
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5) # 设置 CMake 最低版本 project(hello_cmake) # 设置工程名 add_executable(hello_cmake main.cpp) # 生成可执行文件
- pre_test.sh
# !/bin/bash # pre_test 脚本删除之前配置的 build 文件,run_test 运行 clang,生成这次的 build.clang 文件 # 这个脚本的作用是如果存在 build.clang 这个文件夹,就把它删除掉 # ROOT_DIR=`pwd`:shell 脚本的语法,pwd 输出文件当前所在路径,赋值给 ROOT_DIR 这个变量 dir="01-basic/I-compiling-with-clang" if [ -d "$ROOT_DIR/$dir/build.clang" ]; then echo "deleting $dir/build.clang" rm -r $dir/build.clang fi # if then fi 是 shell 脚本里的判断语句,如果 [] 里的条件为真,则执行 then 后面的语句 # 基本格式: # if [判断语句]; then # 执行语句 # fi # -d 与路径配合,路径存在则为真 # 单纯的 dir 等价于 ls -C -b; 默认情况下,文件在列中列出,并垂直排序,特殊字符由反斜杠转义序列表示 # 只要当前路径下存在 build.clang 就删除掉 # 本文 dir 是一个变量
- run_test.sh
#!/bin/bash # Ubuntu支持同时安装多个版本的 clang # 测试需要在调用 cmake 之前确定 clang 二进制文件 # 这个脚本找到具体 clang 编译器路径,并配置 cmake 使用 clang 编译器 if [ -z $clang_bin ]; then clang_ver=`dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f1 | cut -d '-' -f2` clang_bin="clang-$clang_ver" #把版本号存到变量,把版本号添加到 clangC 编译器和 clang 编译器 clang_xx_bin="clang++-$clang_ver" fi echo "Will use clang [$clang_bin] and clang++ [$clang_xx_bin]" # echo 用来输出信息 mkdir -p build.clang && cd build.clang && \ cmake .. -DCMAKE_C_COMPILER=$clang_bin -DCMAKE_CXX_COMPILER=$clang_xx_bin && make # 相当于在 shell 中执行命令:whilch clang 然后将返回的结果也就是路径,赋值给变量 clang_bin clang_bin=`which clang` clang_xx_bin=`which clang++` # which 语句返回后面命令的路径 # -z 指如果后面的路径为空则为真 # 如果用 which 没有找到 clang 的二进制可执行文件,则用 dpkg 找到 clang 并返回版本号 # dpkg –get-selections 罗列出所有包的名字并且给出了他们现在的状态比如已安装( installed)已经卸载。( deinstalled) # grep clang从结果中查找到带有 clang 名字的 # grep -v 反转,选择不匹配的所有行 # grep -m1 单纯的 -m1 表示输出 1 条匹配的结果之后就会停止 # grep -v -m1 libclang 输出包含 clang 的命令中,所有不包含libclang的一条介绍 # 也就是去掉那些 clang 的库,找的是 clang 这个程序的版本 # cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出 # cut -f1 将这行按照空格?分隔之后选择第1个字段,就是 clang-3.6 # cut -d '-' -f2 按照 - 分隔,选择第 2 个字段就是 3.6,从而得到版本号 # ```shell # $ dpkg --get-selections | grep clang | grep -v -m1 libclang # clang-3.6 install # $ dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f1 # clang-3.6 # $ dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f6 # install # $ dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f1 | cut -d '-' -f2 # 3.6 # $ dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f1 | cut -d '-' -f1 # clang # ```
- 编译执行
$ mkdir build.clang $ cd build.clang/ $ cmake .. -DCMAKE_C_COMPILER=clang-3.6 -DCMAKE_CXX_COMPILER=clang++-3.6 $ make $ ./hello_cmake
- 文件 tree
├── build.clang ... ├── CMakeLists.txt ├── main.cpp ├── pre_test.sh ├── run_test.sh
2.9 C++ 标准
# Checking Compile flags
# 将 flag “-std=c11” 传递给变量 COMPILER_SUPPORTS_CXX11
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
# Adding the flag
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
# message([<mode>] "message text" ...)
# mode 的值包括 FATAL ERROR、WARNING、AUTHOR WARNING、STATUSVERBOSE 等,常用的2个--FATAL ERROR、STATUS
# FATAL ERROR:产生 CMake Error,会停止编译系统的构建过程
# STATuS:最常用的命令,常用于查看变量值,类似于编程语言中的 DEBUG 级别信息
# "message text" 为显示在终端的内容
endif()
3. 子工程 CMake
-
许多大型项目由不同的库和二进制文件组成,可利用多个 CMakeLists.txt 文件组织这些库和文件
-
文件 tree
- subbinary:一个可执行文件
- sublibrary1:一个静态库
- sublibrary2:只有头文件的库
├── build ... ├── CMakeLists.txt // 顶层 CMakeLists.txt ├── subbinary │ ├── CMakeLists.txt // 生成可执行文件的 CMakeLists.txt │ └── main.cpp // 可执行文件的源文件 ├── sublibrary1 │ ├── CMakeLists.txt // 生成静态库的 CMakeLists.txt │ ├── include │ │ └── sublib1 │ │ └── sublib1.h │ └── src │ └── sublib1.cpp └── sublibrary2 ├── CMakeLists.txt // 生成仅有头文件的库的 CMakeLists.txt └── include └── sublib2 └── sublib2.h
-
工作空间
$ cd ~/cmake_ws $ mkdir demo09 && cd demo09
-
顶层 CMakeLists.txt
cmake_minimum_required (VERSION 3.5) project(subprojects) # Add sub directories add_subdirectory(sublibrary1) add_subdirectory(sublibrary2) add_subdirectory(subbinary)
-
sublibrary1/CMakeLists.txt
project (sublibrary1) # 此处 ${PROJECT_NAME} 是当前 project 的名字 sublibrary1 add_library(${PROJECT_NAME} src/sublib1.cpp) add_library(sub::lib1 ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include ) # PROJECT_SOURCE_DIR 当前项目的源文件目录 # PROJECT_BINARY_DIR 当前项目的构建目录
-
sublibrary1/include/sublib1/sublib1.h
#ifndef __SUBLIB_1_H__ #define __SUBLIB_1_H__ class sublib1 { public: void print(); }; #endif
-
sublibrary1/src/sublib1.cpp
#include <iostream> #include "sublib1/sublib1.h" void sublib1::print() { std::cout << "Hello sub-library 1!" << std::endl; }
-
sublibrary2/CMakeLists.txt
project (sublibrary2) add_library(${PROJECT_NAME} INTERFACE) add_library(sub::lib2 ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include )
-
sublibrary2/include/sublib2/sublib2.h
#ifndef __SUBLIB_2_H__ #define __SUBLIB_2_H__ #include <iostream> class sublib2 { public: void print() { std::cout << "Hello header only sub-library 2!" << std::endl; } }; #endif
-
subbinary/CMakeLists.txt
project(subbinary) add_executable(${PROJECT_NAME} main.cpp) # 使用别名 sub::lib1 从 subproject1 链接静态库 # 使用别名 sub::lib2 从 subproject2 链接仅标头的库 # 这将导致该目标的包含目录添加到该项目中 target_link_libraries(${PROJECT_NAME} sub::lib1 sub::lib2 )
-
subbinary/main.cpp
#include "sublib1/sublib1.h" #include "sublib2/sublib2.h" int main(int argc, char *argv[]) { sublib1 hi; hi.print(); sublib2 howdy; howdy.print(); return 0; }
4. 案例详解:LIO-SAM
-
tree
├── CMakeLists.txt ├── config │ ├── doc │ │ └── ... │ └── params.yaml ├── Dockerfile ├── include │ └── utility.h ├── launch │ ├── include │ │ ├── config │ │ │ ├── robot.urdf.xacro │ │ │ └── rviz.rviz │ │ ├── module_loam.launch │ │ ├── module_navsat.launch │ │ ├── module_robot_state_publisher.launch │ │ ├── module_rviz.launch │ │ └── rosconsole │ │ ├── rosconsole_error.conf │ │ ├── rosconsole_info.conf │ │ └── rosconsole_warn.conf │ └── run.launch ├── LICENSE ├── msg │ └── cloud_info.msg ├── package.xml ├── README.md ├── src │ ├── featureExtraction.cpp │ ├── imageProjection.cpp │ ├── imuPreintegration.cpp │ └── mapOptmization.cpp └── srv └── save_map.srv
-
CMakeLists.txt
# 设置 CMake 最低版本 cmake_minimum_required(VERSION 2.8.3) # 设置工程名称为 lio_sam project(lio_sam) # 设定编译类型为 release,发布时需要选择 release set(CMAKE_BUILD_TYPE "Release") # 将一个名为 CMAKE_CXX_FLAGS 的变量设置为 -std=c++11 # -std=c++11 是一个 C++ 编译器选项,它告诉编译器使用 C++11 标准进行编译 set(CMAKE_CXX_FLAGS "-std=c++11") # 用于设置 C++ 的编译标志(CMAKE_CXX_FLAGS_RELEASE)为在发布模式下使用的标志 # "-O3": 优化级别的选项,表示使用最高级别的优化,以使生成的代码尽可能地运行更快 # "-Wall": 警告选项,表示开启所有警告信息的显示,帮助开发人员避免潜在的错误 # "-g": 调试信息选项,表示生成包含调试信息的可执行文件,方便调试程序时进行跟踪和定位问题 # "-pthread": 多线程选项,表示在编译和链接过程中使用 POSIX 线程库,以支持多线程操作 set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g -pthread") # 用于在 ROS 中查找和加载 catkin 软件包,并将列出的其他组件作为依赖项添加到当前软件包 # catkin:catkin 是 ROS 的构建系统,用于构建、编译和管理 ROS 软件包 # REQUIRED:指示 catkin 构建系统这些软件包是必需的,如果找不到它们,构建过程将失败 # COMPONENTS:后面列出了一系列需要的软件包。这些软件包可以是 ROS 核心软件包,也可以是自定义的软件包 find_package(catkin REQUIRED COMPONENTS tf # 用于处理坐标变换 roscpp # ROS C++ 客户端库,用于编写 ROS 节点的 C++ 实现 rospy # ROS Python 客户端库,用于编写 ROS 节点的 Python 实现 cv_bridge # 用于在 OpenCV 和 ROS 图像消息之间进行转换 # pcl library pcl_conversions # 用于在 PCL(Point Cloud Library)和 ROS 点云消息之间进行转换 # msgs std_msgs # 包含一些常用的标准消息类型,例如整数、浮点数、字符串等 sensor_msgs # 包含传感器数据的消息类型,例如图像、激光扫描、IMU 数据等 geometry_msgs # 包含几何形状和姿态相关的消息类型,例如点、向量、位姿等 nav_msgs # 包含导航相关的消息类型,例如地图、路径、里程计数据等 message_generation # 用于生成 ROS 消息 visualization_msgs # 包含可视化相关的消息类型,例如标记、路径、图像标记等 ) # find_package 用于在项目中查找并加载指定软件包(例如库、工具或模块),通常用于在构建过程中检测和配置依赖项 find_package(OpenMP REQUIRED) # OpenMP(Open Multi-Processing)是一种支持多线程并行计算的库 find_package(PCL REQUIRED QUIET) # PCL(Point Cloud Library)是一个开源的点云处理库 find_package(OpenCV REQUIRED QUIET) # OpenCV(Open Source Computer Vision Library)是一个计算机视觉库,用于处理图像和视频数据 find_package(GTSAM REQUIRED QUIET) # GTSAM(Georgia Tech Smoothing and Mapping)库提供了一系列用于滤波、优化和建图的方法和工具 find_package(Boost REQUIRED COMPONENTS timer) # Boost 是一个 C++ 扩展库,提供了许多功能强大的组件和工具,其中 timer 组件用于测量程序运行时间和计时 # 将指定目录中的消息文件添加到 ROS 中,以便在编译和使用过程中可以使用这些消息定义 # DIRECTORY:指定消息文件所在的目录,目录名是 "msg"。 # FILES:指定要添加的消息文件的列表,要添加的文件是 "cloud_info.msg" add_message_files( DIRECTORY msg FILES cloud_info.msg ) # 告诉 ROS 在指定的目录中查找服务文件,并将它们添加到软件包中,以便在编译和运行时使用 # DIRECTORY srv:这是一个相对于软件包的目录路径,其中包含要添加的服务文件。此处 srv 是一个子目录,其中包含服务文件 # FILES save_map.srv:这是要添加到软件包的服务文件列表,此处为 save_map.srv add_service_files( DIRECTORY srv FILES save_map.srv ) # 用于生成 ROS 消息包中的消息类型,此处需要指定要使用的消息依赖项,以生成适当的消息文件 generate_messages( DEPENDENCIES geometry_msgs std_msgs nav_msgs sensor_msgs ) # 定义一个 catkin 软件包(package)。catkin 是 ROS 的构建系统,用于管理 ROS 软件包的编译和依赖关系 # INCLUDE_DIRS include:指定软件包需要包含的头文件目录,这里是 "include" 目录 # DEPENDS PCL GTSAM:指定软件包的依赖项,即 PCL 库和 GTSAM 库 # CATKIN_DEPENDS:指定软件包对其他 catkin 软件包的依赖关系,下面列出了具体的依赖项 catkin_package( INCLUDE_DIRS include DEPENDS PCL GTSAM CATKIN_DEPENDS std_msgs nav_msgs geometry_msgs sensor_msgs message_runtime message_generation visualization_msgs ) # 用于指定编译过程中所使用的头文件的搜索路径 # 通过将这些路径添加到搜索路径列表中,编译器可以在编译过程中找到并包含这些头文件 # include:表示当前项目中的 include 目录 # ${catkin_INCLUDE_DIRS}:这是一个变量,表示 catkin 构建系统的头文件目录(下同) include_directories( include ${catkin_INCLUDE_DIRS} ${PCL_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${GTSAM_INCLUDE_DIR} ) # 用来添加多个库文件目录到链接器的搜索路径中 # 确保链接器能够找到所需的库文件,从而成功地完成程序的链接过程 link_directories( include ${PCL_LIBRARY_DIRS} ${OpenCV_LIBRARY_DIRS} ${GTSAM_LIBRARY_DIRS} ) ########### ## Build ## ########### # Range Image Projection # 用于在 ROS 项目中创建并构建一个可执行文件 # 要生成的可执行文件的名称为 ${PROJECT_NAME}_imageProjection,源文件为 src/imageProjection.cpp add_executable(${PROJECT_NAME}_imageProjection src/imageProjection.cpp) # 指定了构建该可执行文件所依赖的其他目标 # ${catkin_EXPORTED_TARGETS} 是一个由 catkin 自动生成的变量,用于跟踪与当前包相关的导出目标 # ${PROJECT_NAME}_generate_messages_cpp 是另一个由 catkin 生成的变量,用于跟踪与 ROS 消息生成器相关的目标 add_dependencies(${PROJECT_NAME}_imageProjection ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) # 指定了与 ${PROJECT_NAME}_imageProjection 可执行文件链接的库 # ${catkin_LIBRARIES} 是一个由 catkin 自动生成的变量,包含了与 ROS 构建系统相关的库 # ${PCL_LIBRARIES} 和 ${OpenCV_LIBRARIES} 是额外的库变量,用于链接 PCL 点云库和 OpenCV 计算机视觉库 target_link_libraries(${PROJECT_NAME}_imageProjection ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES}) # Feature Association add_executable(${PROJECT_NAME}_featureExtraction src/featureExtraction.cpp) add_dependencies(${PROJECT_NAME}_featureExtraction ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) target_link_libraries(${PROJECT_NAME}_featureExtraction ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES}) # Mapping Optimization add_executable(${PROJECT_NAME}_mapOptmization src/mapOptmization.cpp) add_dependencies(${PROJECT_NAME}_mapOptmization ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) target_compile_options(${PROJECT_NAME}_mapOptmization PRIVATE ${OpenMP_CXX_FLAGS}) target_link_libraries(${PROJECT_NAME}_mapOptmization Boost::timer ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES} ${OpenMP_CXX_FLAGS} gtsam) # IMU Preintegration add_executable(${PROJECT_NAME}_imuPreintegration src/imuPreintegration.cpp) target_link_libraries(${PROJECT_NAME}_imuPreintegration Boost::timer ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES} gtsam)