一句话结论:本文归纳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文件。
附录. 使用CMake构建C/C++工程规范
unix下构建C++工程默认选用CMake工具。
总体原则
- 从最开始到测试包,完全自动化,拒绝任何形式的手动干预;
- 显式指定依赖工具或第三方的版本号,确保构建编译过程可复现;
- 构建脚本首先人能很容易看懂,然后能正常工作;
构建工程
构建日志, [日期][error] cmd:错误描述
构建目录结构标准化
构建目录按用途分为SourceTree、BuildTree、InstallTree,3个目录互不重叠没有交集
- SourceTree : 保存源码和CMakeLists.txt
- BuildTree : 保存CMake编译的中间过程产物,固定为“build”
- InstallTree : 保存最终测试包发布件,固定为“output”
整个构建过程,禁止以任何形式修改SourceTree。如需修改,源码拷贝到BuildTree中进行。
构建编译的所有中间件必须放置在BuildTree中 【libasrlog.a,libjsoncpp.a也需要放置在BuildTree中,可以放在${CMAKE_BINARY_DIR}/lib下】
保证构建入口单一,固定为prj/cmake_xxx.sh,示例如下
#!/bin/bash
# cmake_linux.sh
if [ -d "../output" ];then
rm -rf ../output
fi
mkdir ../output
if [ -d "cmake_linux_build" ];then
rm -rf cmake_linux_build
fi
mkdir cmake_linux_build
cd cmake_linux_build
cmake ../../
make -f Makefile -j8
顶层CMakeLists.txt必须包含本工程下所有源码和头文件。如果没有能包含依赖的第三方源码源码头文件,调用ExternalProject_Add()为第三方源码建立扩展工程
add_subdirectory只添加当前子目录,禁止添加当前父目录、兄弟目录、孙目录。
add_subdirectory只在CMakeListst.txt中调用,.cmake中禁止调用
必须包含toolchain文件,且只存放与编译器工具链相关的配置
一个toolchain文件只对应一种编译器
toolchain文件必须通过-DCMAKE_TOOLCHAIN_FILE引用
CMakeLists语法规则
第一行必须指定cmake最低版本号,第二行必须调用project
cmake_minimun_version(VERSION 3.10)
project(helloword C CXX)
cmake的命令小写,属性大写
明确写出target依赖的源文件名称,禁止使用通配符
通过target依赖的方式显式定义lib库的依赖关系
add_libaray(foo SHARED foo.c)
add_libaray(bar SHARED bar.c)
target_link_libraries(bar PRIVATE foo) # bar依赖foo