一句话结论:本文归纳CMake构建c++工程的基本用法,实现多依赖、多工程、多文件格式的工程编译构建。
1.简介
CMake(cross-platform make)是一个跨平台编译工具,它不能直接生成最终可执行程序,而是构建各平台标准的构建文档(如Linux的Makefile、Windows的sln)。
使用时,用户不再需要直接编写底层Makefile文件,而是编写语法简单的CMakeLists.txt文件,CMake在执行目录下查找CMakeLists.txt来生成Makefile、构建工程,大幅降低工程构建和编译的难度。
2.简单工程
- output
- build
- tests
- test0
- main.cc
- CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# set c++11
set(CMAKE_CXX_FLAGS "$GUN_FLAGS -std=c++11")
# set output dir
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../output) # based on current CMakeLists.txt
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../output) # .exe
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../output) # .a
# build test0
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test0 SRC_TEST0) # collect all cxx files in $test0
add_executabel(test0 ${SRC_TEST0})
在项目构建和编译过程中会生成大量中间文件,为保持源代码目录纯净,一般在其他目录新建一个build目
mkdir build
cd build
cmake ../tests/
make
3.生成并使用链接库
目的:在A工程下,生成动态链接库,在B工程下调用。
每个带cxx的模块下都需要有CMakeLists.txt来描述当前模块的编译配置。
- output
- build
- root
- libs
- B
- B.h
- B.cc
- CMakeLists.txt
- tests
- testA
- main.cc
- CMakeLists.txt
libs/B下的CMakeLists.txt
file(GLOB_RECURSE SRC_LIBS_B
${CMAKE_CURRENT_SOURCE_DIR}/B/*.h
${CMAKE_CURRENT_SOURCE_DIR}/B/*.cc
)
add_library(B SHARED ${SRC_LIBS_B}) # shared->.so static->.a object->.o
tests下的CMakeLists
... # "简单工程"中CMakeLists.txt的常规配置
include_libraries({CMAKE_CURRENT_SOURCE_DIR}/../libs/B)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test0 SRC_TEST0) # collect all cxx files in $test0
# link B.so
add_executabel(testA ${SRC_TESTA})
target_link_libraries(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libB.so)
# link B.a
link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libB.a)
add_executable(testA ${SRC_TESTA})
# linke B/*.o
add_executable(testA ${SRC_TESTA} $<TARGET_OBJECTS:B>)
4. 超多依赖模块库
目的:
A -> B.so ->C.a->D.a
B.so->C.a B.so->D.a
C.a->D.a
A->C.a A->D.a
- output
- build
- exlibs
- D
- D.h
- D.cc
- CMakeLists.txt
- root
- statics
- C
- C.h
- C.cc
- CMakeLists.txt
- libs
- B
- B.h
- B.cc
- CMakeLists.txt
- tests
- testA
- main.cc
- CMakeLists.txt
- CMakeLists.txt
项目构建时,脚本从外往里调,从里往外构。最终依赖exlibs/D/CMakeLists.txt。
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/D)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/D SRC_LIBD)
add_library(D STATIC ${SRC_LIBD}) # 最外层CMakeLists会定义libD的输出目录
statics/C/CMakeLists.txt
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../exlibs/D) # include D.header
include_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 使用静态链接库
#include_directories(${CMAKE_CURRENT_SOURCE_DIR})
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC_LIBC)
add_library(C STATIC ${SRC_LIBD}) # 最外层CMakeLists会定义libC.a的输出目录
libs/B/CMakeLists.txt
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../exlibs/D) # include D.header
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../statics/C) # include C.header
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC_LIBB)
link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libC.a ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 静态链接库,左边依赖右边
add_library(B SHARED ${SRC_LIBD})
tests/CMakeLists.txt
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../extlibs/D)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../statics/C)
# C&D都是静态链接库的形式,这里还需要再次包含一遍
link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libC.a ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 静态链接库,左边依赖右边
file(GLOB_RECURSE SRC_TESTA
${CMAKE_CURRENT_SOURCE_DIR/testA/*.h}
${CMAKE_CURRENT_SOURCE_DIR/testA/*.cc}
)
add_executable(testA ${SRC_TESTA})
target_link_libraries(testA PUBLIC ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libB.so) # 使用动态链接库B
最外层CMakeLists.txt
cmake_minium_version(VERSION 3.10)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJEC_SOURCE_DIR}/output)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJEC_SOURCE_DIR}/output)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJEC_SOURCE_DIR}/output)
set(CMAKE_CXX_FALGS "${GUN_FLAGS} -std=c++11 -fPIC -Wl,--no-as-needed -ldl -lphread") # 使用c++11标准;-fPIC 动态库中使用静态库;-Wl,--no-as-needed 链接dlsym所需系统库;-lphread使用多线程库phread
add_subdirectory(exlibs/D)
add_subdirectory(root/statics/C)
add_subdirectory(root/libs/B)
add_subdirectory(root/tests)
5.超多依赖库II
同样的依赖关系,不想把C构建成单独的静态库,而是直接使用C编译成的.o。此时只需要在构建B时将C/*.o链接。
1.修改statics/C/CMakeLists.txt最后一行
# add_library(staticC STATIC ${SRC_LIBD})
add_library(objC OBJECT ${SRC_LIBD}) # 将C直接编译成.o备用
2.修改libs/B/CMakeLists.txt的最后2行
# link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libC.a ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 静态链接库,左边依赖右边
# add_library(staticC SHARED ${SRC_LIBD})
link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 只链接静态库
add_library(B SHARED
${SRC_LIBD}
$<TARGET_OBJECTS:objC>
) # 直接把C编到libB.so里,后续使用时,只需要链接libB.so就可以用C里的符号
3.修改tests/CMakelists.txt,去除对libC.a的依赖
# # C&D都是静态链接库的形式,这里还需要再次包含一遍
# link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libC.a ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a) # 静态链接库,左边依赖右边
# 去除对libC.a的依赖
link_libraries(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/libD.a}
6.总结
除此之外,还需要指定cmake_cxx_flag编译指令以实现链接系统库等个性化需求;也可以引入xxx.cmake美化cmake的CMakelists文件。