PowerShell: simple-make.ps1
日前遇到一個大型舊專案要導入 CI/CD, 該專案檔案結構為數千個獨立小程式所組成, 而專案並沒有集成的 Makefile / .sln / .proj / ... 之類專案檔一次完成全部程式編譯, 需針對每個獨立程式進行編譯後部署. 又獨立程式過多, 若每次 CI/CD 時都需全部重新編譯, 過於耗時.
因其他因素無法撰寫 Makefile 來判斷是否需重新編譯, 也無法使用習慣的 node.js / golang / ... 等語言進行腳本開發. 最後選擇是透過 PowerShell 硬幹.
Windows 環境上, 之前只寫過簡單 BAT 來處理大量繁瑣的指令動作, 複雜腳本是第一次接觸. 邊找資料, 邊踩坑, 在觀念尚不見得完全正確理解下, 完成需求開發. 小小筆記於此.
simple-make.ps1
以下為去掉專案相關資訊的 pseudo code, 僅作為程式邏輯思維用.
$BUILD_ROOT = "~/project/sandbox"
$DIR_SPLIT = "\"
$ProjHashtable = @{
Root = ""
CodeName = ""
DLLFile = ""
SRCFile = ""
}
$toUpdateProjList = New-Object System.Collections.Generic.List[System.Object]
Write-host '--- prepare workspace --------------------------------------------------'
Copy-Item -Path lib -Destination $BUILD_ROOT -Recurse -Force
$CC_PATH = "$env:CC_PATH"
if ($CC_PATH -eq "") {$CC_PATH = "cc.exe"}
Write-host '--- find updated projects ----------------------------------------------'
git fetch origin
$gitdiff = git diff --name-status origin/master
ForEach ($fileStatus in $($gitdiff -split "`r`n")) {
$fileStatusList = $fileStatus.Split("`t")
$filepath = ""
if (($fileStatusList[0] -eq "A") -or ($fileStatusList[0] -eq "M"))
{ $filepath = $fileStatusList[1] }
elseif ($fileStatusList[0].Contains("R"))
{ $filepath = $fileStatusList[2] }
if ($filepath -eq "") { continue }
$farray = $filepath.Split("/")
if (!($farray[0] -like "SRC*")) { continue }
$projCodeName = $farray[1]
$projRoot = $farray[0]
$existProj = $false
for ($i = 0; $i -lt $toUpdateProjList.count; $i++) {
if ($toUpdateProjList[$i].CodeName -eq $projCodeName) {
if ($filepath -like "*CC") {
$toUpdateProjList[$i].SRCFile = $filepath }
elseif ($filepath -like "*DLL") {
$toUpdateProjList[$i].DLLFile = $filepath }
$existProj = $true
break
}
}
if ($existProj) {continue}
$newProj = [pscustomobject]$ProjHashtable
$newProj.Root = $projRoot
$newProj.CodeName = $projCodeName
if ($filepath -like "*CC") {
$newProj.SRCFile = $filepath
$toUpdateProjList += $newProj }
elseif ($filepath -like "*DLL") {
$newProj.DLLFile = $filepath
$toUpdateProjList += $newProj }
}
Write-host '--- build and prepare artifacts ----------------------------------------'
if (Test-Path .\release) {Remove-Item -path .\release -Recurse}
New-Item release -ItemType "directory"
# Build from VPG or copy DLL dirsctory
foreach ($updatedProj in $toUpdateProjList) {
$srcDir = $updatedProj.Root + $DIR_SPLIT + $updatedProj.CodeName
if ($updatedProj.SRCFile -ne "") {
$buildDir = $BUILD_ROOT + $DIR_SPLIT + "SRC"
$buildProj = $buildDir + $DIR_SPLIT + $updatedProj.CodeName + ".proj"
$resultDir = $buildDir + $DIR_SPLIT + $updatedProj.CodeName + $DIR_SPLIT + "WIN32"
$resultDll = $resultDir + $DIR_SPLIT + $updatedProj.CodeName + ".DLL"
$resultSrc = $resultDir + $DIR_SPLIT + $updatedProj.CodeName + ".CC"
Copy-Item -Path $srcDir\* -Destination $buildDir -Recurse -Force
if (Test-Path $resultDir) { Remove-Item -path $resultDir -Recurse }
New-Item $resultDir -ItemType "directory"
& "$CC_PATH" $buildProj
$timeout = 600
$interval = 10
while (!(Test-Path $resultSrc) -and !(Test-Path $resultDll) -and ($timeout -gt 0)) {
$timeout -= $interval
Start-Sleep $interval }
& ./stopProcess.ps1
if (!(Test-Path $resultSrc) -or !(Test-Path $resultDll)) {
$result = 9
continue }
Copy-Item -Path $resultDir\* -Destination release -Recurse -Force }
else {
$depolyFiles = $srcDir + $DIR_SPLIT + $updatedProj.CodeName + $DIR_SPLIT + "WIN32" + $DIR_SPLIT + $updatedProj.CodeName + "*"
Copy-Item -Path $depolyFiles -Destination release -Recurse -Force }
}
exit $result
Reference
- PowerShell 文件 - PowerShell | Microsoft Docs
- Compare timestamps for two files
- Boolean literals in PowerShell - Stack Overflow
- Array
- PSCustomObject
- How to run an EXE file in PowerShell with parameters with spaces and quotes - Stack Overflow
- What are Powershell exit codes? | Powershell return code | ManageEngine