一个简单的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

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

编译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

在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)

4.OBJECT

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

add_executable(app1 ${APP1_SRC} ${COMMON_SRC})
add_executable(app2 ${APP2_SRC} ${COMMON_SRC})

当然也可以将共用的源文件编译成静态库,编译可执行文件或动态库时直接链接即可。如下所示,将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)

若目标是静态库,两个静态库不会链接,也不会合并在一起,则这种方法失效。如下所示,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)

共用的源文件使用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>)

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)

6.向父目录传递变量

# 若是父目录,则不执行,避免报警告
if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
	# 向父目录传递LIBS变量
    set(LIBS lib_static PARENT_SCOPE)
endif()

# 父目录直接引用即可
message(STATUS "LIBS = ${LIBS}")

参考资料

  1. https://cmake.org/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值