-
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcmake-functions.ps1
More file actions
208 lines (180 loc) · 7.44 KB
/
cmake-functions.ps1
File metadata and controls
208 lines (180 loc) · 7.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# CMake FetchContent helper functions for update-dependency.ps1
function Parse-CMakeFetchContent {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$filePath,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrEmpty($_) -or $_ -match '^[a-zA-Z][a-zA-Z0-9_.-]*$'})]
[string]$depName
)
$content = Get-Content $filePath -Raw
if ($depName) {
$pattern = "FetchContent_Declare\s*\(\s*$depName\s+([^)]+)\)"
} else {
# Find all FetchContent_Declare blocks
$allMatches = [regex]::Matches($content, "FetchContent_Declare\s*\(\s*([a-zA-Z0-9_-]+)", 'Singleline')
if ($allMatches.Count -eq 1) {
$depName = $allMatches[0].Groups[1].Value
$pattern = "FetchContent_Declare\s*\(\s*$depName\s+([^)]+)\)"
} else {
throw "Multiple FetchContent declarations found. Use #DepName syntax."
}
}
$match = [regex]::Match($content, $pattern, 'Singleline,IgnoreCase')
if (-not $match.Success) {
throw "FetchContent_Declare for '$depName' not found in $filePath"
}
$block = $match.Groups[1].Value
# Look for GIT_REPOSITORY and GIT_TAG patterns specifically
# Exclude matches that are in comments (lines starting with #)
$repoMatch = [regex]::Match($block, '(?m)^\s*GIT_REPOSITORY\s+(\S+)')
$tagMatch = [regex]::Match($block, '(?m)^\s*GIT_TAG\s+(\S+)')
$repo = if ($repoMatch.Success) { $repoMatch.Groups[1].Value } else { "" }
$tag = if ($tagMatch.Success) { $tagMatch.Groups[1].Value } else { "" }
if ([string]::IsNullOrEmpty($repo) -or [string]::IsNullOrEmpty($tag)) {
throw "Could not parse GIT_REPOSITORY or GIT_TAG from FetchContent_Declare block"
}
# Resolve CMake variable references like ${FOO_REF}
$gitTagVariable = $null
if ($tag -match '^\$\{(\w+)\}$') {
$gitTagVariable = $Matches[1]
$setMatch = [regex]::Match($content, "(?m)^\s*set\s*\(\s*$gitTagVariable\s+`"?([^`"\s)]+)")
if (-not $setMatch.Success) {
throw "CMake variable '$gitTagVariable' referenced by GIT_TAG not found in $filePath"
}
$tag = $setMatch.Groups[1].Value
}
return @{ GitRepository = $repo; GitTag = $tag; DepName = $depName; GitTagVariable = $gitTagVariable }
}
function Find-TagForHash {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$repo,
[Parameter(Mandatory=$true)]
[ValidatePattern('^[a-f0-9]{40}$')]
[string]$hash
)
try {
$refs = git ls-remote --tags $repo
if ($LASTEXITCODE -ne 0) {
throw "Failed to fetch tags from repository $repo (git ls-remote failed with exit code $LASTEXITCODE)"
}
foreach ($ref in $refs) {
$commit, $tagRef = $ref -split '\s+', 2
if ($commit -eq $hash) {
return $tagRef -replace '^refs/tags/', ''
}
}
return $null
}
catch {
Write-Host "Warning: Could not resolve hash $hash to tag name: $_"
return $null
}
}
function Test-HashAncestry {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$repo,
[Parameter(Mandatory=$true)]
[ValidatePattern('^[a-f0-9]{40}$')]
[string]$oldHash,
[Parameter(Mandatory=$true)]
[ValidatePattern('^[a-f0-9]{40}$')]
[string]$newHash
)
try {
# Create a temporary directory for git operations
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid())
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
try {
Push-Location $tempDir
# Initialize a bare repository and add the remote
git init --bare 2>$null | Out-Null
git remote add origin $repo 2>$null | Out-Null
# Fetch both commits
git fetch origin $oldHash 2>$null | Out-Null
git fetch origin $newHash 2>$null | Out-Null
# Check if old hash is ancestor of new hash
git merge-base --is-ancestor $oldHash $newHash 2>$null
$isAncestor = $LastExitCode -eq 0
return $isAncestor
}
finally {
Pop-Location
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
catch {
Write-Host "Error: Could not validate ancestry for $oldHash -> $newHash : $_"
# When in doubt, fail safely to prevent incorrect updates
return $false
}
}
function Update-CMakeFile {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$filePath,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrEmpty($_) -or $_ -match '^[a-zA-Z][a-zA-Z0-9_.-]*$'})]
[string]$depName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$newValue
)
$content = Get-Content $filePath -Raw
$fetchContent = Parse-CMakeFetchContent $filePath $depName
$originalValue = $fetchContent.GitTag
$repo = $fetchContent.GitRepository
$wasHash = $originalValue -match '^[a-f0-9]{40}$'
if ($wasHash) {
# Convert tag to hash and add comment
$newHashRefs = git ls-remote $repo "refs/tags/$newValue"
if ($LASTEXITCODE -ne 0) {
throw "Failed to fetch tag $newValue from repository $repo (git ls-remote failed with exit code $LASTEXITCODE)"
}
if (-not $newHashRefs) {
throw "Tag $newValue not found in repository $repo"
}
$newHash = ($newHashRefs -split '\s+')[0]
$replacement = "$newHash # $newValue"
# Validate ancestry: ensure old hash is reachable from new tag
if (-not (Test-HashAncestry $repo $originalValue $newHash)) {
throw "Cannot update: hash $originalValue is not in history of tag $newValue"
}
} else {
$replacement = $newValue
}
# Update the value, replacing entire line content after the value
# This removes potentially outdated version-specific comments
$gitTagVariable = $fetchContent.GitTagVariable
if ($gitTagVariable) {
# Update the set() line that defines the variable
$pattern = "(?m)(^\s*set\s*\(\s*$gitTagVariable\s+`"?)([^`"\s)]+)(`"?[^)]*\))[^\r\n]*"
$valueOnly = if ($wasHash) { $newHash } else { $newValue }
$trailingComment = if ($wasHash) { " # $newValue" } else { "" }
$newContent = [regex]::Replace($content, $pattern, "`${1}$valueOnly`${3}$trailingComment")
} else {
# Update GIT_TAG value in FetchContent_Declare block
$pattern = "(FetchContent_Declare\s*\(\s*$depName\s+[^)]*GIT_TAG\s+)[^\r\n]+(\r?\n[^)]*\))"
$newContent = [regex]::Replace($content, $pattern, "`${1}$replacement`${2}", 'Singleline')
}
if ($newContent -eq $content) {
throw "Failed to update GIT_TAG in $filePath - pattern may not have matched"
}
$newContent | Out-File $filePath -NoNewline
# Verify the update worked
$verifyContent = Parse-CMakeFetchContent $filePath $depName
$expectedValue = $wasHash ? $newHash : $newValue
if ($verifyContent.GitTag -notmatch [regex]::Escape($expectedValue)) {
throw "Update verification failed - read-after-write did not match expected value"
}
}