• 一个简单的CMake实例


    1.目录结构

    下面是CMake实例的目录结构。app1和app2目录保存可执行程序的源文件,编译后会生成app1和app2两个可执行程序。build目录是临时目录,用来保存CMake生成的文件和编译器编译生成的文件。common目录存放可执行程序和库的共用源文件,编译生成libcommon.a静态库。lib保存可执行程序需要的动态库源文件,编译生成libdy.so动态库。output目录存放安装后的文件。根据编译的类型分为debug和release目录,根据目标文件运行的平台又分为arm64和x86目录,bin保存安装的可执行程序,include保存安装的头文件,lib保存安装的静态库和动态库。

    .
    ├── app1
    │   └── app1.c
    ├── app2
    │   └── app2.c
    ├── build
    ├── CMakeLists.txt
    ├── common
    │   └── common.c
    ├── include
    │   ├── app1.h
    │   ├── app2.h
    │   ├── common.h
    │   └── lib.h
    ├── lib
    │   └── lib.c
    └── output
        ├── debug
        │   ├── arm64
        │   │   ├── bin
        │   │   │   ├── app1
        │   │   │   └── app2
        │   │   ├── include
        │   │   │   ├── app1.h
        │   │   │   ├── app2.h
        │   │   │   ├── common.h
        │   │   │   └── lib.h
        │   │   └── lib
        │   │       ├── libcommon.a
        │   │       └── libdy.so
        │   └── x86
        │       ├── bin
        │       │   ├── app1
        │       │   └── app2
        │       ├── include
        │       │   ├── app1.h
        │       │   ├── app2.h
        │       │   ├── common.h
        │       │   └── lib.h
        │       └── lib
        │           ├── libcommon.a
        │           └── libdy.so
        └── release
            ├── arm64
            │   ├── bin
            │   │   ├── app1
            │   │   └── app2
            │   ├── include
            │   │   ├── app1.h
            │   │   ├── app2.h
            │   │   ├── common.h
            │   │   └── lib.h
            │   └── lib
            │       ├── libcommon.a
            │       └── libdy.so
            └── x86
                ├── bin
                │   ├── app1
                │   └── app2
                ├── include
                │   ├── app1.h
                │   ├── app2.h
                │   ├── common.h
                │   └── lib.h
                └── lib
                    ├── libcommon.a
                    └── libdy.so
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    2.编译命令

    首先使用cmake生成Makefile,使用-D选项开启或关闭某些配置,具体的配置选项如下。

    # Release: 编译为Release版本(使用-O2 -DNDEBUG编译选项)。
    # Debug: 编译为Debug版本(使用-O0 -g编译选项)。
    # 不设置该选项,默认编译Debug版本。
    -DCMAKE_BUILD_TYPE=Release/Debug
    # aarch64-linux-gnu-gcc: 使用aarch64-linux-gnu-gcc编译器,目标文件运行在arm64平台上。
    # "": 设置为空,使用gcc编译,目标文件运行在x86平台上。
    # 不设置该选项,默认目标文件运行在x86平台上。
    -DCROSS_COMPILE=aarch64-linux-gnu-gcc/""
    # ON: 编译可执行程序APP1。OFF : 不编译可执行程序APP1。
    -DAPP1=ON/OFF
    # ON: 编译可执行程序APP2。OFF : 不编译可执行程序APP2。
    -DAPP2=ON/OFF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编译Debug 版本,目标运行在arm64平台上,同时编译APP1和APP2,编译命令如下,安装完成后,文件保存在output目录下。

    mkdir build
    cd build
    cmake -DCMAKE_BUILD_TYPE=Debug -DCROSS_COMPILE=aarch64-linux-gnu- -DAPP1=ON -DAPP2=ON ..
    make
    make install
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在build目录中使用make clean清除编译产生的文件。若要重新生成Makefile,则需要将build目录下的所有文件删除,使用cmake命令重新生成Makefile,然后再编译。

    3.CMake代码

    # CMAKE_BUILD_TYPE Release/Debug
    # CROSS_COMPILE aarch64-linux-gnu-gcc/""
    # APP1  ON/OFF
    # APP2  ON/OFF
    cmake_minimum_required(VERSION 3.00)
    
    project(CMAKE_DEMON)
    
    # cmake set info
    set(CMAKE_VERBOSE_MAKEFILE ON)
    # 开启后会生成文件compile_commands.json,其包含所有编译单元所执行的指令
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
    
    # 设置编译Release或Debug版本
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        set(BUILD_TYPE "release")
    else()
        set(CMAKE_BUILD_TYPE "Debug")
        set(BUILD_TYPE "debug")
    endif()
    
    # 设置Release和Debug版本的编译选项
    set(CMAKE_C_FLAGS_DEBUG "-O0 -g")
    set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
    set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
    set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")
    
    # 设置编译器
    set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
    set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++)
    set(CMAKE_STRIP ${CROSS_COMPILE}strip)
    
    if (CMAKE_C_COMPILER STREQUAL "aarch64-linux-gnu-gcc")
        set(PLATFORM arm64)
    else()
        set(PLATFORM x86)
    endif()
    
    if (MSVC)
        add_compile_options(/W4)    # warning level 4
    else()
        add_compile_options(-Wall)  # lots of warnings
    endif()
    
    # 共同的头文件
    include_directories(include)
    # 共同的链接库搜索目录
    link_directories(${PROJECT_BINARY_DIR})
    # 共同的链接库
    #link_libraries(xxx)
    
    # 定义预处理宏,类似于指定gcc -D选项
    # add_compile_definitions(CMAKE_DEMON_TEST) # 3.12及以上版本才有该命令
    # add_definitions(-DCMAKE_DEMON_TEST)
    
    # 将common目录下的源文件编译成libcommon.a
    file(GLOB COMMON_SRC common/*.c common/*/*.c)
    if(NOT COMMON_SRC)
        set(COMMON_LIB "")
    else()
        # 将common目录下的源文件编译成静态库
        add_library(common STATIC ${COMMON_SRC})
        # 给libcommon.a库添加链接库
        target_link_libraries(common rt pthread)
        # 所有可执行文件都需要链接libcommon.a
        set(COMMON_LIB common)
    endif()
    
    # 将lib目录下的源文件编译成libdy.so
    file(GLOB DY_SRC lib/*.c lib/*/*.c)
    if(NOT DY_SRC)
        set(DY_SRC "")
    else()
        # 将common目录下的源文件编译成静态库
        add_library(dy SHARED ${DY_SRC})
        # 给libcommon.a库添加链接库
        target_link_libraries(dy rt pthread)
        # 所有可执行文件都需要链接libdy.so
        set(DY_LIB dy)
    endif()
    
    # 调用xxx目录下的CMakeLists,生成的文件保存到${PROJECT_BINARY_DIR}/xx文件夹中
    # add_subdirectory(xxx xx)
    
    if(APP1)
        file(GLOB APP1_SRC app1/*.c app1/*/*.c)
        add_executable(app1 ${APP1_SRC})
        # 定义目标app1的预处理宏,类似于指定gcc -D选项
        # target_compile_definitions(app1 PRIVATE CMAKE_DEMON_APP1)
        # 定义目标app1的头文件目录,类似于指定gcc -I选项
        # target_include_directories(app1 PRIVATE app1_inc)
        # 给app1添加链接库,被链接的库放在前面
        target_link_libraries(app1 rt pthread ${COMMON_LIB} ${DY_LIB})
    endif()
    
    if(APP2)
        file(GLOB APP2_SRC app2/*.c app2/*/*.c)
        add_executable(app2 ${APP2_SRC})
        # 定义目标app2的预处理宏,类似于指定gcc -D选项
        # target_compile_definitions(app2 PRIVATE CMAKE_DEMON_APP2)
        # 定义目标app2的头文件目录,类似于指定gcc -I选项
        # target_include_directories(app2 PRIVATE app2_inc)
        # 给app2添加链接库,被链接的库放在前面
        target_link_libraries(app2 rt pthread ${COMMON_LIB} ${DY_LIB})
    endif()
    
    # 设置安装路径的前缀
    set(CMAKE_INSTALL_PREFIX 
        ${PROJECT_SOURCE_DIR}/output/${BUILD_TYPE}/${PLATFORM})
    # 安装编译生成的库、可执行程序
    install(TARGETS common dy app1 app2 
            ARCHIVE DESTINATION lib 
            LIBRARY DESTINATION lib 
            RUNTIME DESTINATION bin 
            RUNTIME DESTINATION bin)
    # 安装头文件
    file(GLOB HEADER_SRC include/*.h include/*/*.h)
    install(FILES ${HEADER_SRC} DESTINATION include)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    4.OBJECT

    当有多个目标依赖共用的源文件时,若把这些源文件添加到目标的源文件列表,则每生成一个目标,这些共用的源文件就会被编译一次。如下所示,app1和app2都依赖COMMON_SRC,若生成app1和app1,则COMMON_SRC会被编译两次,导致编译时间边长,编译占用的空间也会变大。

    add_executable(app1 ${APP1_SRC} ${COMMON_SRC})
    add_executable(app2 ${APP2_SRC} ${COMMON_SRC})
    
    • 1
    • 2

    当然也可以将共用的源文件编译成静态库,编译可执行文件或动态库时直接链接即可。如下所示,将COMMON_SRC编译成静态库,编译app1和app2时直接链接即可。

    add_library(common STATIC ${COMMON_SRC})
    
    add_executable(app1 ${APP1_SRC})
    target_link_libraries(app1 common)
    
    add_executable(app2 ${APP2_SRC})
    target_link_libraries(app1 common)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    若目标是静态库,两个静态库不会链接,也不会合并在一起,则这种方法失效。如下所示,common1不会链接common,这两个库是独立的。生成app1时,必须要同时链接common和common1两个库,若只链接common1,则会报找不到common的符号。

    add_library(common STATIC ${COMMON_SRC})
    add_library(common1 STATIC ${COMMON_SRC1})
    target_link_libraries(common1 common)  # 无效
    
    add_executable(app1 ${APP1_SRC})
    target_link_libraries(app1 common1 common)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    共用的源文件使用OBJECT编译,无论目标是可执行文件、动态库、静态库,直接引用即可,不会再次编译。如下所示,共用的源文件common使用OBJECT编译,需要common的地方直接引用TARGET_OBJECTS引用。

    add_library(common OBJECT ${COMMON_SRC})
    
    add_library(common1 STATIC ${COMMON_SRC1} $<TARGET_OBJECTS:common>)
    add_library(common2 SHARED ${COMMON_SRC2} $<TARGET_OBJECTS:common>)
    add_executable(app1 ${APP1_SRC} $<TARGET_OBJECTS:common>)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.生成文件名相同的动态库和静态库

    使用下面的代码,将会生成libcommon.a和libcommon.so库。

    add_library(common_static STATIC ${COMMON_SRC})
    # 将common_static重命名为common
    set_target_properties(common_static PROPERTIES  OUTPUT_NAME "common")
    # 构建一个新的target时,会尝试清理同名的target,如果没有清理,则不会构建
    set_target_properties(common_static PROPERTIES  CLEAN_DIRECT_OUTPUT 1)
    
    add_library(common SHARED ${COMMON_SRC}
    target_link_libraries(common lib)
    set_target_properties(common  PROPERTIES  CLEAN_DIRECT_OUTPUT 1)
    # 不管是父目录还是子目录的cmake脚本,引用libcommon.a必须使用target名字common_static
    install(TARGETS common common_static 
    	LIBRARY DESTINATION lib
    	ARCHIVE DESTINATION lib)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6.向父目录传递变量

    # 若是父目录,则不执行,避免报警告
    if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
    	# 向父目录传递LIBS变量
        set(LIBS lib_static PARENT_SCOPE)
    endif()
    
    # 父目录直接引用即可
    message(STATUS "LIBS = ${LIBS}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    参考资料

    1. https://cmake.org/
  • 相关阅读:
    jquery-qrcode客户端二维码生成类库扩展--融入自定义Logo图片
    Go连接Redis集群并实现单例,组件go-redis
    K8s高可用集群搭建
    JVM面试题-JVM对象的创建过程、内存分配、内存布局、访问定位等问题详解
    爱做梦的人工智能「Stabled Diffusion」
    Linux多线程概念及实现
    卷积神经网络大致结构、卷积层设计的参数、卷积结果计算公式、卷积参数的共享、池化层、卷积神经网络的具体结构、感受野、推荐使用的卷积神经网络
    Java SE 12 新增特性
    element-ui问题合集(el-input-number加减一次就失效,el-select同时收集id与name)
    scrcpy用法大全
  • 原文地址:https://blog.csdn.net/u011037593/article/details/126810770