Skip to content

Commit 0954f89

Browse files
vaindclaude
andcommitted
feat: Add CMake FetchContent support to updater
Implements support for updating CMake FetchContent_Declare() statements in addition to existing submodules, properties files, and scripts. Key features: - Support for path.cmake#DepName and auto-detection syntax - Hash vs tag detection with hash format preservation - Hash-to-tag resolution for version comparison - GitHub Actions output integration - Comprehensive test coverage (23 tests) Resolves: #91 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 06ba389 commit 0954f89

10 files changed

+554
-1
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# CMake FetchContent helper functions for update-dependency.ps1
2+
3+
function Parse-CMakeFetchContent($filePath, $depName) {
4+
$content = Get-Content $filePath -Raw
5+
6+
if ($depName) {
7+
$pattern = "FetchContent_Declare\s*\(\s*$depName\s+([^)]+)\)"
8+
} else {
9+
# Find all FetchContent_Declare blocks
10+
$allMatches = [regex]::Matches($content, "FetchContent_Declare\s*\(\s*([a-zA-Z0-9_-]+)", 'Singleline')
11+
if ($allMatches.Count -eq 1) {
12+
$depName = $allMatches[0].Groups[1].Value
13+
$pattern = "FetchContent_Declare\s*\(\s*$depName\s+([^)]+)\)"
14+
} else {
15+
throw "Multiple FetchContent declarations found. Use #DepName syntax."
16+
}
17+
}
18+
19+
$match = [regex]::Match($content, $pattern, 'Singleline,IgnoreCase')
20+
if (-not $match.Success) {
21+
throw "FetchContent_Declare for '$depName' not found in $filePath"
22+
}
23+
$block = $match.Groups[1].Value
24+
25+
# Look for GIT_REPOSITORY and GIT_TAG patterns specifically
26+
# Exclude matches that are in comments (lines starting with #)
27+
$repoMatch = [regex]::Match($block, '(?m)^\s*GIT_REPOSITORY\s+(\S+)')
28+
$tagMatch = [regex]::Match($block, '(?m)^\s*GIT_TAG\s+(\S+)')
29+
30+
$repo = if ($repoMatch.Success) { $repoMatch.Groups[1].Value } else { "" }
31+
$tag = if ($tagMatch.Success) { $tagMatch.Groups[1].Value } else { "" }
32+
33+
if ([string]::IsNullOrEmpty($repo) -or [string]::IsNullOrEmpty($tag)) {
34+
throw "Could not parse GIT_REPOSITORY or GIT_TAG from FetchContent_Declare block"
35+
}
36+
37+
return @{ GitRepository = $repo; GitTag = $tag; DepName = $depName }
38+
}
39+
40+
function Find-TagForHash($repo, $hash) {
41+
try {
42+
$refs = git ls-remote --tags $repo
43+
foreach ($ref in $refs) {
44+
$commit, $tagRef = $ref -split '\s+', 2
45+
if ($commit -eq $hash) {
46+
return $tagRef -replace '^refs/tags/', ''
47+
}
48+
}
49+
return $null
50+
}
51+
catch {
52+
Write-Host "Warning: Could not resolve hash $hash to tag name: $_"
53+
return $null
54+
}
55+
}
56+
57+
function Update-CMakeFile($filePath, $depName, $newValue) {
58+
$content = Get-Content $filePath -Raw
59+
$fetchContent = Parse-CMakeFetchContent $filePath $depName
60+
$originalValue = $fetchContent.GitTag
61+
$repo = $fetchContent.GitRepository
62+
$wasHash = $originalValue -match '^[a-f0-9]{40}$'
63+
64+
if ($wasHash) {
65+
# Convert tag to hash and add comment
66+
$newHashRefs = git ls-remote $repo "refs/tags/$newValue"
67+
if (-not $newHashRefs) {
68+
throw "Tag $newValue not found in repository $repo"
69+
}
70+
$newHash = ($newHashRefs -split '\s+')[0]
71+
$replacement = "$newHash # $newValue"
72+
73+
# Validate ancestry: ensure old hash is reachable from new tag
74+
# Note: Skipping ancestry check for now as it requires local repository
75+
# TODO: Implement proper ancestry validation for remote repositories
76+
Write-Host "Warning: Skipping ancestry validation for hash update from $originalValue to $newValue"
77+
} else {
78+
$replacement = $newValue
79+
}
80+
81+
# Update GIT_TAG value, preserving formatting
82+
$pattern = "(FetchContent_Declare\s*\(\s*$depName\s+[^)]*GIT_TAG\s+)\S+([^#\r\n]*).*?(\s*[^)]*\))"
83+
$newContent = [regex]::Replace($content, $pattern, "`${1}$replacement`${3}", 'Singleline')
84+
85+
if ($newContent -eq $content) {
86+
throw "Failed to update GIT_TAG in $filePath - pattern may not have matched"
87+
}
88+
89+
$newContent | Out-File $filePath -NoNewline
90+
91+
# Verify the update worked
92+
$verifyContent = Parse-CMakeFetchContent $filePath $depName
93+
$expectedValue = $wasHash ? $newHash : $newValue
94+
if ($verifyContent.GitTag -notmatch [regex]::Escape($expectedValue)) {
95+
throw "Update verification failed - read-after-write did not match expected value"
96+
}
97+
}

updater/scripts/update-dependency.ps1

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ param(
66
# * `get-version` - return the currently specified dependency version
77
# * `get-repo` - return the repository url (e.g. https://github.com/getsentry/dependency)
88
# * `set-version` - update the dependency version (passed as another string argument after this one)
9+
# - a CMake file (.cmake) with FetchContent_Declare statements:
10+
# * Use `path/to/file.cmake#DepName` to specify dependency name
11+
# * Or just `path/to/file.cmake` if file contains single FetchContent_Declare
912
[Parameter(Mandatory = $true)][string] $Path,
1013
# RegEx pattern that will be matched against available versions when picking the latest one
1114
[string] $Pattern = '',
@@ -16,6 +19,19 @@ param(
1619
Set-StrictMode -Version latest
1720
. "$PSScriptRoot/common.ps1"
1821

22+
# Parse CMake file with dependency name
23+
$cmakeFile = $null
24+
$cmakeDep = $null
25+
if ($Path -match '^(.+\.cmake)#(.+)$') {
26+
$cmakeFile = $Matches[1]
27+
$cmakeDep = $Matches[2]
28+
$Path = $cmakeFile # Set Path to file for existing logic
29+
} elseif ($Path -match '\.cmake$') {
30+
$cmakeFile = $Path
31+
$cmakeDep = $null # Will auto-detect
32+
}
33+
$isCMakeFile = $cmakeFile -ne $null
34+
1935
if (-not (Test-Path $Path ))
2036
{
2137
throw "Dependency $Path doesn't exit";
@@ -41,7 +57,32 @@ if (-not $isSubmodule)
4157
$isScript = $Path -match '\.(ps1|sh)$'
4258
function DependencyConfig ([Parameter(Mandatory = $true)][string] $action, [string] $value = $null)
4359
{
44-
if ($isScript)
60+
if ($isCMakeFile) {
61+
# CMake file handling
62+
switch ($action) {
63+
'get-version' {
64+
$fetchContent = Parse-CMakeFetchContent $cmakeFile $cmakeDep
65+
$currentValue = $fetchContent.GitTag
66+
if ($currentValue -match '^[a-f0-9]{40}$') {
67+
# Try to resolve hash to tag for version comparison
68+
$repo = $fetchContent.GitRepository
69+
$tagForHash = Find-TagForHash $repo $currentValue
70+
return $tagForHash ?? $currentValue
71+
}
72+
return $currentValue
73+
}
74+
'get-repo' {
75+
return (Parse-CMakeFetchContent $cmakeFile $cmakeDep).GitRepository
76+
}
77+
'set-version' {
78+
Update-CMakeFile $cmakeFile $cmakeDep $value
79+
}
80+
Default {
81+
throw "Unknown action $action"
82+
}
83+
}
84+
}
85+
elseif ($isScript)
4586
{
4687
if (Get-Command 'chmod' -ErrorAction SilentlyContinue)
4788
{
@@ -99,6 +140,9 @@ if (-not $isSubmodule)
99140
}
100141
}
101142
}
143+
144+
# Load CMake helper functions
145+
. "$PSScriptRoot/cmake-functions.ps1"
102146
}
103147

104148
if ("$Tag" -eq '')
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(sentry-native
4+
GIT_REPOSITORY
5+
https://github.com/getsentry/sentry-native
6+
GIT_TAG
7+
v0.9.1 # Current stable version
8+
GIT_SHALLOW
9+
FALSE
10+
GIT_SUBMODULES
11+
"external/breakpad"
12+
)
13+
14+
FetchContent_MakeAvailable(sentry-native)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
sentry-native
5+
GIT_REPOSITORY https://github.com/getsentry/sentry-native
6+
GIT_TAG a64d5bd8ee130f2cda196b6fa7d9b65bfa6d32e2 # 0.9.1
7+
GIT_SHALLOW FALSE
8+
GIT_SUBMODULES "external/breakpad"
9+
)
10+
11+
FetchContent_MakeAvailable(sentry-native)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
sentry-native
5+
GIT_REPOSITORY https://github.com/getsentry/sentry-native
6+
# Missing GIT_TAG
7+
GIT_SHALLOW FALSE
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
sentry-native
5+
# Missing GIT_REPOSITORY
6+
GIT_TAG v0.9.1
7+
GIT_SHALLOW FALSE
8+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
sentry-native
5+
GIT_REPOSITORY https://github.com/getsentry/sentry-native
6+
GIT_TAG v0.9.1
7+
GIT_SHALLOW FALSE
8+
)
9+
10+
FetchContent_Declare(
11+
googletest
12+
GIT_REPOSITORY https://github.com/google/googletest
13+
GIT_TAG v1.14.0
14+
EXCLUDE_FROM_ALL
15+
)
16+
17+
FetchContent_MakeAvailable(sentry-native googletest)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
sentry-native
5+
GIT_REPOSITORY https://github.com/getsentry/sentry-native
6+
GIT_TAG v0.9.1
7+
GIT_SHALLOW FALSE
8+
GIT_SUBMODULES "external/breakpad"
9+
)
10+
11+
FetchContent_MakeAvailable(sentry-native)

0 commit comments

Comments
 (0)