前言:
项目一直是以 .sln 解决方案打开和处理的,上传到 github 也是需要将 sln 文件包括到项目里,不太优雅(虽然方便),毕竟现在开源项目基本都是使用 CMake 做跨平台编译
因为项目是以 Windows 编译为主,就只写了 CMakeLists 中的 Windows 的部分,后续如果要跨平台的话,可以在此基础上拓展。
写这篇文章的主要目的是积累和分享学习经验,在寻找相关 vs 参数设置上花费了不少时间,写下来后可以让有类似需求的同学少走些弯路
正篇:
我也是最近才接触到 CMakeLists 的写法,从一开始的静态库和动态库的简单编写到现在整个项目都使用 CMake 编写,也算是一个循序渐进的过程
静态库和动态库的编写参考我过去的文章:
好了,开始吧
我们需要先设置 CMake 的最低版本,什么是最低版本,也就是说你添加 CMake 的函数时,有些函数是在特定的 CMake 版本后才被添加进来的,如果在该版本之前添加这些函数的话,CMake 会提示说找不到这些函数并报错
比如说,source_group 函数用于项目中组织源文件,将它们分组显示在 IDE 中,使项目结构更清晰,它是 CMake 2.8.11 中新添加进来的,所以就需要设置 CMake 的最低版本为 2.8.11
- cmake_minimum_required(VERSION 2.8.11)
一般情况下,我们会设置到 3.x 以后,这是通用的做法,因为有些函数会随着版本更新而添加新的参数或者功能
设置好最低 CMake 版本后,我们可能会需要设置 SDK 的最低版本,这里指的是 Windows 的 SDK,也是类似 CMake 的最低版本,有些你用的 win32 函数可能是新 SDK 添加进来的
- # Check for Win SDK version 10.0.19041 or above
- if(MSVC AND MSVC_VERSION LESS 1920)
- message(STATUS "Windows API version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
- string(REPLACE "." ";" WINAPI_VER "${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
-
- list(GET WINAPI_VER 0 WINAPI_VER_MAJOR)
- list(GET WINAPI_VER 1 WINAPI_VER_MINOR)
- list(GET WINAPI_VER 2 WINAPI_VER_BUILD)
-
- set(WINAPI_COMPATIBLE FALSE)
- if(WINAPI_VER_MAJOR EQUAL 10)
- if(WINAPI_VER_MINOR EQUAL 0)
- if(WINAPI_VER_BUILD GREATER_EQUAL 19041)
- set(WINAPI_COMPATIBLE TRUE)
- endif()
- else()
- set(WINAPI_COMPATIBLE TRUE)
- endif()
- elseif(WINAPI_VER_MAJOR GREATER 10)
- set(WINAPI_COMPATIBLE TRUE)
- endif()
-
- if(NOT WINAPI_COMPATIBLE)
- message(FATAL_ERROR "project requires Windows 10 SDK version 10.0.19041.0 and above to compile.\nPlease download the most recent Windows 10 SDK in order to compile (or update to Visual Studio 2019).")
- endif()
- endif()
设置好 Windows SDK 最低版本后,还需要添加一些库的路径,比如我们可能会使用到 Qt 库,那么我们也可以继续添加 Qt 的路径
- set(CMAKE_PREFIX_PATH ${QTDIR})
-
- if(QTDIR OR DEFINED ENV{QTDIR} OR DEFINED ENV{QTDIR32})
- # Qt path set by user or env var
- message(STATUS "Qt path is ${QTDIR}")
- else()
- set(QTDIR "D:/Qt/5.15.x/msvc2019_x86_static")
- message(WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. D:/Qt/5.15.x/msvc2019_x86_static)")
- endif()
CMAKE_PREFIX_PATH
是 CMake 中一个用于指定查找第三方库、头文件、CMake 模块等路径的变量。它主要用于帮助 CMake 在特定路径下查找依赖项。
也就是说我们在 Windows 的环境变量中添加了 Qt 的路径,那么使用 CMAKE_PREFIX_PATH 可以直接找到 Qt 的路径并赋值给 QTDIR
如何找不到 Qt 的路径,那么我们就手动设置 Qt 的路径
说明一下,msvc2019_x86_static 是你编译 Qt 静态库的文件夹(用于商业要付费,建议链接到动态库),如何编译 Qt 的静态库,可以参考我之前的一篇文章,
接下来要设置 VS 的各个配置

- set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)

- set(CMAKE_GENERATOR_PLATFORM Win32)
- set(CMAKE_GENERATOR_TOOLSET "host=x86" CACHE STRING "Platform Toolset" FORCE)
- set(CMAKE_MFC_FLAG 1)

- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd /Zi")
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT /Zi")
Debug 和 Release 下生成 pdb 文件
- set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /DEBUG /OPT:REF /OPT:ICF")
- set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
VS 配置这块的 CMake 的函数使用合起来如下
- message(STATUS "Set visual studio build params")
- set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
- set(CMAKE_GENERATOR_PLATFORM Win32)
- set(CMAKE_GENERATOR_TOOLSET "host=x86" CACHE STRING "Platform Toolset" FORCE)
- set(CMAKE_SUPPRESS_REGENERATION true)
- set(CMAKE_MFC_FLAG 1)
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd /Zi")
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT /Zi")
- set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /DEBUG /OPT:REF /OPT:ICF")
- set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
- add_definitions(-D_UNICODE)
其中还有三个函数没说明,
1. message 相当于 printf 函数,用于输出字符串,这边可以在 cmake 过程中观察执行到哪一步,方便定位错误
2. CMAKE_SUPPRESS_REGENERATION
是 CMake 中的一个变量,用于指定在生成项目文件(如 Visual Studio 的 .sln 和 .vcproj 文件)时是否抑制重新生成的行为。这个变量主要用于生成项目文件时的一些配置。
设置这个变量为 TRUE
时,可以告诉 CMake 在生成项目文件时避免强制重新生成已有的项目文件。这样做可以节省一些时间,特别是在一些场景下,比如在多次配置和生成过程中,确保不会因为每次重新生成项目文件而造成不必要的延迟。
3. add_definitions(-D_UNICODE)
是 CMake 中用于向编译器添加预定义宏的命令。在这里,-D
选项表示定义一个宏,_UNICODE
是一个预定义的宏,用于指示编译器使用 Unicode 字符集。
要注意的是,CMAKE_GENERATOR_PLATFORM 和 CMAKE_GENERATOR_TOOLSET 必须要在 Project 之前使用,否则不生效,使用 clang 编译的时候,就要加上条件语句用于区分
接着定义项目名
这个最直接的作用就是 cmake 生成 my-project.sln 文件
设置 C++ 标准库版本
- set(CMAKE_CXX_STANDARD 14)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- set(CMAKE_CXX_EXTENSIONS OFF)
-
set(CMAKE_CXX_STANDARD 14)
指定 C++ 标准为 C++14。这告诉 CMake 使用 C++14 标准来编译项目中的 C++ 代码。
-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
表示要求编译器严格遵循所指定的 C++ 标准。如果编译器不支持所设定的 C++ 标准,编译将会失败。这有助于确保项目代码只使用所选定的标准语言特性。
-
set(CMAKE_CXX_EXTENSIONS OFF)
告诉 CMake 不要启用编译器的扩展特性。这可以确保编译器不会启用除 C++ 标准所定义之外的额外扩展功能。
之前配置完 Qt 的路径后,这时就要设置 moc 了,moc 其实就是 Qt 的反射机制,用于信号槽的连接
- set(CMAKE_AUTOMOC ON)
- set(CMAKE_AUTORCC ON)
设置版本号,版本号也就是 VS 中 rc 文件里的各个值

你需要使用 configure_file 函数将 .in 文件配置成 rc 文件
configure_file
是 CMake 中的一个命令,用于在构建过程中通过读取一个文件的内容,替换其中的变量,然后生成一个新的文件。
它的主要作用是处理项目中的模板文件,将其中的变量替换为相应的值,生成一个目标文件。这个命令通常用于生成配置文件,包含了项目编译时所需的特定信息,例如版本号、路径等。
其中 MP_VERSION_MAJOR 是变量,也可以理解为宏
- set(MP_VERSION 1.0.0)
-
- string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" MP_VERSION_MATCH "${MP_VERSION}")
-
- set(MP_VERSION_MAJOR ${CMAKE_MATCH_1})
- set(MP_VERSION_MINOR ${CMAKE_MATCH_2})
- set(MP_VERSION_REVISION ${CMAKE_MATCH_3})
- # MP_BUILD_NUMBER 我们一般不设置
- set(MP_BUILD_NUMBER "0")
- set(MP_FILE_DESCRIPTION "XXX")
- set(MP_INTERNAL_NAME "my-project.dll")
- set(MP_LEGAL_COPYRIGHT "Copyright (C) xxx xxxx, Co., Ltd.")
- set(MP_ORIGINAL_FILENAME "my-project.dll")
- set(MP_PRODUCT_NAME "XXXXXXX")
- set(MP_PRODUCT_VERSION "${MP_VERSION_MAJOR}.${MP_VERSION_MINOR}.${MP_VERSION_REVISION}.${MP_BUILD_NUMBER}")
- set(MP_COMPANY_NAME "XXXXXXX有限公司")
比如说下面是我的 in 文件
- // Microsoft Visual C++ generated resource script.
- //
- #include "resource.h"
-
- #define APSTUDIO_READONLY_SYMBOLS
- /////////////////////////////////////////////////////////////////////////////
- //
- // Generated from the TEXTINCLUDE 2 resource.
- //
- #include "winres.h"
-
- /////////////////////////////////////////////////////////////////////////////
- #undef APSTUDIO_READONLY_SYMBOLS
-
- /////////////////////////////////////////////////////////////////////////////
- // Chinese (Simplified, PRC) resources
-
- #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
- LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
-
- #ifdef APSTUDIO_INVOKED
- /////////////////////////////////////////////////////////////////////////////
- //
- // TEXTINCLUDE
- //
-
- 1 TEXTINCLUDE
- BEGIN
- "resource.h\0"
- END
-
- 2 TEXTINCLUDE
- BEGIN
- "#include ""winres.h""\r\n"
- "\0"
- END
-
- 3 TEXTINCLUDE
- BEGIN
- "\r\n"
- "\0"
- END
-
- #endif // APSTUDIO_INVOKED
-
-
- /////////////////////////////////////////////////////////////////////////////
- //
- // Icon
- //
-
- // Icon with lowest ID value placed first to ensure application icon
- // remains consistent on all systems.
- IDI_MAIN ICON "..\\img\\my_project.ico"
-
-
- /////////////////////////////////////////////////////////////////////////////
- //
- // Version
- //
-
- VS_VERSION_INFO VERSIONINFO
- FILEVERSION ${MP_VERSION_MAJOR},${MP_VERSION_MINOR},${MP_VERSION_REVISION},${MP_BUILD_NUMBER}
- PRODUCTVERSION ${MP_VERSION_MAJOR},${MP_VERSION_MINOR},${MP_VERSION_REVISION},${MP_BUILD_NUMBER}
- FILEFLAGSMASK 0x3fL
- #ifdef _DEBUG
- FILEFLAGS 0x1L
- #else
- FILEFLAGS 0x0L
- #endif
- FILEOS 0x40004L
- FILETYPE 0x1L
- FILESUBTYPE 0x0L
- BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "080404b0"
- BEGIN
- VALUE "CompanyName", "${MP_COMPANY_NAME}"
- VALUE "FileDescription", "${MP_FILE_DESCRIPTION}"
- VALUE "FileVersion", "${MP_PRODUCT_VERSION}"
- VALUE "InternalName", "${MP_INTERNAL_NAME}"
- VALUE "LegalCopyright", "${MP_LEGAL_COPYRIGHT}"
- VALUE "OriginalFilename", "${MP_ORIGINAL_FILENAME}"
- VALUE "ProductName", "${MP_PRODUCT_NAME}"
- VALUE "ProductVersion", "${MP_PRODUCT_VERSION}"
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x804, 1200
- END
- END
-
- #endif // Chinese (Simplified, PRC) resources
- /////////////////////////////////////////////////////////////////////////////
-
-
-
- #ifndef APSTUDIO_INVOKED
- /////////////////////////////////////////////////////////////////////////////
- //
- // Generated from the TEXTINCLUDE 3 resource.
- //
-
-
- /////////////////////////////////////////////////////////////////////////////
- #endif // not APSTUDIO_INVOKED
在此基础上,调用 configure_file
- configure_file(my_project.rc.in ${CMAKE_CURRENT_SOURCE_DIR}/build/my_project.rc)
就可以转换为需要的 my_project.rc 文件了,这样做的好处就是在 CMake 中设置 rc 中的变量,特别是每次发版都要修改的版本号
同理,对于要修改版本号这种需求,都可以使用 in 文件转换来实现
这步配置完后,我们开始进入库的配置和代码文件的配置
使用 find_package 将 Qt 库加入到项目工程中,仅添加用到的 Qt 库,其他官方库亦是如此
- find_package(Qt5Core)
- find_package(Qt5Gui)
- find_package(Qt5Widgets)
- find_package(Qt5WinExtras)
设置源码文件路径,一般我们都会将 .cpp .h 都放到 src 文件夹下,以便查找
这里我们的源码都是在 src 文件夹下,之所以定义 SRC_PATH,是下面添加每个 cpp 文件都要添加路径,这样节省了时间以及美观
- set(SRC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src)
设置完 SRC_PATH 之后就可以设置各个源码子目录了,比如我们的代码层级是这样设置的

我们希望 CMake 生成的 .sln 工程在被打开也是呈现这样的目录,那么我们就需要使用 source_group 函数
先定义好 cpp 路径,我们需要手动将每个头文件和源文件都添加进来,有些人是使用比较快速的方法,就是只要后缀是 .h 和 .cpp 的文件都一键添加进来,这样做,有好有弊
我个人觉得不太好,因为实际过程中,有些 cpp 文件是过旧的,你不想删除但也不想添加到工程中,这时你如果一键添加,就不是预期的结果了
- set(AUDIO_SOURCES
- ${SRC_PATH}/audio/1.cpp
- ${SRC_PATH}/audio/2.cpp
- ${SRC_PATH}/audio/3.cpp
- )
-
- set(AUDIO_HEADERS
- ${SRC_PATH}/audio/1.h
- ${SRC_PATH}/audio/2.h
- ${SRC_PATH}/audio/3.h
- )
- source_group("Source Files/audio" FILES ${AUDIO_SOURCES})
- source_group("Header Files/audio" FILES ${AUDIO_HEADERS})
- source_group("Source Files/base" FILES ${BASE_SOURCES})
- source_group("Header Files/base" FILES ${BASE_HEADERS})
- source_group("Source Files/live" FILES ${LIVE_SOURCES})
- source_group("Header Files/live" FILES ${LIVE_HEADERS})
- source_group("Source Files/net" FILES ${NET_SOURCES})
- source_group("Header Files/net" FILES ${NET_HEADERS})
- source_group("Source Files/ui" FILES ${UI_SOURCES})
- source_group("Header Files/ui" FILES ${UI_HEADERS})
这样我们的源文件和头文件就设置好了
现在配置三方库的宏路径,一般情况下我们习惯将用到的三方库都放到一个文件夹,比如 third_party
- set(ADir ${CMAKE_CURRENT_SOURCE_DIR}/third_party/a)
- set(BDir ${CMAKE_CURRENT_SOURCE_DIR}/third_party/b)
- set(CDir ${CMAKE_CURRENT_SOURCE_DIR}/third_party/c)
接着设置预处理宏和预编译静态库
- set(MY_PROJECT_PLATFORM_DEPS
- aaa.lib
- bbb.lib
- ccc.lib
- )
-
- set(COMPILE_DEFINITIONS
- UNICODE
- _UNICODE
- WIN32
- _NO_GDIPLUS_
- _CRT_RAND_S
- QT_CORE_LIB
- QT_GUI_LIB
- QT_WIDGETS_LIB
- QT_WINEXTRAS_LIB
- MP_VERSION="${MP_VERSION}"
- )
注意预处理宏有个 MP_VERSION="${MP_VERSION}",这个实际上是将版本号由 CMakeLists 透传到项目中,这样我们就可以在项目中直接使用 MP_VERSION
- const char kAppVersion[] = MP_VERSION;
项目中我们还有两个自编译的三方库,需要使用 add_subdirectory 添加进来,这样做的目的有个好处就是,每次 CMake -B build 时,都会一同将子项目的 CMakeLists.txt 一同编译
使用 msbuild 编译 .sln 工程除了编译主项目也会将添加进来的子项目一同编译出来
注意:每个子项目也需要一个 CMakeLists.txt
我们的自编译的三方库都是编译成静态库
- add_subdirectory(${ADir}/A/)
- add_subdirectory(${BDir}/B/)
如何编译成静态库,可以参考我的另一篇文章:
子项目的 CMakeLists.txt 位置就在 ${ADir}/A/ 下面,像这样,${ADir}/A/CMakeLists.txt,${BDir}/B/CMakeLists.txt
设置完这些,使用 add_executable 添加可执行文件
- add_executable(my_project
- ${AUDIO_SOURCES} ${AUDIO_HEADERS}
- ${AUDIO_CAPTURES_SOURCES} ${AUDIO_CAPTURES_HEADERS}
- ${BASE_SOURCES} ${BASE_HEADERS}
- ${LIVE_SOURCES} ${LIVE_HEADERS}
- ${NET_SOURCES} ${NET_HEADERS}
- ${RESOURCE_FILES}
- )
下一步我们开始设置工程的属性
工程中如果有自定义宏文件,如下,

对于这种文件,我们需要找到文件的位置,并使用 set_target_properties 添加到工程中
- set_target_properties(my_project PROPERTIES VS_USER_PROPS "${CMAKE_SOURCE_DIR}/projects/third_party.props")
其他的一些工程的属性设置也是类似
- # enable vcpkg
- set_target_properties(missevan_fm_kernel PROPERTIES VS_GLOBAL_VcpkgEnabled true)
- # Release 下生成 pdb 文件
- set_target_properties(missevan_fm_kernel PROPERTIES LINK_FLAGS "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF")
需要注意的是,VS_GLOBAL_VcpkgEnabled 只能设置 Use Vcpkg 选项

如果你要设置下面的 Triplet 和 Vcpkg Configuration 选项,目前没有办法做到,我是通过 powershell 脚本强行修改 vcxproj 文件做到修改 Triplet 的选项
powershell 脚本的编写,我后续会在另一篇文章中介绍
如果你还需要设置 .def 文件,可以使用 target_link_options 做到,def 文件你可以将它视为 dllexport 的等同效果,一般用来暴露工程的 main 函数
- if (CMAKE_BUILD_TYPE STREQUAL "Release")
- target_link_options(my_project PRIVATE /DEF:${CMAKE_CURRENT_SOURCE_DIR}/projects/my_project.def)
- endif ()

接下来是添加头文件路径,使用 include_directories 或者 target_include_directories,target_include_directories 是比较现代的做法,官方是推荐使用后者的
不过我是用的前者,理由是使用 target_include_directories 时,总会给我添加一些稀奇古怪的路径
- include_directories(
- ${SRC_PATH}/
- ${SRC_PATH}/my_project/
- ${FaacDir}/include/
- ${ADir}/include/
- ${BDir}/include/
- ${CDir}/include/
- ${QTDIR}/include/
- ${QTDIR}/include/QtCore/
- ${QTDIR}/include/QtGui/
- ${QTDIR}/include/QtANGLE/
- ${QTDIR}/include/QtWidgets/
- ${QTDIR}/include/QtWinExtras/
- )
添加库路径,使用 target_link_directories
- target_link_directories(missevan_fm_kernel PRIVATE
- ${ADir}/lib/
- ${BDir}/lib/
- ${CDir}/lib/
- ${CMAKE_CURRENT_SOURCE_DIR}/build/DDir/$<CONFIG>/
- ${QTDIR}/lib/
- ${QTDIR}/plugins/imageformats/
- ${QTDIR}/plugins/platforms/
- ${QTDIR}/plugins/audio/
- ${QTDIR}/plugins/styles/
- )
${CMAKE_CURRENT_SOURCE_DIR}/build/DDir/$<CONFIG>/ 这行说明一下
当我们使用 add_subdirectory 添加三分库时,因为分为 Debug 和 Release 调试模式,故指定的库路径也有所不同,比如 Debug 的库在 Debug 文件夹下,Release 的库在 Release 文件夹下
$<CONFIG> 会在两种模式下自动赋值为对应的调试模式,比如现在用的是 Debug 模式,那 $<CONFIG> 也会变成 Debug,不过对于 DebugDll 和 ReleaseDll,$<CONFIG> 无法转换。
还有,target_link_directories 在 Windows 添加的路径很是奇葩

它会在每行路径下新添加一个 xxx/$(Configuration) 路径,搜了一下类似的案例,发现很多人都遇到过,是个顽疾
官方虽然开了 case 尝试修复,目前为止,仍然处于“怠工”阶段
接着添加三方依赖库,也就是 Link/Input->Additional Dependencies,使用 target_link_libraries
- target_link_libraries(my_project
- PRIVATE
- A
- B
- $<$<CONFIG:Debug>:
- libcmtd.lib
- Qt5Cored.lib
- Qt5Guid.lib
- Qt5Widgetsd.lib
- Qt5WinExtrasd.lib
- >
- $<$<CONFIG:Release>:
- Ole32.lib
- Shell32.lib
- Shlwapi.lib
- User32.lib
- Gdi32.lib
- Qt5Core.lib
- Qt5Gui.lib
- Qt5Widgets.lib
- Qt5WinExtras.lib
- >
- )
其中 A 和 B 是使用 add_subdirectory 添加的子目录,这样做的好处是它会自动添加子库的依赖

接着添加预编译宏,使用 target_compile_definitions
- target_compile_definitions(my_project
- PRIVATE
- $<$<CONFIG:Debug>:
- QT_QML_DEBUG
- _DEBUG
- _CONSOLE
- >
- $<$<CONFIG:Release>:
- QT_NO_DEBUG
- NDEBUG
- >
- )
接着设置链接器子系统,也就是 Windows 和 Console,一般 Debug 下使用 Console,因为需要弹出控制台查看调试信息,Release 下则是 Windows
注意 Console 对应的是 main 入口,Windows 对应的是 wWinmain 入口

使用 set_target_properties 即可
- set_target_properties(my_project PROPERTIES WIN32_EXECUTABLE $<$<CONFIG:Release>:TRUE>)
$<$<CONFIG:Release>:TRUE> 表示只有 Release 下才为 TRUE,其余默认为 FALSE,FALSE 就是默认使用 Console
如果你需要在编译完后将 exe 依赖的三分库的 dll 挪到特定文件夹下,可以使用 add_custom_command 实现
在 vs 中,我们可以使用 Configuration Properties->Build Events->Post-Build Event,在 Command Line 输入 for 循环复制的伪代码

但是 CMake 中我们则需要上面提到的方法
- set(DLL_FILES
- ${ADir}/a.dll
- ${BDir}/b.dll
- ${CDir}/c.dll
- )
-
- foreach(DLL ${DLL_FILES})
- if(CMAKE_BUILD_TYPE STREQUAL "Debug")
- add_custom_command(TARGET my_project POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy ${DLL} ${CMAKE_CURRENT_SOURCE_DIR}/build/bin/$<CONFIG>/
- )
- elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
- add_custom_command(TARGET my_project POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy ${DLL} ${CMAKE_CURRENT_SOURCE_DIR}/build/bin/MYPROJECT/${MF_VERSION}/
- )
- endif()
- endforeach()
通过这种方法,在程序编译后会自动将 a.dll b.dll c.dll 复制到 ${CMAKE_CURRENT_SOURCE_DIR}/build/bin/$<CONFIG>/ 文件夹下
如果你需要覆盖某个文件内的内容,比如说版本号,可以借助 file 函数
- if (CMAKE_BUILD_TYPE STREQUAL "Release")
- set(DATA_VERSION_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/bin/MYPROJECT/data/VERSION)
- file(WRITE ${DATA_VERSION_PATH} ${MF_VERSION})
- message(STATUS "Set build version success.")
- endif()
其中 ${MF_VERSION} 是版本号
更新:vcpkg 可以不使用 powershell 脚本添加 vcpkg 选项,而是使用 TRIPLET 实现
- cmake .. -DVCPKG_TARGET_TRIPLET=x86-windows-static
结尾:如果有跨平台需求的,可以参考开源项目的 CMakeKLists,比如说 OBS,它支持跨平台的构建,其中包含了很多脚本的编写
CMake 确实复杂,短时间内是无法理解透,起码目前为止很多 CMake 函数以及 CMake 中的脚本编写我还是懵懂状态,后续得继续学习才行啊