Windows 專案的 CMakeLists 編寫

sun發表於2023-12-05

前言:

專案一直是以 .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 編譯的時候,就要加上條件語句用於區分

接著定義專案名

project(my-project) 

這個最直接的作用就是 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 中的指令碼編寫我還是懵懂狀態,後續得繼續學習才行啊

    

 

相關文章