背景:
最近团队的新项目开始基于CMake作为工程管理,结合VSCode作为IDE进行开发,一个原因当然是为了可支持跨平台。原来我们的开发环境是使用VS系列IDE进行开发,在底层框架完全改为CMake支持后,后续的项目开发也开始完全用CMake组织工程,虽然说的是使用VSCode开发,不过对于今天要总结的内容暂时不必要,所以,这次介绍使用CMake生成VS2015的工程,重点在CMakeLists.txt怎么写。
由于真正用于项目开发的CMakeLists.txt已经非常庞大和复杂,所以这里我将简化一版自己的练习工程作为总结。
需要环境:
- 演示操作系统:Windows
- CMake工具:这里我用的是cmake-3.19.8-win64-x64
- VS系列IDE:作为代码开发
- VSCode:可选,如果有CMake-GUI,完全可以替代,无非就是配置后Configuration和Generation,只是我最近发现VSCode安装CMake相关插件后,可以修改后直接在根CMakeLists.txt里保存一下,就能生成工程了,非常方便。
- 开发语言:C++
- 引入三方框架:Qt (这里只介绍Qt,其它三方库同理)
达到结果:
既然是CMake多工程的最小实现,那么,一是多个工程,或者说必要的几类工程例子:动态库、静态库、可执行程序、Qt界面库,这些如何组织起来;二是最小实现,在满足如上库的工程能够组件起来的情况下,能够将CMake精简,首先理解最基础的命令即可。
所以,例子工程将实现在main中,调用简单动态库导出的一个接口并打印内容、调用静态库中一个计算函数并打印结果,调用界面库中的一个Qt界面类,并show出来。
实现步骤:
- 目录构建
- 目录树:
Training
└─SourceCode
├─Product
│ ├─Bin
│ ├─Config
│ └─Data
└─Source
├─SampleLib
│ ├─Include
│ └─Src
├─SampleWidget
│ ├─Include
│ └─Src
├─TrainingApp
│ ├─Include
│ └─Src
└─TrainingCore
├─Include
└─Src
- 以上是我所习惯的工程项目组织路径,具体介绍如下
- Training代码工程名;
- Training下的SourceCode则包含了工程代码(Source)和产物(Product)
- 产物Product内Bin是产物生成路径,所以工程编译生成的exe、dll等,会有一个拷贝的动作,拷贝到Bin下面的Release(或者Debug、MinSizeRel、RelWithDebInfo)子目录内;
- Product内的Config是软件启动所需要的如网络、数据库等的配置文件存放目录;
- Product内的Data是软件启动所需要的必要数据初始化,比如地图预加载数据之类的;
- Source内就是各自的工程了:
- TrainingApp是main.cpp所在工程,是可执行程序例子工程,会依赖下面的三个例子工程,调用里面的接口或类;
- SampleLib是静态库例子工程;
- TrainingCore是动态库例子工程;
- SampleWidget是带Qt界面的动态库例子工程;
SourceCode下
Source下
一个工程下,以TrainingApp为例
- CMakeLists.txt的组织
接下来主要关心Source下的工程结构,首先是根CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(AlgorithmTraining VERSION 1.0.0)
# 避免find_package 设置_ROOT的警告
cmake_policy(SET CMP0074 OLD)
#--------------------------------------------------------------------
# 系统变量初始化
#--------------------------------------------------------------------
# 支持的Configuration
message("Generated with config types: ${CMAKE_CONFIGURATION_TYPES}")
#设置Product生成目录
SET(PRODUCT_EXECUTABLE_DIR ${CMAKE_SOURCE_DIR}/../Product/Bin/
${PRODUCT_EXECUTABLE_DIR})
message(STATUS "PRODUCT_EXECUTABLE_DIR: " ${PRODUCT_EXECUTABLE_DIR})
# 设置Qt环境变量路径 or $ENV{QTDIR}
SET(CMAKE_PREFIX_PATH D:\\Qt\\Qt5.10.1\\5.10.1\\msvc2015_64)
message(STATUS "CMAKE_PREFIX_PATH: " ${CMAKE_PREFIX_PATH})
#--------------------------------------------------------------------
# 添加需要包含的模块
#--------------------------------------------------------------------
# install时需要的目录变量的module
include(GNUInstallDirs)
# 打包导出配置所需要的module
include(CMakePackageConfigHelpers)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#--------------------------------------------------------------------
# 添加子目录
#--------------------------------------------------------------------
add_subdirectory(TrainingApp)
add_subdirectory(TrainingCore)
add_subdirectory(SampleLib)
add_subdirectory(SampleWidget)
#--------------------------------------------------------------------
# 添加自定义配置
#--------------------------------------------------------------------
# 生成后事件:创建<Configuration>目录
add_custom_target(TARGET ALL
COMMAND ${CMAKE_COMMAND} -E make_directory ${PRODUCT_EXECUTABLE_DIR}/$<CONFIGURATION>)
接下来依次是子CMakeLists.txt:
可执行程序TrainingApp:
# 工程名称
set(PROJECT_NAME TrainingApp)
project(${PROJECT_NAME}
VERSION ${CMAKE_PROJECT_VERSION})
#------------------------------------------------
# 依赖库/框架
#------------------------------------------------
#--------------------------------------------------------------------
# 源代码文件
#--------------------------------------------------------------------
set(_Enter
"${CMAKE_CURRENT_LIST_DIR}/Src/main.cpp"
)
source_group(入口 FILES ${_Enter})
# 生成target
add_executable(${PROJECT_NAME}
${_Enter}
)
#--------------------------------------------------------------------
# 工程依赖
#--------------------------------------------------------------------
# 路径寻址
target_include_directories(${PROJECT_NAME}
PRIVATE
./Include
)
# 依赖库
target_link_libraries(${PROJECT_NAME}
PUBLIC
TrainingCore
SampleLib
SampleWidget
)
#--------------------------------------------------------------------
# 事件
#--------------------------------------------------------------------
# 拷贝
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${PROJECT_NAME}>"
"${PRODUCT_EXECUTABLE_DIR}/$<CONFIGURATION>/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
COMMENT "Copying to product directory")
动态库TrainingCore
# 工程名称
set(PROJECT_NAME TrainingCore)
project(${PROJECT_NAME}
VERSION ${CMAKE_PROJECT_VERSION})
#------------------------------------------------
# 依赖库/框架
#------------------------------------------------
#--------------------------------------------------------------------
# 源代码文件
#--------------------------------------------------------------------
set(_Export
"${CMAKE_CURRENT_LIST_DIR}/Include/TrainingCoreExport.h"
)
source_group(导出 FILES ${_Export})
set(_Public
"${CMAKE_CURRENT_LIST_DIR}/Include/TrainingCoreApplication.h"
"${CMAKE_CURRENT_LIST_DIR}/Src/TrainingCoreApplication.cpp"
)
source_group(公用方法 FILES ${_Public})
add_library(${PROJECT_NAME} SHARED
${_Export}
${_Public}
)
#--------------------------------------------------------------------
# 工程依赖
#--------------------------------------------------------------------
# 导出宏
target_compile_definitions(${PROJECT_NAME} PRIVATE TRAININGCORE_EXPORTS)
# 路径寻址
target_include_directories(${PROJECT_NAME}
PRIVATE
./Include
)
#--------------------------------------------------------------------
# 事件
#--------------------------------------------------------------------
# 拷贝
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${PROJECT_NAME}>"
"${PRODUCT_EXECUTABLE_DIR}/$<CONFIGURATION>/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
COMMENT "Copying to product directory")
静态库SampleLib
# 工程名称
set(PROJECT_NAME SampleLib)
project(${PROJECT_NAME}
VERSION ${CMAKE_PROJECT_VERSION})
#------------------------------------------------
# 依赖库/框架
#------------------------------------------------
#--------------------------------------------------------------------
# 源代码文件
#--------------------------------------------------------------------
# 生成target
add_library(${PROJECT_NAME} STATIC)
file(GLOB INC_FILES Include/*.h)
file(GLOB INC_FILES1 Src/*.h)
set(${PROJECT_NAME}_Includes
${INC_FILES}
${INC_FILES1}
)
aux_source_directory(Src CPP_FILES)
set(${PROJECT_NAME}_SRCS
${CPP_FILES}
)
target_sources(${PROJECT_NAME}
PRIVATE
${${PROJECT_NAME}_Includes}
${${PROJECT_NAME}_SRCS}
)
#--------------------------------------------------------------------
# 工程依赖
#--------------------------------------------------------------------
# 路径寻址
target_include_directories(${PROJECT_NAME}
PRIVATE
./Include
)
#--------------------------------------------------------------------
# 事件
#--------------------------------------------------------------------
Qt界面库SampleWidget
# 工程名称
set(PROJECT_NAME SampleWidget)
project(${PROJECT_NAME}
VERSION ${CMAKE_PROJECT_VERSION})
#------------------------------------------------
# 依赖库/框架
#------------------------------------------------
# Qt
find_package(Qt5
COMPONENTS
Core
Gui
Widgets
LinguistTools
REQUIRED
)
set(CMAKE_AUTOMOC ON) # 自动处理moc文件
set(CMAKE_AUTORCC ON) # 自动处理资源文件
set(CMAKE_AUTOUIC ON) # 自动处理UI文件
#--------------------------------------------------------------------
# 源代码文件
#--------------------------------------------------------------------
set(_Export
"${CMAKE_CURRENT_LIST_DIR}/Include/SampleWidgetExport.h"
)
source_group(导出 FILES ${_Export})
set(_UI
"${CMAKE_CURRENT_LIST_DIR}/Include/SampleWidget.h"
"${CMAKE_CURRENT_LIST_DIR}/Src/SampleWidget.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Src/SampleWidget.ui"
)
source_group(界面 FILES ${_UI})
# 生成target
add_library(${PROJECT_NAME} SHARED
${_Export}
${_UI}
)
#--------------------------------------------------------------------
# 工程依赖
#--------------------------------------------------------------------
# 导出宏
target_compile_definitions(${PROJECT_NAME} PRIVATE SAMPLEWIDGET_EXPORTS)
# 路径寻址
target_include_directories(${PROJECT_NAME}
PRIVATE
./Include
)
# 链接库
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets)
#--------------------------------------------------------------------
# 事件
#--------------------------------------------------------------------
# 拷贝
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${PROJECT_NAME}>"
"${PRODUCT_EXECUTABLE_DIR}/$<CONFIGURATION>/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
COMMENT "Copying to product directory")
- 通过VSCode执行CMake,build出最终的VS2015工程
- 刚才说VSCode可选,是因为CMake的GUI完全可以满足,把根CMakeLists.txt拖进去,然后设置必要参数,Configure->Generate后,就在指定的build目录下生成了。
- 然后说下VSCode如何“爽快”地生成:
需要安装CMake需要的插件:CMake、CMake Tools - 在VSCode中点击“打开文件夹”,选到Source目录(即根CMakeLists.txt的目录)
这个时候VSCode把目录树都加载好了 - 需要选择指定生成的工程
- VSCode会弹出这个,选择VS14.0(或者按需选择)即可
- 然后每次修改了任意CMakeLists.txt后,只需在根的CMakeLists.txt内Ctrl+s保存下,它就自动给生成build目录,生成工程了:
- 然后查看目录就会发现Source下有“build”文件夹,里面就是生成的VS工程了:
这里是用VS2015作为例子,现在就可以打开sln,进入熟悉的VS-IDE开发了
- 配置到直接F5启动调试
- 进入熟悉的界面,先编译一下,看生成的产物有没有拷贝到指定目录。
在Product/Bin下面确实生成了Debug目录,里面有2个库和1个exe
- 这时候双击exe会提示缺Qt库,这很正常,使用Qt的Bin内的windeployqt.exe执行一下,就会获得所需要的Qt基础依赖库
- 如果想F5直接启动,那么就把TrainingApp设置为启动项目,然后配置下属性内的调试
然后“应用”,就可以愉快地F5启动了。
分享
最后分享这个练习工程的CMakeLists.txt和必要的演示源码,放在网盘链接内,目录组织形式根据例子介绍的放入了,还包括一个CMake 3.19的Window安装包。
大家可以基于这个安装包,结合上面的步骤,自己跑一遍,就基本熟悉CMake如何组建工程的了,然后就可以用这样的方法写自己的工程了,进而学习更复杂的CMake指令,以及更多的工程构建,包括跨平台如何支持的配置。
链接:https://pan.baidu.com/s/1Tta_s__yCCfGmeefMALzZA 提取码:lcc6