CMake learning-All in one

References

1 Introduction

1.1 What is CMake

1.1.1 CMake background

insert image description here

1.1.2 CMake definitions
  • CMake is a cross-platform installation (compilation) tool , which can describe the installation (compilation) process of all platforms with simple sentences. It can output various makefiles or project files . The CMake configuration file name is CMakeLists.txt, that is, write CMake code in the file CMakeLists.txt . In a word: CMake is a language that combines multiple cpp and hpp files into a large project
1.1.3 The difference between CMake and Make
  • CMake is a cross-platform build automation tool that can generate build files for different compilers, operating systems, and build environments. CMake generates Makefile or other build scripts by parsing the CMakeLists.txt file. Before executing the make command, you need to use the cmake command to generate the build script
  • Make is a rule-based build tool that automatically compiles source code and generates executables. Make controls the compilation process by reading the rules in the Makefile, and automatically detects modified files for recompilation

insert image description here

1.2 CMake syntax features

  • Basic syntax format : instruction (parameter 1 parameter 2...)
    • Parameters are enclosed in parentheses
    • Parameters are separated by spaces or semicolons
  • Instructions are case-independent, parameters and variables are case-independent
  • Variables use ${} to take values, but in the if control statement, the variable name is directly used

1.3 CMake Common Commands and Variables

  • cmake_minimum_required
    • Specifies the minimum version requirements for CMake
    # CMake最小版本要求为2.8.3
    # cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
    cmake_minimum_required(VERSION 2.8.3)
    
  • project
    • Define the project name and specify the languages ​​supported by the project
    # 指定工程名为 HELLOWORLD
    # project(projectname [CXX] [C] [Java])
    project(HELLOWORLD)
    
  • set
    • explicitly define variables
    # 定义 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
    • Add multiple specific header file search paths to the project
    # 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
    # include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
    include_directories(/usr/include/myincludefolder ./include)
    
  • link_directories
    • Add multiple specific library file search paths to a project
    # 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
    # link_directories(dir1 dir2 ...)
    link_directories(/usr/lib/mylibfolder ./lib)
    
  • add_library
    • Generate library files
    # 通过变量 SRC 生成 libhello.so 共享库
    # add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
    add_library(hello SHARED ${
          
          SRC})
    
  • add_compile_options
    • Add compilation parameters
    # 添加编译参数 -Wall -std=c++11 -O2
    add_compile_options(-Wall -std=c++11 -O2)
    
  • add_executable
    • generate executable
    # 编译 main.cpp 生成可执行文件 main
    # add_executable(exename source1 source2 ... sourceN)
    add_executable(main main.cpp)
    
  • target_link_libraries
    • Add the library that needs to be linked to the target
    # 将 hello 动态库文件链接到可执行文件 main
    # target_link_libraries(target library1<debug | optimized> library2...)
    target_link_libraries(main hello)
    
  • add_subdirectory
    • Add a subdirectory for storing source files to the current project, and specify where the intermediate binary and target binary are stored
    # 添加 src 子目录,src 中需有一个 CMakeLists.txt
    # add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
    add_subdirectory(src)
    
  • aux_source_directory
    • Find all source code files in a directory and store the list in a variable, this directive is temporarily used to automatically build the source file list
    # 定义 SRC 变量,其值为当前目录下所有的源代码文件
    aux_source_directory(. SRC)
    # 编译 SRC 变量所代表的源代码文件,生成 main 可执行文件
    add_executable(main ${
          
          SRC})
    

  • CMAKE_CXX_FLAGS
    • g++ compile options
    # 在 CMAKE_CXX_FLAGS 编译选项后追加 -std=c++11,采用 C++11 标准
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    
  • CMAKE_BUILD_TYPE
    • Compilation 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
    • The content referred to by these three variables is consistent
    • If it is in source build, it refers to the top-level directory of the project
    • If it is an out-of-source compilation, it refers to the directory where the project compilation takes place
    • PROJECT_BINARY_DIR is slightly different from other directives, but now it can be understood that they are consistent
  • CMAKE_SOURCE_DIR、PROJECT_SOURCE_DIR、_SOURCE_DIR
    • The content referred to by these three variables is consistent, no matter what compilation method is used, it is the top-level directory of the project
    • That is, when in source build, he is consistent with variables such as CMAKE_BINARY_DIR
    • PROJECT_SOURCE_DIR is slightly different from other directives, but now it can be understood that they are consistent
  • CMAKE_CXX_COMPILER
    • Specify the C++ compiler
  • EXECUTABLE_OUTPUT_PATH
    • Path to store executable output
  • LIBRARY_OUTPUT_PATH
    • Storage path of library file output

2. CMake Basic Routine

2.1 hello-CMake

  • Workspace
    $ 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) parse

      • CMake builds include a project name, and the command automatically generates some variables that make it easier to refer to certain variables when using multiple projects. For example, the variable PROJECT_NAME is generated: PROJECT_NAME is the variable name, ${PROJECT_NAME} is the variable value, and the value is hello_cmake
    • add_executable() analysis

      • Specify some source files to generate executable files, in this case the source file is main.cpp
      • The first parameter is the executable file name, the second parameter is the list of source files to compile
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./hello_cmake
    
  • file tree
    ├── build
        ...
    ├── CMakeLists.txt
    └── main.cpp
    

2.2 Include header files

  • Workspace
    $ 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 指定了库的范围
    
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    
    # 运行 make 命令时,输出仅显示构建状态
    $ make
    # 要查看用于调试目的的完整输出,可以使用 make VERBOSE=1 命令
    #$ make VERBOSE=1    
    
    # 执行
    $ ./hello_headers
    
  • file tree
    ├── build
    ...
    ├── CMakeLists.txt
    ├── include
    │   └── Hello.h
    └── src
        ├── Hello.cpp
        └── main.cpp
    

2.3 Include static library

  • Workspace

    $ cd ~/cmake_ws
    $ mkdir demo03 && cd demo03
    
  • Hello.h

    • Same as 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 不会被暴露出去
    
  • Compile and execute

    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./hello_binary
    
  • file tree

    ├── CMakeLists.txt
    ├── include
    │   └── static
    │       └── Hello.h
    └── src
        ├── Hello.cpp
        └── main.cpp
    

2.4 Include dynamic library

The difference between dynamic library and static library

  • The extension of the static library is generally *.a or *.lib
  • The extension of the dynamic library is generally *.so or *.dll
  • The static library will be directly integrated into the target file when compiling, and the compiled executable file can run independently
  • The dynamic library will not be integrated into the target file when compiling, and the executable program cannot run alone, and a dynamic library file is required
  • Workspace
    $ cd ~/cmake_ws
    $ mkdir demo04 && cd demo04
    
  • Hello.h
    • Same as 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
    )
    
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./hello_binary
    
  • file tree
    ├── build
    ...
    ├── CMakeLists.txt
    ├── include
    │   └── shared
    │       └── Hello.h
    └── src
        ├── Hello.cpp
        └── main.cpp
    

2.5 Set build type

  • Workspace
    $ 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)
    
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./build_type
    
  • file tree
    ├── build
    ...
    ├── CMakeLists.txt
    ├── main.cpp
    

2.6 Set the compilation method

  • Workspace
    $ 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"
    
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./compile_flags
    
  • file tree
    ├── build
    ...
    ├── CMakeLists.txt
    ├── main.cpp
    

2.7 Include third-party libraries

  • Workspace
    $ 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}
    ) #>
    
  • Compile and execute
    # 外部构建与编译
    $ mkdir build
    $ cd build/
    $ cmake ..
    $ make
    
    # 执行
    $ ./third_party_include
    
  • file tree
    ├── build
    ...
    ├── CMakeLists.txt
    ├── main.cpp
    

2.8 Use clang to compile the project

gcc/g++ and clang/clang++ are commonly used C/C++ compilers under Linux

  • Workspace
    $ 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
    # ```
    
  • Compile and execute
    $ mkdir build.clang
    $ cd build.clang/
    $ cmake .. -DCMAKE_C_COMPILER=clang-3.6 -DCMAKE_CXX_COMPILER=clang++-3.6
    $ make
    
    $ ./hello_cmake
    
  • file tree
    ├── build.clang
        ...
    ├── CMakeLists.txt
    ├── main.cpp
    ├── pre_test.sh
    ├── run_test.sh
    

2.9 C++ Standard

# 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. Subproject CMake

  • Many large projects consist of different libraries and binaries, which can be organized using multiple CMakeLists.txt files

  • file tree

    • subbinary: an executable file
    • sublibrary1: a static library
    • sublibrary2: library with only header files
    ├── 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
    
  • Workspace

    $ cd ~/cmake_ws
    $ mkdir demo09 && cd demo09
    
  • Top-level 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. Detailed case explanation: 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)
    

Guess you like

Origin blog.csdn.net/qq_42994487/article/details/131922668