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}")
参考资料
- https://cmake.org/