• [CMake] CMake 基础命令


    前言

    本文主要是对 https://github.com/ttroy50/cmake-examples 项目进行学习,并记录。本章主要包含了一些常用的 CMake 基本命令,以及存在子项目时的相关内容。

    基础指令

    指定基本信息

    首先需要注意的一点是,开头需要指定使用的 Cmake 版本,以及给 Project 一个名字:

    cmake_minimum_required(VERSION 3.5)
    project(test C CXX)  # 可以同时指定项目所使用的语言
    
    • 1
    • 2

    然后可以根据需求设置相关的编译语言版本:

    set(CMAKE_C_STANDARD 11)
    set(CMAKE_CXX_STANDARD 17)
    
    • 1
    • 2

    这里就引入 cmake 中 变量(variable)的概念。 其基本都是全大写的名字,防止与用户输入的其他命令混淆。只是这里有一部分 cmake 自己预留的变量名了。

    例如:

    CMAKE_SOURCE_DIR : 文件根目录
    CMAKE_CURRENT_SOURCE_DIR:目前源文件的根目录
    PROJECT_SOURCE_DIR:当前cmake 项目的根目录
    CMAKE_CURRENT_BINARY_DIR:当前根路径的 build 目录
    CMAKE_BINARY_DIR:根目录的 build 目录
    PROJECT_BINARY_DIR:目前项目的 build 目录

    添加源文件

    其中 set 就是我们直接指定,需要完全把需要 set 给变量的内容写出来,没办法通过通配符省事。

    set(SOURCES src/A.cpp  src/B.cpp)
    
    • 1

    不可以用下面的形式:

    set(SOURCEs src/*.cpp)
    
    • 1

    否则 SOURCES 打印出来的内容就是 “src/*.cpp” 不具有实际意义。

    通配符查找可以使用 file 命令来定义:

    file(GLOB SOURCES src/*.cpp) 
    
    • 1

    添加头文件

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

    假设文件目录结构如上所示,除了源文件,我们还需要添加头文件才可以完成程序编译。这里可以使用 target_include_directories() 命令添加,它相当于添加编译命令 -I /dir/path。

    target_include_directories(target PRIVATE ${PROJECT_SOURCE_DIR}/include)
    
    • 1

    注意一点,这里第一个参数就是 target,意思是把路径加到哪个目标上,所以我们得现有一个编译的目标,可以是库或者可执行文件。所以定义顺序应该是:

    add_executable(hello ${SOURCES})
    target_include_directories(hello PRIVATE ${PROJECT_SOURCE_DIR}/include)
    
    • 1
    • 2

    编译静态库

    编译静态库可以使用下面的命令:

    add_library(hello_lib STATIC src/Hello.cpp)
    
    • 1

    注意这里还是需要在后面添加编译库需要的头文件才可以正常编译。

    target_include_directories(hello_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
    • 1

    上面添加的头文件路径将会用在以下两种情况:

    • 编译本项目的库
    • 编译其他需要链接此库的目标时

    PRIVATE : 路径只能在编译当前库中使用

    INTERFACE : 路径还会添加到其他任何链接这个库的目标头文件路径中

    PUBLIC:上面的总和,即添加到当前 target 的头文件目录中,同时相关路径也会添加到链接此库的 target 中。

    这里添加的时 include 的总目录,所以也推荐在代码中包含头文件也带有路径信息

    #include “static/Hello.h” 这样即使有同名的头文件,但是在不同子文件夹中也可以分辨。

    链接静态库

    相应的,我们添加一个可执行对象,然后使用 target_link_libraries() 命令链接相关库就可以了。这个命令同时还会将相关库的任何设为 PUBLIC 或者是 INTERFACE 的头文件路径一块添加到可执行文件编译命令中。如果上面 target_include_directories 使用的是 PRIVATE,这里在编译可执行文件时会报找不到 static/Hello.h

    add_executable(hello src/main.cpp)
    target_link_libraries(hello PRIVATE hello_lib)
    
    • 1
    • 2

    编译动态库

    add_library 同样可以用来编译动态链接库,只将上面你的 STATIC 变为 SHARED 即可。

    add_library(hello_lib SHARED src/Hello.cpp)
    
    • 1

    还可以在下面额外添加一个别名目标,可以在上下文中使用别名:

    add_library(hello::lib ALIAS hello_lib)
    add_executable(hello src/main.cpp)
    target_link_libraries(hello PRIVATE hello::lib)
    
    • 1
    • 2
    • 3

    允许安装(make install)

    CMake 支持安装操作,运行用户安装自己的二进制文件,库或者是其他文件。基础的安装路径是被 CMAKE_INSTALL_PREFIX 变量控制的。在 Linux 上默认是 /usr/local

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ryGELP0-1670061089648)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/42b2e5da-0a5d-438b-a41c-3c668fe24c32/Untitled.png)]

    上面是目录组织结构,然后下面是相对完整的一个 Cmake 文件:

    cmake_minimum_required(VERSION 3.5)
    project(install CXX)
    set(CMAKE_CXX_STANDARD 17)
    
    message("INSTALL PREFIX: " ${CMAKE_INSTALL_PREFIX})
    
    add_library(hello_lib SHARED src/Hello.cpp)
    target_include_directories(hello_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
    add_executable(hello_bin src/main.cpp)
    target_link_libraries(hello_bin PRIVATE hello_lib)
    
    install(TARGETS hello_bin DESTINATION bin)
    install(TARGETS hello_lib DESTINATION lib)
    install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION include)
    install(FILES cmake-examples.conf DESTINATION etc)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    install 实际可以看作是一个拷贝命令,将 install 命令里的内容拷贝到我们指定的目录下,使在环境中也可以运行。

    TARGETS 主要用来移动生成的库和二进制文件,
    DIRECTORY 用来批量移动路径下的所有头文件
    FILES 用来单独移动一个目标文件

    以上 DESTNITION 后面跟的路径,完整路径实际就是:${CMAKE_INSTALL_PREFIX}/folder_name

    然后运行我们的编译命令:

    cmake -B build .
    cd build
    make install
    
    ./hello_bin
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面的安装路径会保存在本地的 install_manifest.txt 文件中。

    编译类型

    CMake 支持不同的编译类型选项。不同的类型对应了不同程度的优化等级。

    • Release : -O3 -DNDEBUG
    • Debug : -g
    • MinSizeRel:-Os -DNDEBUG
    • RelWithDebInfo:-O2 -g -DNDEBUG

    在编译时增加 -DCMAKE_BUILD_TYPE 命令来控制。 默认的 CMake 是不加任何优化选项的。对于一些项目可能需要添加默认的编译类型,下面的语句可以做到:

    if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
      message("Setting build type to 'RelWithDebInfo' as none was specified.")
      set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
      # Set the possible values of build type for cmake-gui
      set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
        "MinSizeRel" "RelWithDebInfo")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    设置 C++ 标志

    CMake 中比较推荐的设置 C++ flags 的方式是使用 target_compile_definitions() 命令。

    taeget_compile_definitions(hello_bin PRIVATE EX3)
    
    • 1

    上面将会在编译时添加 -DEX3 这个命令。 上面的 PRIVATE 同样是控制是否将相关 flags 传递给依赖这个 target 的目标。
    上述同样可以通过 target_compile_options() 命令实现。 当然也可以通过 set 命令来设置:

    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2")
    
    • 1

    相似的也可以通过编译时添加相关配置:

    cmake -DCMAKE_CXX_FLAGS=-DEX3 .
    
    • 1

    使用第三方库

    几乎所有的重要项目都会依赖第三方库 , CMake 支持使用 find_package() 自动查找这些工具路CMake 将会从 CMAKE_MODULE_PATH 中的路径搜索 名字为 Findxxx.cmake 的文件。

    以 Boost 库为例:

    find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
    
    • 1

    其中:

    • Boost : 就是我们要找的目标库。用来查找目标的 FindBoost.cmake 文件。
    • 1.46.1 : 是最小的 boost 版本。
    • REQUIRED :告诉 module 这个库是必需的,如果找不到就要报错
    • COMPONENTS : 是需要使用的 Boost 组件。

    运行上面的命令,可能会有下面的内容输出:

    -- Found Boost: /usr/include (found suitable version "1.65.1", minimum required is "1.46.1") found components: filesystem system
    
    • 1

    因为一个库可以包含很多个组件,上面我们只需要使用 system 和 filesystem 组件即可。 在使用上面的同时,会自动定义一个变量是: xxx_FOUND(这里的例子是 Boost_FOUND)来 check 系统中是否找到目标依赖库。

    如果找到了也会有

    xxx_INCLUDE_DIRS 变量表示依赖库头文件。
    xxx_LIBRARY 表示依赖库的路径。

    if(Boost_FOUND)
      message("Boost FOUND")
      include_directories(${Boost_INCLUDE_DIRS})
      message(" Boost INCLUDE DIRS: " ${Boost_INCLUDE_DIRS})
    else()
      message("Boost NOT FOUND!!")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    找到依赖库之后有两种形式可以使用:

    add_exeutable(hello_bin main.cpp)
    
    # 使用 alias nanme
    target_link_libraries(hello_bin PRIVATE Boost::filesystem)
    
    # 直接使用变量
    # target_include_directories(hello_bin PRIVATE ${Boost_INCLUDE_DIRS})
    # target_link_libraries(hello_bin PRIVATE ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用 Clang 编译

    CMake 支持指定编译器来编译项目:

    • CMAKE_C_COMPILER : C 语言的编译器
    • CMAKE_CXX_COMPILER:C++ 编译器
    • CMAKE_LINKER:链接器
    cmake -DCMAKE_CXX_COMPILER=clang++ 。
    
    • 1

    使用 Ninja 编译

    使用 cmake —help 可以看到 cmake 支持的所有生成器。使用 -G xxx 即可指定生成器。

    cmake -G Ninja .
    
    • 1

    加载模块

    正如前面在使用第三方库部分提到的,在 CMake v3.5+ 之后,支持使用 alias targets 来加载模块:

    target_link_libraries(hello_bin PRIVATE Boost::filesystem)
    
    • 1

    C++ 标准

    在比较老的版本可以使用以下内容来指定 CXX_STANARD:

    # $ cmake --version
    cmake_minimum_required(VERSION 2.8)
    
    # Set the project name
    project (hello_cpp11)
    
    # try conditional compilation
    include(CheckCXXCompilerFlag)
    CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
    CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
    
    # check results and add 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.")
    endif()
    
    # Add an executable
    add_executable(hello_cpp11 main.cpp)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    随着升级在 v3.1 及之后可以直接通过 set 来指定:

    set(CMAKE_CXX_STANDARD 11)
    
    • 1

    或者支持自动检测:

    # set the C++ standard to the appropriate standard for using auto
    target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)
    
    # Print the list of known compile features for this version of CMake
    message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    sub-projects

    很多大型项目都是由不同对的库和二进制文件组成的,这样就会形成很多文件夹和子项目。

    $ tree
    .
    ├── CMakeLists.txt
    ├── subbinary
    │   ├── CMakeLists.txt
    │   └── main.cpp
    ├── sublibrary1
    │   ├── CMakeLists.txt
    │   ├── include
    │   │   └── sublib1
    │   │       └── sublib1.h
    │   └── src
    │       └── sublib1.cpp
    └── sublibrary2
        ├── CMakeLists.txt
        └── include
            └── sublib2
                └── sublib2.h
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    文件目录结构如上。 其中:
    subbinary : 是要编译一个可执行文件
    subbinary1 : 是要编译一个依赖库
    subbinary2:只有头文件

    添加子路径:

    一个 CMakeLists.txt 可以包含并调用子路径中的 CMakeLists.txt。 使用 add_subdirectory() 命令。

    当使用 project() 命令创建项目时,CMake 会自动定义一系列的变量用表示一些项目的细节,例如:

    project(sublibrary1)
    
    • 1

    就可以使用 ${sublibrary1_SOURCE_DIR} 来表示子路径。相关的变量名如下:

    • PROJECT_NAME : 设置的项目名称
    • CMAKE_PROJECT_NAME:最上层的项目名称
    • PROJECT_SOURCE_DIR:当前项目的目录
    • PROJECT_BINARY_DIR:当前目录下的 build 路径
    • name_SOURCE_DIR:名字叫 name 的项目的目录
    • name_BINARY_DIR:名字叫 name 的二进制文件路径

    如果有一个依赖库只有头文件,那么 CMake 支持使用 INTERFACE 来创建一个没有任何输出的 target。

    add_library(${PROJECT_NAME} INTERFACE)
    
    • 1

    然和也要使用 INTERFACE 模式来添加相关的头文件路径,以使后面使用相关 target 的项目都可以找到头文件:

    target_include_directories(${PROJECT_Name} INTERFACE ${PROJECT_SOURCE_DIR}/include)
    
    • 1

    如果一个子项目编译出来的是链接库,则不需要输入路径,可以直接就使用 target_link_libraries() 链接。

    target_link_libraries(subbinary PUBLIC sublibrary1)
    
    # 也可是使用别名,使调用更清晰
    add_library(sublibrary1 )
    add_library(sub::sub1 ALIAS sublibrary1 )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后使用直接就可以:

    target_link_libraries(hello PUBLIC sub::sub1)
    
    • 1

    下面直接贴出完整的 CMakeLists.txt。

    # subprojects
    cmake_minimum_required(VERSION 3.5)
    project(subprojects)
    
    add_subdirectory(sublibrary1)
    add_subdirectory(sublibrary2)
    add_subdirectory(sublibrary)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后下面是各个子目录的 CMakeLists.txt :

    sublibrary1 文件夹:

    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
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    sublibrary2 文件夹:

    
    project (sublibrary2)
    
    add_library(${PROJECT_NAME} INTERFACE)
    add_library(sub::lib2 ALIAS ${PROJECT_NAME})
    
    target_include_directories(${PROJECT_NAME}
        INTERFACE
            ${PROJECT_SOURCE_DIR}/include
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    subbinary 文件夹:

    project(subbinary)
    
    # Create the executable
    add_executable(${PROJECT_NAME} main.cpp)
    
    # Link the static library from subproject1 using its alias sub::lib1
    # Link the header only library from subproject2 using its alias sub::lib2
    # This will cause the include directories for that target to be added to this project
    target_link_libraries(${PROJECT_NAME}
        sub::lib1
        sub::lib2
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到 CMake 版本在最上层指定依次就可以了。

  • 相关阅读:
    leetcode--541. 反转字符串II
    usb设备一直连接异常
    Linux开发工具之自动化构建工具-make/Makefile
    网络协议攻击
    傻瓜式快速下载TCGA数据(win x86版本)
    三天吃透MySQL面试八股文
    竞赛选题 深度学习LSTM新冠数据预测
    Flutter 教程之使用 Flutter 构建 Chrome 扩展(教程含源码)
    VScode多文件编译/调试配置
    1000 mil = 2.54 cm , 板子导电速度约为 1.5x10^8 m/s
  • 原文地址:https://blog.csdn.net/Chris_zhangrx/article/details/128164319