Cmake 简介


  • Cmake 是什么

    1、Cmake高级编译配置工具。当多个人要用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件共享库,这时候Cmake 的作用就凸显出来了
    2、Cmake所有操作都是通过编译CMakeLists.txt所写内容来完成的
    3、学习Cmake的目的,是为了将来处理大型 C/C++/Java 项目做准备,其也是一种生成 Makefile 的工具


引入:Cmake 编译 hello_world 程序


  • 首先项目文件夹内中写一个名为main.cpphello_world源码

    // main.cpp
    
    #include <iostream>
    using namespace std;
    int main()
    {
        cout << "hello world";
        return 0;
    }
  • 使用Cmake编译,需要在项目文件夹创建CMakeLists.txt文件(注意区分大小写)

    # CMakeLists.txt
    
    project(HELLO CXX)
    
    set(SRC_LIST test.cpp)
    
    message(STATUS "This is BINARY_DIR " ${PROJECT_BINARY_DIR})
    
    message(STATUS "This is SOURCE_DIR " ${PROJECT_SOURCE_DIR})
    
    add_executable(hello ${SRC_LIST})
  • 当前目录打开终端,输入cmake .,使用 Cmake 进行编译,如果没有报错,会生成很多文件,再使用make编译生成可执行文件


Cmake 语法介绍


  • project命令

    • 说明

      1、project可以用来指定工程的名字支持的语言,默认支持所有语言
      2、project(HELLO):指定工程名HELLO,并支持所有语言
      3、project(HELLO CXX):指定工程名HELLO,并指定语言为 C++
      4、project(HELLO C CXX):指定工程名HELLO,并指定语言为 C 和 C++

    • 两个隐式变量

      1、该指定隐式声明两个 Cmake 变量
      2、<projectname>_BINARY_DIR,上例中为HELLO_BINARY_DIR
      3、<projectname>_SOURCE_DIR,上例中为HELLO_SOURCE_DIR
      4、当前该项目这两个变量都指向当前工作目录,后续会讲外部编译
      5、如果更改了工程名,这两个变量名也会更改。为了解决这个问题,可以使用Cmake 预定义两个变量PROJECT_BINARY_DIRPROJECT_SOURCE_DIR

  • set命令

    1、set可以用来显式指定变量
    2、set(SRC_LIST main.cpp):指定SRC_LIST变量包含main.cpp
    3、set(SRC_LIST main.cpp t1.cpp t2.cpp):指定SRC_LIST变量包含后面的所有内容
    4、set(SRC_LIST "main.cpp"):可以在文件名外"",如果源文件名包含空格,则必须用""包括

  • message命令

    1、message用于向终端输出用户自定义的信息,主要包含三种信息
    2、SEND_ERROR:产生错误,生成过程被跳过
    3、STATUS:输出前缀为--的信息
    4、FATAL_ERROR:立即终止所有 Cmake 过程

  • ADD_EXECUTABLE命令

    1、ADD_EXECUTABLE用于生成可执行文件
    2、ADD_EXECUTABLE(hello ${SRC_LIST}):生成的可执行文件名hello源文件来自读取SRC_LIST变量中的内容

  • 语法的基本原则

    1、变量使用${变量名}方式取值,但在IF 控制语句中是直接使用变量名
    2、命令参数使用()括起,参数之间使用空格或分号隔开
    3、命令不区分大小写,但参数和变量区分大小写


内部构建与外部构建


  • 上面的例子实际上就是内部构建,其产生的临时文件特别多不方便清理

  • 外部构建,就会把生成的临时文件放在build 文件夹,强烈推荐使用外部构建

  • 使用外部构建

    1、建立一个build 目录,可以在任何地方,推荐在项目当前目录下
    2、进入build 目录,运行cmake ..(..表示上级目录,仅适用于 build 在当前目录下的情况),当然也可以写CMakeLists.txt所在的绝对路径
    3、现在,生成的文件都在build 目录下了,在build 目录中使用make来构建工程

  • 注意外部构建的两个变量

    1、HELLO_BINARY_DIR:是编译路经,即编译时build 文件夹的路径
    2、HELLO_SOURCE_DIR:是工程路径,仍然是之前的工程路径


构建一个工程的规则与操作


  • 一个工程的开发和管理通常有以下规则:

    1、为工程添加一个子目录 src,用于存放工程源码
    2、添加一个子目录 doc,用来放置这个工程的文档hello.txt
    3、在工程目录添加文本文件COPYRIGHT版权文件README用户说明
    4、在工程目录添加runhello.sh脚本,用来调用 hello 二进制文件
    5、将构建后的目标文件放入构建目录bin 子目录
    6、将doc 目录的内容以及COPYRIGHTREADME安装(拷贝)到/usr/share/doc/cmake(此为 linux 路径)

  • 将目标文件放入构建目录的 bin 子目录

    • 创建一个src 目录,将源码移入src。注意工程的每个目录下都有要CMakeLists.txt,所以src下也需要

    • 重写CMakelists.txt

      目录树结构:
      
      .
      |---build
      |---CMakeLists.txt
      |---src
          |---CMakeLists.txt
          |---main.cpp
      # 根目录CMakeLists.txt
      
      project(HELLO)
      
      add_subdirectory(src ./bin)
      # src目录CMakelists.txt
      
      add_executable(hello main.cpp)
    • add_subdirectory命令

      1、完整参数add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
      2、该命令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置,写在最外层的CMakeLists.txt首先告知
      3、EXCLUDE_FROM_ALL函数是将写的目录从编译中排除,如程序中的 example
      4、add_subdirectory(src bin):将src 目录加入工程并指定编译输出的路径为 bin 目录。如果不指定 bin 目录,则所有编译结果都会存放在 src 目录中

    • 更改二进制的保存路径

      1、可以使用set命令重新定义EXECUTABlE_OUTPUT_PATHLIBRARY_OUTPUT_PATH变量,来指定最终的目标二进制的位置
      2、set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
      3、set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)


使用 CMake 来进行安装


  • 安装的命令

    1、一种是从代码编译后直接make install安装
    2、一种是打包时指定目录安装。两种指定目录的方法,make install DESTDIR=/tmp/test./configure -prefix=/usr

  • 如何安装 hello world

    1、使用 Cmake 一个新的命令installinstall的安装可以包括二进制动态库静态库以及文件目录脚本
    2、使用 Cmake 一个新的变量CMAKE_INSTALL_PREFIX,该变量用于指定安装的路径

    目录树结构:
    
    .
    |---build
    |---CMakeLists.txt
    |---COPYRIGHT
    |---README
    |---doc
        |---hello.txt
    |---runhello.sh
    |---src
        |---CMakeLists.txt
        |---main.cpp
  • 安装文件COPYRIGHTREADME

    1、在根目录CMakeLists.txt中添加:install(FILES COPYRIGHT README DESTINATION share/doc/cmake)
    2、FILES:安装的是文件
    3、DESTINATION:写绝对路径。也可以写相对路径,但相对路径的实际路径${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
    4、CMAKE_INSTALL_PREFIX默认是在/usr/local/(此为 linux 路径,windows 默认C:\Program Files (x86)\),在cmake时使用cmake -DCMAKE_INSTALL_PREFIX=/usr可以指定该变量的路径,也可以使用set命令更改变量地址

  • 安装脚本runhello.sh

    1、在根目录CMakeLists.txt中添加:install(PROGRAMS runhello.sh DESTINATION bin)
    2、PROGRAMS非目标文件的可执行程序的安装(比如脚本)

  • 安装目录 doc

    1、可以通过在 doc 目录下创建CMakeLists.txt,在其中安装目录下的文件
    2、也可以在根目录CMakeLists.txt中添加:install(DIRECTORY doc/ DESTINATION share/doc/cmake)
    3、DIRECTORY:安装的是目录。后面连接的是所在目录的相对路径
    4、注意,docdoc/差别很大。目录/结尾,将这个目录中的内容安装到指定目录;目录不以/结尾,则会将这个目录安装到指定目录

  • 补充:安装二进制动态库静态库

    1、安装上述都使用TARGETS说明,如install(TARGETS test.exe DESTINATION bin)
    2、可以用ARCHIVE特指静态库LIBRARY特指动态库RUNTIME特指二进制
    3、可以通过特指分别指定安装路径,如install(TARGETS test.so test.exe LIBRARY DESTINATION lib RUNTIME DESTINATION bin)

  • 安装过程

    cmake
    make
    make install

Cmake 构建动态库


  • 静态库与动态库的区别

    1、静态库拓展名一般为.a.lib动态库拓展名一般为.so.dll
    2、静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行
    3、动态库在编译时不会链接到目标程序中,即可执行文件无法单独运行

  • 构建实例

    • 目前目录中的内容

      目录树结构:
      
      ·
      |---build
      |---CMakeLists.txt
      |---lib
          |---CMakeLists.txt
          |---hello.hpp
          |---hello.cpp
      // hello.hpp
      #ifndef HELLO_HPP
      #define HELLO_HPP
      
      void hellofunc();
      
      #endif
      // hello.cpp
      #include "hello.hpp"
      #include <iostream>
      
      void hellofunc()
      {
          std::cout << "hello world";
      }
      # 根目录CMakeLists.txt
      
      project(HELLO)
      
      add_subdirectory(lib ./bin)
      # lib中的CMakeLists.txt
      
      set(LIBHELLO_SRC hello.cpp)
      
      add_library(hello SHARED ${LIBHELLO_SRC})
    • add_library命令

      1、add_library(hello SHARED ${LIBHELLO_SRC})
      2、hello:就是正常的库名,生成的名字前会加上 lib,最终产生的文件是libhello.so(linux 下为.so)
      3、SHARED:表示生成动态库,此外STATIC表示生成静态库
      4、${LIBHELLO_SRC}源文件


重命名和安装动态库及使用


  • 同时构建静态和动态库

    • 错误理解

      # 如果用这种方式,两种库名字是一样的,只会构建一个动态库,不会构建出静态库,尽管静态库后缀是.a
      add_library(hello SHARED ${LIBHELLO_SRC})
      add_library(hello STATIC ${LIBHELLO_SRC})
      
      # 修改静态库的名字,这样是可以的,但往往我们希望两种库名字相同
      add_library(hello SHARED ${LIBHELLO_SRC})
      add_library(hello_static STATIC ${LIBHELLO_SRC})
    • set_target_properties命令

      1、set_target_properties可以用来设置输出的名称
      2、对于动态库,还可以用来指定动态库版本API 版本
      3、注意命令只是更改了输出的名称,在Cmake操作仍然使用原始名称

      # lib中的CMakeLists.txt
      
      set(LIBHELLO_SRC hello.cpp)
      
      # 创建静态库名为 hello_static
      add_library(hello_static STATIC ${LIBHELLO_SRC})
      
      # 重命名静态库名称为 hello
      set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")
      # Cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,因此在下面构建动态库 libhello.so 时,就会清理掉 libhello.a,所以需要添加该设置(较新版本的 CMake 可以不用)
      set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUTS true)
      
      add_library(hello SHARED ${LIBHELLO_SRC})
  • 安装共享库和头文件

    • 本例我们要将hello.so动态库安装到/lib目录,将hello.hpp头文件安装到/include/hello目录(下方内容继续写入lib中的CMakeLists.txt中)

      # 文件放到该目录下
      install(FILES hello.hpp DESTINATION include/hello)
      
      # 二进制、静态库、动态库安装都使用 TARGETS
      # ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指二进制
      install(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
  • 使用头文件即动态链接库

    • 找不到头文件

      1、使用include_directories命令可以向工程添加多个特定的头文件搜索路径,路径之间用空格隔开
      2、在CMakeLists.txt中写入:include_directories(路径1 路径2)

    • 找不到引用函数

      1、方法一:使用link_directories命令可以添加非标准的动态库搜索路径
      2、指定第三方库所在的路径link_directories(路径1 路径2)
      3、方法二:使用target_link_libraries命令可以添加需要链接的动态库
      4、用这种方法只要给出动态库的名字就行(这种方法对静态库也通用)
      5、注意要写在executable命令生成可执行文件后target_link_libraries(可执行文件名 动态库名)


补充


  • cmake_minimum_required命令

    1、cmake_minimum_required(VERSION 2.7)
    2、指定 Cmake 最低版本,如上指定 Cmake 不能低于 2.7 版本

  • find_package命令

    1、find_package(imgui [REQUIRED])
    2、在计算机中查找第三方库,首先应确保安装好了对应的库
    3、REQUIRED表示这个库是必须的,没有则会报错

  • file(GLOB)命令

    1、file(GLOB SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.cpp" "${PROJECT_SOURCE_DIR}/src/*.hpp")
    2、通过通配符可以便捷引入源码文件,注意双引号

  • ${CMAKE_PROJECT_NAME}关键字:替换为project()指定的工程名

  • target_compile_features命令

    1、target_compile_features(${CMAKE_PROJECT_NAME} xcc_std_17)
    2、打开对C++17 标准的支持

  • C++第三方库的安装

    1、可以直接下载源码,然后手动构建并指定 Cmake 库的路径
    2、对于 Linux 和 Mac,可以直接使用包管理工具安装
    3、使用微软开源工具 Vcpkg 安装,类似于 python 的 pip,github 地址。只需要调用 vcpkg 安装第三方库,然后在 Cmake 构建时指定 vcpkg 工具链即可。命令行只需要额外传递一个参数-DCMAKE_TOOLCHAIN_FILE=<vcpkg安装路径>/scripts/buildsystems/vcpkg.cmake,如果用的是VScode 插件,只需要在设置Configure Settings中添加:"CMAKE_TOOLCHAIN_FILE": "路径"即可


页底评论