CMake学习-All in one

参考引用

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)
    

猜你喜欢

转载自blog.csdn.net/qq_42994487/article/details/131922668