美文网首页
用CMake构建中等规模的工程

用CMake构建中等规模的工程

作者: RonZheng2010 | 来源:发表于2019-01-22 21:07 被阅读0次

    主要涉及如下主题:

      1. 目录布局
      1. 定义全局编译选项
      1. 交叉编译
      1. 编译开发者自己的库
      1. 编译第三方软件
      1. 引用库
      1. 编译可执行程序
      1. 安装配置文件
      1. 安装启动脚本
      1. CPack打包

    如下是示例工程:

    git clone https://github.com/sronzheng/cmake-demo.git
    

    1. 目录布局

    目录布局包括两方面,一是源代码的布局,二是部署到目标系统后的布局。

    源代码布局如下:

    • cmake是自定义的CMake脚本,3th-party是第三方软件,platform是依赖特定平台特性的库,common是平台无关的库,apps是应用程序,scripts是软件包的脚本,如启动脚本等。
    • 库的目录下:
      • include是对外开放的C++头文件,src下是只在库内部使用的C++头文件和实现文件,test是单元测试程序,example是使用库的例子。
    • apps的程序目录下:
      • src是C++头文件和实现文件, etc是配置文件。
    ├── cmake
    ├── 3th-party
    │   ├── gtest
    │   ├── jsoncpp
    │   └── protobuf
    ├── platform
    ├── common
    │   └── arithm
    │       ├── include
    │       ├── src
    │       ├── test
    │       └── example
    ├── apps
    │   └── hello
    │       ├── etc
    │       └── src
    └── scripts
    

    部署后的布局为:

    • include是库的头文件,lib是库,etc是配置文件,bin是程序和启动脚本等。
    ├── include
    ├── lib
    ├── etc
    └── bin
    

    2. 定义全局编译选项

    如果只改变单个target的编译选项,用add_definitions()就可以。如果是改变针对所有的target,更好的办法是设置CMake的变量:CMAKE_C_FLAGS和CMAKE_CXX_FLAGS,它们分别用于gcc和g++编译。

    # BuildType.cmake
     
    set (CMAKE_C_FLAGS           "-Wall -std=c++11 -pthread")
    set (CMAKE_C_FLAGS_DEBUG     "-g -ggdb")
    set (CMAKE_C_FLAGS_RELEASE   "-O2")
    
    set (CMAKE_CXX_FLAGS         "-Wall -std=c++11 -pthread")
    set (CMAKE_CXX_FLAGS_DEBUG   "-g -ggdb")
    set (CMAKE_CXX_FLAGS_RELEASE "-O2")
    

    3. 交叉编译

    在嵌入式开发中几乎总是要使用交叉编译,而且还可能需要编译到不同的目标平台。切换到新的目标平台时,整个编译环境,包括编译工具链,系统库等,都会改变。

    可以用CMake选项CMAKE_TOOLCHAIN_FILE指定一个文件,在这个文件中指定编译环境。

    cmake -DCMAKE_TOOLCHAIN_FILE=ArmToolchain.cmake .
    

    如下是一个CMAKE_TOOLCHAIN_FILE的例子。

    # 只有指定这个变量,CMake才认为是在进行交叉编译
    set (CMAKE_SYSTEM_NAME Linux) 
    
    # 指定编译器
    set (CMAKE_C_COMPILER /opt/arm/bin/arm-none-linux-gnueabi-gcc)
    set (CMAKE_CXX_COMPILER /opt/arm/bin/arm-none-linux-gnueabi-g++)
    
    # 指定目标环境的根目录
    set (CMAKE_FIND_ROOT_PATH /opt/arm)
    
    # FIND_PROGRAM()使用宿主机的程序,而不是目标环境的
    set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
    
    # FIND_LIBRARY()使用目标环境的库
    set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
    
    # FIND_PATH()和FIND_FILE() 使用目标环境的库
    set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
    

    4. 编译开发者自己的库

    编译库最小的要求是指出需要哪些源文件,还有头文件目录。这样就能make得到库了。

    include_directories (../include .)
    add_library (arithm SHARED Arithm.cpp)
    

    如果要支持make install或CPack打包,则还需要指出库的安装目录。这里将arithm库安装在lib下。

    install (TARGETS arithm 
             DESTINATION lib)
    

    直接在CMake语句中列出头文件目录,源代码文件或库的做法,不是很好看。更好的办法是用set()定义一组变量代表它们。

    5. 编译第三方软件

    ExternalProject_Add()用于引入外部软件包,它来自CMake的ExternalProject模块。它创建一个定制的target,并完成构建的一系列步骤:下载 - 更新 - 配置 - 编译 - 安装 - 测试。

    如下是编译Google Test的例子。Google Test本身用CMake编译。

    • URL项指出了gtest-release-1.8.0.tar.gz的地址,这里已经把它下载到本地源代码目录下,这是避免下载,节省时间的通常做法。
    • PREFIX项指出了编译输出目录的位置;
    • CMAKE_COMMAND项指定配置工具CMake,CMAKE_ARGS项是CMake的参数;
    • BUILD_COMMAND项指定编译工具make,INSTALL_COMMAND项指定安装工具make install;
    • gtest-release-1.8.0.tar.gz将在编译目录下展开,并用in-source build的方式编译。所以BUILD_IN_SOURCE项的值是1。
    # 引入ExternalProject模块
    include (ExternalProject)
    
    ExternalProject_Add (gtest
        PREFIX 3th-party/gtest
        URL ${CMAKE_SOURCE_DIR}/3th-party/gtest/src/gtest-release-1.8.0.tar.gz
        CMAKE_COMMAND cmake
        CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/3th-party/gtest
        BUILD_COMMAND make
        INSTALL_COMMAND make install
        BUILD_IN_SOURCE 1)
    

    如果编译成功,就可以在${CMAKE_BINARY_DIR}/3th-party/gtest下找到GoogleTest的头文件和库了。

    另外一个例子是ProtoBuf。ProtoBuf按照老式的configure方式配置。

    • 主要的不同是用CONFIGURE_COMMAND代替CMAKE_COMMAND;
    • 如果是交叉编译,需要手工提供configure的参数,而不是直接传递${CMAKE_TOOLCHAIN_FILE};
    if (TARGET_ARCH STREQUAL "ABC")
        set (PB_CMD_ARGS_CROSS --host=arm-linux-gnueabi)
    endif()
    
    set (PB_CMD_ARGS --with-protoc=/usr/bin/protoc
                            --disable-static
                            --enable-shared)
    
    ExternalProject_Add (protobuf
        PREFIX 3th-party/protobuf
        URL ${CMAKE_SOURCE_DIR}/3th-party/protobuf/src/protobuf-2.4.1.tar.gz
        CONFIGURE_COMMAND ./configure ${PB_CMD_ARGS_CROSS}  ${PB_CMD_ARGS} --prefix=${CMAKE_BINARY_DIR}/3th-party/protobuf
        BUILD_COMMAND make
        INSTALL_COMMAND make install
        BUILD_IN_SOURCE 1)
    

    如下是给make install准备的脚本,将protobuf安装在lib目录下。注意并没有给GoogleTest的安装脚本,因为GoogleTest不需要安装到目标系统中。

    install (DIRECTORY ${PROTOBUF_DIR}/include/google/protobuf/
             DESTINATION include/protobuf)
    
    install (DIRECTORY ${PROTOBUF_DIR}/lib/
             DESTINATION lib)
    

    6. 引用库

    编译好库之后,下一步是让其他库或程序方便地引用它。一个好的做法,是定义一组变量表示库的头文件目录和库。

    开发者自己的库是以CMAKE_SOURCE_DIR为基础的。

    set (MODULE_ROOT_DIR     ${CMAKE_SOURCE_DIR})
    set (COMMON_DIR          ${MODULE_ROOT_DIR}/common)
    
    set (ARITHM_LIBRARY      arithm)
    set (ARITHM_DIR          ${COMMON_DIR}/arithm)
    set (ARITHM_INCLUDE_DIR  ${ARITHM_DIR}/include)
    

    引用第三方库与引用用户自己的库类似,只是它以CMAKE_BINARY_DIR为基础,因为第三方库是在CMAKE_BINARY_DIR中展开源代码的。

    set (EXTERN_MODULE_ROOT_DIR  ${CMAKE_BINARY_DIR})
    set (3TH_PARTY_DIR           ${EXTERN_MODULE_ROOT_DIR}/3th-party)
    
    set (GTEST_DIR               ${3TH_PARTY_DIR}/gtest)
    set (GTEST_INCLUDE_DIR       ${GTEST_DIR}/include)
    set (GTEST_LIBRARY           ${GTEST_DIR}/lib/libgtest.a)
    
    set (PROTOBUF_DIR            ${3TH_PARTY_DIR}/protobuf)
    set (PROTOBUF_INCLUDE_DIR    ${PROTOBUF_DIR}/include)
    set (PROTOBUF_LIBRARY        ${PROTOBUF_DIR}/lib/libprotobuf.so)
    

    7. 编译可执行程序

    如下编译可执行程序,这里

    • 使用set()定义了一组变量让脚本看起来更整齐;
    • 使用了前面定义的库变量ARITHM_INCLUDE_DIR和ARITHM_LIBRARY。
    set (TARGET hello)
    
    set (INC_DIRS 
        ${ARITHM_INCLUDE_DIR}
        . )
    
    set (LIBS
        ${ARITHM_LIBRARY} )
    
    set (SRCS
        Hello.cpp )
    
    include_directories (${INC_DIRS})
    add_executable (${TARGET} ${SRCS})
    target_link_libraries (${TARGET} ${LIBS})
    

    如下是给make install准备的脚本,将demo安装在bin目录下:

    install (TARGETS ${TARGET} 
             DESTINATION bin)
    

    8. 安装配置文件

    很多程序可以依赖配置文件提供初始设置。一般把程序的配置文件放在一个目录下,如etc,让make install安装整个目录:

    install (DIRECTORY etc/
             DESTINATION etc)
    

    9. 安装启动脚本

    软件包一般会需要一个启动脚本和一个停止脚本,把它们放在scripts目录下。需要make install安装它们,安装到bin目录下。

    set (SCRIPT
         start.sh
         stop.sh)
    
    install (PROGRAMS ${SCRIPT} 
             DESTINATION bin)
    

    10. CPack打包

    CPack模块是CMake提供的工具,用于配置生成各种安装包,包括二进制安装包和源码安装包。

    • InstallRequiredSystemLibraries模块是CPack所依赖的,所以要先导入;
    • CPack变量CPACK_PACKAGE_VERSION_XXX指出安装包的版本信息,用户要给出它们的值;
    • 安装包的名字格式为<pkg_name>.<version_major>.<version_minor>.<version_patch>.tar.gz,比如demo.1.22.3333.tar.gz。
    include (InstallRequiredSystemLibraries)
    
    # 安装包名字,如果不指定,则使用project()指定的名字
    set (CPACK_PACKAGE_NAME demo)
    
    # 版本信息
    set (CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}")
    set (CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}")
    set (CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}")
    include (CPack)
    

    可以在一个C++头文件Version.hpp指定版本信息。这样各个模块可以用#include直接引用,并在启动时打印这些信息;同时CMake也可以从文件解析版本信息,并传递给CPack。

     // Version.hpp
    
    #define DEMO_VERSION_MAJOR 1
    #define DEMO_VERSION_MINOR 0
    #define DEMO_VERSION_PATCH 0
    

    用file()从Version.hpp读入版本信息,并用string()解析出将要传递给CPack的变量。

    file (READ Version.hpp VERSION_STR)
    
    string (REGEX REPLACE ".*#define +VERSION_MAJOR +([0-9]+).*" "\\1" VERSION_MAJOR "${VERSION_STR}")
    string (REGEX REPLACE ".*#define +VERSION_MINOR +([0-9]+).*" "\\1" VERSION_MINOR "${VERSION_STR}")
    string (REGEX REPLACE ".*#define +VERSION_PATCH +([0-9]+).*" "\\1" VERSION_PATCH "${VERSION_STR}")
    

    使用如下命令生成二进制安装包。

    cpack -C CPackConfig.cmake
    

    相关链接

    CMake 常用法
    用CMake构建中等规模的工程
    在CMake工程中使用NDK独立工具链 (一)
    在CMake工程中使用NDK独立工具链 (二)

    参考资料

    CMake 入门实战
    http://www.hahack.com/codes/cmake/

    CMake交叉编译配置
    https://blog.csdn.net/bytxl/article/details/50635788

    ExternalProject
    https://cmake.org/cmake/help/v3.0/module/ExternalProject.html

    相关文章

      网友评论

          本文标题:用CMake构建中等规模的工程

          本文链接:https://www.haomeiwen.com/subject/vkqldqtx.html