CMake構(gòu)建學(xué)習(xí)筆記24-使用通用腳本構(gòu)建PROJ和GEOS
1. 通用腳本
在之前的文章《CMake構(gòu)建學(xué)習(xí)筆記21-通用的CMake構(gòu)建腳本》中我們創(chuàng)建了一個(gè)通用的cmake構(gòu)建腳本cmake-build.ps1:
param(
[string]$SourceLocalPath,
[string]$BuildDir,
[string]$Generator,
[string]$InstallDir,
[string]$SymbolDir,
[string[]]$PdbFiles,
[hashtable]$CMakeCacheVariables,
[bool]$MultiConfig = $false # 控制是否使用多配置類型
)
# 清除舊的構(gòu)建目錄
if (Test-Path $BuildDir) {
Remove-Item -Path $BuildDir -Recurse -Force
}
New-Item -ItemType Directory -Path $BuildDir
# 構(gòu)建CMake命令行參數(shù)
$CMakeArgs = @(
"-B", "`"$BuildDir`"",
"-G", "`"$Generator`"",
"-A", "x64"
)
if ($MultiConfig) {
$CMakeArgs += "-DCMAKE_CONFIGURATION_TYPES=RelWithDebInfo"
}
else {
$CMakeArgs += "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
}
$CMakeArgs += (
"-DCMAKE_PREFIX_PATH=`"$InstallDir`"",
"-DCMAKE_INSTALL_PREFIX=`"$InstallDir`""
)
# 添加額外的CMake緩存變量
foreach ($key in $CMakeCacheVariables.Keys) {
$CMakeArgs += "-D$key=$($CMakeCacheVariables[$key])"
}
# 配置CMake
cmake $SourceLocalPath $CMakeArgs
# 構(gòu)建階段,指定構(gòu)建類型
cmake --build $BuildDir --config RelWithDebInfo --parallel
# 安裝階段,指定構(gòu)建類型和安裝目標(biāo)
cmake --build $BuildDir --config RelWithDebInfo --target install
# 復(fù)制符號(hào)庫(kù)
foreach ($file in $PdbFiles) {
Write-Output $file
if (Test-Path $file) {
Copy-Item -Path $file -Destination $SymbolDir
}
else {
Write-Output "Warning: PDB file not found: $file"
}
}
# 清理構(gòu)建目錄
#Remove-Item -Path $BuildDir -Recurse -Force
在《CMake構(gòu)建學(xué)習(xí)筆記22-libxml2庫(kù)的構(gòu)建》這篇文章中使用這個(gè)腳本構(gòu)建了libxml2庫(kù):
param(
[string]$Name = "libxml2-v2.14.4",
[string]$SourceDir = "../Source",
[string]$Generator,
[string]$InstallDir,
[string]$SymbolDir
)
# 根據(jù) $Name 動(dòng)態(tài)構(gòu)建路徑
$zipFilePath = Join-Path -Path $SourceDir -ChildPath "$Name.zip"
$SourcePath = Join-Path -Path $SourceDir -ChildPath $Name
$BuildDir = Join-Path -Path "." -ChildPath $Name
# 解壓ZIP文件到指定目錄
if (!(Test-Path $SourcePath)) {
Expand-Archive -LiteralPath $zipFilePath -DestinationPath $SourceDir -Force
}
# 檢查目標(biāo)文件是否存在,以判斷是否安裝
$DstFilePath = "$InstallDir/bin/libxml2.dll"
if (Test-Path $DstFilePath) {
Write-Output "The current library has been installed."
exit 1
}
# 復(fù)制符號(hào)庫(kù)
$PdbFiles = @(
"$BuildDir/RelWithDebInfo/libxml2.pdb"
)
# 額外構(gòu)建參數(shù)
$CMakeCacheVariables = @{
BUILD_SHARED_LIBS = "ON"
LIBXML2_WITH_ZLIB = "ON"
LIBXML2_WITH_ICONV = "ON"
LIBXML2_WITH_HTTP = "ON"
}
# 調(diào)用通用構(gòu)建腳本
. ./cmake-build.ps1 -SourceLocalPath $SourcePath `
-BuildDir $BuildDir `
-Generator $Generator `
-InstallDir $InstallDir `
-SymbolDir $SymbolDir `
-PdbFiles $PdbFiles `
-CMakeCacheVariables $CMakeCacheVariables `
-MultiConfig $true
因?yàn)樘峁┝薱make構(gòu)建方式的程序的構(gòu)建行為是比較統(tǒng)一的,這個(gè)構(gòu)建libxml2庫(kù)的腳本可以進(jìn)一步封裝,形成一個(gè)通用的調(diào)用cmake-build.ps1構(gòu)建程序的腳本。cmake-build.ps1只是包含了調(diào)用cmake執(zhí)行構(gòu)建的內(nèi)容,但是其實(shí)整個(gè)構(gòu)建過程需要做的事情很多,比如安裝符號(hào)庫(kù)、安裝程序的依賴庫(kù)等等,這些過程指的再封裝一層構(gòu)建的腳本。筆者封裝的腳本build-common.ps1如下:
# build-library.ps1
param(
[Parameter(Mandatory=$true)]
[string]$Name,
[Parameter(Mandatory=$true)]
[string]$SourceDir,
[Parameter(Mandatory=$true)]
[string]$InstallDir,
[string]$SymbolDir,
[string]$Generator,
[string]$MSBuild,
[hashtable]$CMakeCacheVariables = @{},
[string[]]$PdbFiles = @(),
[string]$TargetDll, # 用于判斷是否已安裝的 DLL 路徑
[bool]$MultiConfig = $false, # 控制是否使用多配置類型
[bool]$Force = $false, # 是否強(qiáng)制重新構(gòu)建
[bool]$Cleanup = $true, # 是否在構(gòu)建完成后刪除源碼和構(gòu)建目錄
[string[]]$Librarys = @() # 可選的依賴庫(kù)數(shù)組,例如:-Librarys "zlib", "libjpeg"
)
# 動(dòng)態(tài)路徑構(gòu)建
$zipFilePath = Join-Path -Path $SourceDir -ChildPath "$Name.zip"
$SourcePath = Join-Path -Path $SourceDir -ChildPath $Name
$BuildDir = Join-Path -Path "." -ChildPath $Name
# 檢查是否已經(jīng)安裝(通過目標(biāo) DLL)
if (-not $Force -and $TargetDll -and (Test-Path $TargetDll)) {
Write-Output "Library already installed: $TargetDll"
exit 0
}
# 創(chuàng)建所有依賴庫(kù)的容器
if ($Librarys.Count -gt 0) {
. "./BuildRequired.ps1"
BuildRequired -Librarys $Librarys
}
# 確保源碼目錄存在:解壓 ZIP
if (!(Test-Path $SourcePath)) {
if (!(Test-Path $zipFilePath)) {
Write-Error "Archive not found: $zipFilePath"
exit 1
}
Write-Output "Extracting $zipFilePath to $SourceDir..."
Expand-Archive -LiteralPath $zipFilePath -DestinationPath $SourceDir -Force
}
# 如果是強(qiáng)制構(gòu)建,且構(gòu)建目錄已存在,先刪除舊的構(gòu)建目錄(確保干凈構(gòu)建)
if ($Force -and (Test-Path $BuildDir)) {
Write-Output "Force mode enabled. Removing previous build directory: $BuildDir"
Remove-Item $BuildDir -Recurse -Force -ErrorAction SilentlyContinue
}
# 遍歷并添加前綴
$PdbFiles = $PdbFiles | ForEach-Object {
Join-Path -Path $BuildDir -ChildPath $_
}
# 調(diào)用通用 CMake 構(gòu)建腳本
Write-Output "Starting build for $Name..."
. ./cmake-build.ps1 -SourceLocalPath $SourcePath `
-BuildDir $BuildDir `
-Generator $Generator `
-InstallDir $InstallDir `
-SymbolDir $SymbolDir `
-PdbFiles $PdbFiles `
-CMakeCacheVariables $CMakeCacheVariables `
-MultiConfig $MultiConfig
if ($LASTEXITCODE -ne 0) {
Write-Error "Build failed for $Name."
exit $LASTEXITCODE
}
# 構(gòu)建成功后,根據(jù) Cleanup 開關(guān)決定是否刪除
if ($Cleanup) {
Write-Output "Build succeeded. Cleaning up temporary directories..."
if (Test-Path $SourcePath) {
Remove-Item $SourcePath -Recurse -Force -ErrorAction SilentlyContinue
Write-Output "Removed source directory: $SourcePath"
}
if (Test-Path $BuildDir) {
Remove-Item $BuildDir -Recurse -Force -ErrorAction SilentlyContinue
Write-Output "Removed build directory: $BuildDir"
}
}
Write-Output "Build completed for $Name."
這段腳本干了很多零碎的事情,但是對(duì)于一個(gè)完整的構(gòu)建系統(tǒng)是必須的,比如判斷是否需要強(qiáng)制構(gòu)建、是否需要清理中間文件、安裝程序的依賴庫(kù)、安裝符號(hào)庫(kù)等等。另外,腳本的使用源代碼其實(shí)是從壓縮包解壓出來的,這是因?yàn)楣P者需要將源代碼文件也值得放在git中進(jìn)行管理,使用源代碼壓縮包更為方便。
2. 構(gòu)建geos、proj
在實(shí)現(xiàn)了通用腳本build-common.ps1之后,構(gòu)建程序就非常容易了,比如構(gòu)建geos的腳本如下:
# geos.ps1
param(
[string]$Name = "geos-3.12.2",
[string]$SourceDir = "../Source",
[string]$Generator,
[string]$InstallDir,
[string]$SymbolDir,
[bool]$Force = $false, # 是否強(qiáng)制重新構(gòu)建
[bool]$Cleanup = $true # 是否在構(gòu)建完成后刪除源碼和構(gòu)建目錄
)
# 目標(biāo)文件
$DllPath = "$InstallDir/bin/geos_c.dll"
# 依賴庫(kù)數(shù)組
$Librarys = @()
# 符號(hào)庫(kù)文件
$PdbFiles = @(
"bin/RelWithDebInfo/geos.pdb",
"bin/RelWithDebInfo/geos_c.pdb"
)
# 額外構(gòu)建參數(shù)
$CMakeCacheVariables = @{
BUILD_TESTING = "OFF"
}
. ./build-common.ps1 -Name $Name `
-SourceDir $SourceDir `
-InstallDir $InstallDir `
-SymbolDir $SymbolDir `
-Generator $Generator `
-TargetDll $DllPath `
-PdbFiles $PdbFiles `
-CMakeCacheVariables $CMakeCacheVariables `
-MultiConfig $false `
-Force $Force `
-Cleanup $Cleanup `
-Librarys $Librarys
在這個(gè)腳本中,$SourceDir是源代碼壓縮包所在的文件夾,$Name是壓縮包和壓縮包內(nèi)文件夾的名稱。而構(gòu)建proj的腳本如下:
# proj.ps1
param(
[string]$Name = "proj-9.4.1",
[string]$SourceDir = "../Source",
[string]$Generator,
[string]$InstallDir,
[string]$SymbolDir,
[bool]$Force = $false, # 是否強(qiáng)制重新構(gòu)建
[bool]$Cleanup = $true # 是否在構(gòu)建完成后刪除源碼和構(gòu)建目錄
)
# 目標(biāo)文件
$DllPath = "$InstallDir/bin/proj_9_4.dll"
# 依賴庫(kù)數(shù)組
$Librarys = @("nlohmann-json", "sqlite", "libtiff")
# 符號(hào)庫(kù)文件
$PdbFiles = @(
"bin/RelWithDebInfo/proj_9_4.pdb"
)
# 額外構(gòu)建參數(shù)
$CMakeCacheVariables = @{
BUILD_TESTING = "OFF"
ENABLE_CURL = "OFF"
BUILD_PROJSYNC = "OFF"
}
. ./build-common.ps1 -Name $Name `
-SourceDir $SourceDir `
-InstallDir $InstallDir `
-SymbolDir $SymbolDir `
-Generator $Generator `
-TargetDll $DllPath `
-PdbFiles $PdbFiles `
-CMakeCacheVariables $CMakeCacheVariables `
-MultiConfig $false `
-Force $Force `
-Cleanup $Cleanup `
-Librarys $Librarys
proj必須依賴于sqlite,具體的構(gòu)建辦法可參看《CMake構(gòu)建學(xué)習(xí)筆記23-SQLite庫(kù)的構(gòu)建》。因?yàn)閹?kù)程序本身就可能會(huì)依賴別的依賴庫(kù),所以在這里干脆實(shí)現(xiàn)了在構(gòu)建庫(kù)之前,也構(gòu)建該庫(kù)的依賴庫(kù),具體實(shí)在build-common.ps1中實(shí)現(xiàn):
if ($Librarys.Count -gt 0) {
. "./BuildRequired.ps1"
BuildRequired -Librarys $Librarys
}
BuildRequired.ps1也是個(gè)構(gòu)建腳本,具體內(nèi)容非常簡(jiǎn)單,就是調(diào)用依賴庫(kù)的構(gòu)建腳本:
function BuildRequired {
param (
[string[]]$Librarys
)
Write-Output "------------------------------------------------"
Write-Output "Start installing all required dependencies..."
foreach ($item in $Librarys) {
Write-Output "Find the library named $item and start installing..."
# 動(dòng)態(tài)構(gòu)建腳本文件名并執(zhí)行
$BuildScript = "./$item.ps1";
& $BuildScript -Generator $Generator -InstallDir $InstallDir -SymbolDir $SymbolDir
}
Write-Output "All required dependencies have been installed."
Write-Output "------------------------------------------------"
}
3. 其他
提供的腳本太多,筆者確實(shí)也覺得有點(diǎn)太繞了,反而不如前面的文章的腳本內(nèi)容直觀。不過這也符合編程的基本思路吧,開始的程序都很簡(jiǎn)單直接,后來隨著功能的增多,慢慢就變得越來越抽象難以理解。以上腳本都收錄在項(xiàng)目中,可參考使用。

浙公網(wǎng)安備 33010602011771號(hào)