CMake构建学习笔记24-使用通用脚本构建PROJ和GEOS

1. 通用脚本

在之前的文章《CMake构建学习笔记21-通用的CMake构建脚本》中我们创建了一个通用的cmake构建脚本cmake-build.ps1

param(     [string]$SourceLocalPath,     [string]$BuildDir,     [string]$Generator,     [string]$InstallDir,     [string]$SymbolDir,     [string[]]$PdbFiles,     [hashtable]$CMakeCacheVariables,     [bool]$MultiConfig = $false  # 控制是否使用多配置类型 )  # 清除旧的构建目录 if (Test-Path $BuildDir) {     Remove-Item -Path $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir  # 构建CMake命令行参数 $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  # 构建阶段,指定构建类型 cmake --build $BuildDir --config RelWithDebInfo --parallel  # 安装阶段,指定构建类型和安装目标 cmake --build $BuildDir --config RelWithDebInfo --target install  # 复制符号库 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"     } }  # 清理构建目录 #Remove-Item -Path $BuildDir -Recurse -Force 

《CMake构建学习笔记22-libxml2库的构建》这篇文章中使用这个脚本构建了libxml2库:

param(         [string]$Name = "libxml2-v2.14.4",     [string]$SourceDir = "../Source",     [string]$Generator,     [string]$InstallDir,       [string]$SymbolDir  )  # 根据 $Name 动态构建路径 $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 }  # 检查目标文件是否存在,以判断是否安装 $DstFilePath = "$InstallDir/bin/libxml2.dll" if (Test-Path $DstFilePath) {     Write-Output "The current library has been installed."     exit 1 }   # 复制符号库 $PdbFiles = @(     "$BuildDir/RelWithDebInfo/libxml2.pdb" )   # 额外构建参数 $CMakeCacheVariables = @{     BUILD_SHARED_LIBS = "ON"     LIBXML2_WITH_ZLIB = "ON"     LIBXML2_WITH_ICONV = "ON"     LIBXML2_WITH_HTTP = "ON" }  # 调用通用构建脚本 . ./cmake-build.ps1 -SourceLocalPath $SourcePath `     -BuildDir $BuildDir `     -Generator $Generator `     -InstallDir $InstallDir `     -SymbolDir $SymbolDir `     -PdbFiles $PdbFiles `     -CMakeCacheVariables $CMakeCacheVariables `     -MultiConfig $true 

因为提供了cmake构建方式的程序的构建行为是比较统一的,这个构建libxml2库的脚本可以进一步封装,形成一个通用的调用cmake-build.ps1构建程序的脚本。cmake-build.ps1只是包含了调用cmake执行构建的内容,但是其实整个构建过程需要做的事情很多,比如安装符号库、安装程序的依赖库等等,这些过程指的再封装一层构建的脚本。笔者封装的脚本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,        # 是否强制重新构建     [bool]$Cleanup = $true,        # 是否在构建完成后删除源码和构建目录        [string[]]$Librarys = @()  # 可选的依赖库数组,例如:-Librarys "zlib", "libjpeg" )  # 动态路径构建 $zipFilePath = Join-Path -Path $SourceDir -ChildPath "$Name.zip" $SourcePath = Join-Path -Path $SourceDir -ChildPath $Name $BuildDir = Join-Path -Path "." -ChildPath $Name  # 检查是否已经安装(通过目标 DLL) if (-not $Force -and $TargetDll -and (Test-Path $TargetDll)) {     Write-Output "Library already installed: $TargetDll"     exit 0 }  # 创建所有依赖库的容器 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 }  # 如果是强制构建,且构建目录已存在,先删除旧的构建目录(确保干净构建) 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 $_ }  # 调用通用 CMake 构建脚本 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 }  # 构建成功后,根据 Cleanup 开关决定是否删除 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." 

这段脚本干了很多零碎的事情,但是对于一个完整的构建系统是必须的,比如判断是否需要强制构建、是否需要清理中间文件、安装程序的依赖库、安装符号库等等。另外,脚本的使用源代码其实是从压缩包解压出来的,这是因为笔者需要将源代码文件也值得放在git中进行管理,使用源代码压缩包更为方便。

2. 构建geos、proj

在实现了通用脚本build-common.ps1之后,构建程序就非常容易了,比如构建geos的脚本如下:

# geos.ps1 param(         [string]$Name = "geos-3.12.2",     [string]$SourceDir = "../Source",     [string]$Generator,     [string]$InstallDir,       [string]$SymbolDir,       [bool]$Force = $false,        # 是否强制重新构建     [bool]$Cleanup = $true        # 是否在构建完成后删除源码和构建目录 )  # 目标文件 $DllPath = "$InstallDir/bin/geos_c.dll"  # 依赖库数组 $Librarys = @()    # 符号库文件 $PdbFiles = @(     "bin/RelWithDebInfo/geos.pdb",     "bin/RelWithDebInfo/geos_c.pdb" )   # 额外构建参数 $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 

在这个脚本中,$SourceDir是源代码压缩包所在的文件夹,$Name是压缩包和压缩包内文件夹的名称。而构建proj的脚本如下:

# proj.ps1 param(         [string]$Name = "proj-9.4.1",     [string]$SourceDir = "../Source",     [string]$Generator,     [string]$InstallDir,       [string]$SymbolDir,       [bool]$Force = $false,        # 是否强制重新构建     [bool]$Cleanup = $true        # 是否在构建完成后删除源码和构建目录 )  # 目标文件 $DllPath = "$InstallDir/bin/proj_9_4.dll"  # 依赖库数组 $Librarys = @("nlohmann-json", "sqlite", "libtiff")  # 符号库文件 $PdbFiles = @(     "bin/RelWithDebInfo/proj_9_4.pdb"       )   # 额外构建参数 $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,具体的构建办法可参看《CMake构建学习笔记23-SQLite库的构建》。因为库程序本身就可能会依赖别的依赖库,所以在这里干脆实现了在构建库之前,也构建该库的依赖库,具体实在build-common.ps1中实现:

if ($Librarys.Count -gt 0) {     . "./BuildRequired.ps1"     BuildRequired -Librarys $Librarys } 

BuildRequired.ps1也是个构建脚本,具体内容非常简单,就是调用依赖库的构建脚本:

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..."                 # 动态构建脚本文件名并执行         $BuildScript = "./$item.ps1";                    & $BuildScript -Generator $Generator -InstallDir $InstallDir -SymbolDir $SymbolDir            }     Write-Output "All required dependencies have been installed."        Write-Output "------------------------------------------------"   } 

3. 其他

提供的脚本太多,笔者确实也觉得有点太绕了,反而不如前面的文章的脚本内容直观。不过这也符合编程的基本思路吧,开始的程序都很简单直接,后来随着功能的增多,慢慢就变得越来越抽象难以理解。以上脚本都收录在项目中,可参考使用。

发表评论

评论已关闭。

相关文章