Skip to content

Commit f8e6ac4

Browse files
shanselmanCopilot
andauthored
Use GitVersion for app version metadata
Remove stale hardcoded project/app version metadata and derive product builds from GitVersion tag history. Preserve prerelease labels in user-facing app versions, update local release helpers/docs/CI validation, and add regression tests. Closes #570 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3d75dd6 commit f8e6ac4

12 files changed

Lines changed: 294 additions & 131 deletions

File tree

.config/dotnet-tools.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"gitversion.tool": {
6+
"version": "6.4.0",
7+
"commands": [
8+
"dotnet-gitversion"
9+
],
10+
"rollForward": false
11+
}
12+
}
13+
}

.github/workflows/ci.yml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ jobs:
353353

354354
steps:
355355
- uses: actions/checkout@v6
356+
with:
357+
fetch-depth: 0
356358

357359
- name: Setup .NET 10
358360
uses: actions/setup-dotnet@v5
@@ -371,17 +373,32 @@ jobs:
371373
run: dotnet restore src/OpenClaw.Tray.WinUI -r ${{ matrix.rid }}
372374

373375
- name: Build WinUI Tray App (Release)
374-
run: dotnet build src/OpenClaw.Tray.WinUI --no-restore -c Release -r ${{ matrix.rid }} -p:Version=${{ needs.test.outputs.semVer }}
376+
run: dotnet build src/OpenClaw.Tray.WinUI --no-restore -c Release -r ${{ matrix.rid }}
375377

376378
- name: Publish WinUI Tray App
377-
run: dotnet publish src/OpenClaw.Tray.WinUI -c Release -r ${{ matrix.rid }} --self-contained --no-restore -p:Version=${{ needs.test.outputs.semVer }} -o publish
379+
run: dotnet publish src/OpenClaw.Tray.WinUI -c Release -r ${{ matrix.rid }} --self-contained --no-restore -o publish
378380

379381
- name: Publish SetupEngine.UI
380382
run: |
381-
dotnet publish src/OpenClaw.SetupEngine.UI -c Release -r ${{ matrix.rid }} --self-contained -p:Version=${{ needs.test.outputs.semVer }} -o publish-setup
383+
dotnet publish src/OpenClaw.SetupEngine.UI -c Release -r ${{ matrix.rid }} --self-contained -o publish-setup
382384
mkdir publish\SetupEngine
383385
copy publish-setup\* publish\SetupEngine\ -Recurse
384386
387+
- name: Verify GitVersion assembly metadata
388+
shell: pwsh
389+
run: |
390+
$expected = "${{ needs.test.outputs.semVer }}"
391+
$assemblyPath = Resolve-Path "publish\OpenClaw.Tray.WinUI.dll"
392+
$assembly = [System.Reflection.Assembly]::LoadFile($assemblyPath)
393+
$attribute = $assembly.GetCustomAttributes([System.Reflection.AssemblyInformationalVersionAttribute], $false) | Select-Object -First 1
394+
if (-not $attribute) {
395+
throw "OpenClaw.Tray.WinUI.dll is missing AssemblyInformationalVersionAttribute."
396+
}
397+
$actual = $attribute.InformationalVersion -replace '\+.*$', ''
398+
if ($actual -ne $expected) {
399+
throw "AssemblyInformationalVersion '$actual' did not match GitVersion SemVer '$expected'."
400+
}
401+
385402
- name: Disable NuGet source mapping for signing
386403
if: startsWith(github.ref, 'refs/tags/v') && matrix.rid != 'win-arm64'
387404
shell: pwsh
@@ -450,6 +467,8 @@ jobs:
450467

451468
steps:
452469
- uses: actions/checkout@v6
470+
with:
471+
fetch-depth: 0
453472

454473
- name: Setup .NET 10 for VS MSBuild
455474
uses: actions/setup-dotnet@v5
@@ -505,7 +524,6 @@ jobs:
505524
/p:AppxPackageSigningEnabled=false
506525
/p:AppxBundle=Never
507526
/p:UapAppxPackageBuildMode=SideloadOnly
508-
/p:Version=${{ needs.test.outputs.majorMinorPatch }}
509527
/p:AppxPackageDir=AppPackages\
510528
511529
- name: Find MSIX Package
@@ -661,14 +679,13 @@ jobs:
661679
- 🦞 System tray integration with gateway status
662680
- 🔄 Auto-updates from GitHub Releases
663681
- ✅ Code-signed with Azure Trusted Signing
664-
- 📦 MSIX package available for native camera/microphone consent prompts
665682
666683
### Requirements
667684
- Windows 10 version 1903 or later
668685
- [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
669686
- OpenClaw gateway running locally
670687
671688
### Quick Start
672-
1. Run the installer for your architecture (or sideload the MSIX for camera consent)
689+
1. Run the installer for your architecture
673690
2. Launch from Start Menu or system tray
674691
3. Right-click tray icon → Settings to configure

docs/RELEASING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ Only tag when `HEAD == origin/master`.
180180
## Versioning rules
181181

182182
- Do not manually bump project or manifest versions for routine releases.
183-
- Treat csproj `<Version>` as a local fallback for dev builds.
183+
- Do not add csproj `<Version>` release fallbacks; product versions come from
184+
GitVersion/tag history.
184185
- Release versions come from the tag (`vX.Y.Z` or `vX.Y.Z-alpha.N`).
185-
- CI computes the version from git history/tags and passes it to builds.
186-
186+
- CI computes GitVersion outputs for artifact naming, while product builds use
187+
GitVersion-backed assembly metadata.

docs/VERSIONING.md

Lines changed: 47 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,73 @@
11
# Versioning in OpenClaw Windows Hub
22

3-
## How Versioning Works
3+
## Source of truth
44

5-
This project uses GitVersion for automatic semantic versioning based on git tags and commit history. The version is used in multiple places:
5+
OpenClaw uses GitVersion and git tags for application versioning. Product
6+
project files must not hardcode release versions with `<Version>` elements.
67

7-
### Version Properties in .csproj
8+
Canonical release tags use:
89

9-
The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `<Version>` property:
10+
- Stable: `vX.Y.Z`
11+
- Alpha: `vX.Y.Z-alpha.N`
1012

11-
```xml
12-
<Version>0.3.0</Version>
13-
```
14-
15-
Other version-related properties (`FileVersion` and `AssemblyVersion`) are **not** explicitly set in the csproj files. This is intentional.
13+
`GitVersion.yml` controls how tag history becomes SemVer. The product build
14+
imports GitVersion through `src\Directory.Build.props`, so normal `dotnet build`,
15+
`.\build.ps1`, `.\run-app-local.ps1`, and CI builds all derive assembly metadata
16+
from the same tag history.
1617

17-
### Automatic Version Derivation
18+
## Assembly metadata
1819

19-
When only `<Version>` is set in a .NET project:
20-
- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0``0.3.0.0`)
21-
- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0``0.3.0.0`)
22-
- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.3.0-beta.1`)
20+
GitVersion-derived builds set:
2321

24-
This ensures all version properties stay in sync automatically.
22+
- `AssemblyVersion` and `FileVersion` to numeric versions Windows/.NET can
23+
compare.
24+
- `AssemblyInformationalVersion` to the SemVer identity used by user-visible
25+
surfaces.
2526

26-
### CI Build Process
27+
`OpenClaw.Shared.AppVersionInfo` reads `AssemblyInformationalVersionAttribute`
28+
from the tray assembly and exposes:
2729

28-
During CI builds (`.github/workflows/ci.yml`), GitVersion determines the semantic version from git history and passes it to the build:
29-
30-
```bash
31-
dotnet build -p:Version=${{ needs.test.outputs.semVer }}
32-
```
30+
- `AppVersionInfo.Version` -> bare SemVer, for example `1.2.3-alpha.4`
31+
- `AppVersionInfo.DisplayVersion` -> `v`-prefixed SemVer, for example
32+
`v1.2.3-alpha.4`
3333

34-
This `-p:Version=...` argument overrides the `<Version>` property in the csproj, and consequently also sets `FileVersion` and `AssemblyVersion` to match.
34+
Build metadata after `+` is stripped before display, but prerelease labels are
35+
preserved. That makes alpha builds identify themselves precisely in About,
36+
diagnostics, support context, `device.info`, MCP handshake metadata, and update
37+
diagnostics.
3538

36-
### Auto-Updater Version Detection
39+
## CI release flow
3740

38-
The Updatum auto-updater determines the current application version by reading the **AssemblyVersion** from the running executable using:
41+
The release workflow computes GitVersion in the `test` job for workflow outputs
42+
and artifact naming. Product builds themselves also use GitVersion-backed
43+
MSBuild metadata; CI should not pass a competing hardcoded `-p:Version=...`
44+
value that could hide drift.
3945

40-
```csharp
41-
Assembly.GetExecutingAssembly().GetName().Version
42-
```
46+
Release build jobs must check out full git history (`fetch-depth: 0`) so
47+
GitVersion can see tags.
4348

44-
This is why it's critical that `AssemblyVersion` (and `FileVersion`) match the semantic version - otherwise, the updater will get confused and keep offering the same update repeatedly.
49+
## Local scripts
4550

46-
## Historical Issue
51+
`scripts\Get-OpenClawVersion.ps1` uses the repository-local
52+
`.config\dotnet-tools.json` manifest and `GitVersion.Tool` to print the same
53+
GitVersion value local scripts need outside MSBuild.
4754

48-
Previously, the csproj files had hardcoded values:
55+
For example:
4956

50-
```xml
51-
<Version>0.3.0</Version>
52-
<FileVersion>0.2.0</FileVersion>
53-
<AssemblyVersion>0.2.0</AssemblyVersion>
57+
```powershell
58+
.\scripts\Get-OpenClawVersion.ps1 -Variable SemVer
59+
.\scripts\Get-OpenClawVersion.ps1 -Variable MajorMinorPatch
5460
```
5561

56-
This caused a version mismatch:
57-
- The semantic version was 0.3.0
58-
- But the file and assembly versions were stuck at 0.2.0
59-
- Updatum would read 0.2.0 from the running EXE
60-
- It would see 0.4.0 available on GitHub
61-
- It would offer to update from "0.2.0" to "0.4.0" even though the user was already on 0.3.0 or 0.4.0
62-
63-
## Solution
64-
65-
By removing the hardcoded `FileVersion` and `AssemblyVersion` properties, they now automatically derive from `Version`. When CI overrides `Version` via command-line, all three properties are set correctly and consistently.
66-
67-
## Best Practices
62+
`scripts\build-inno-local.ps1` uses that helper for Inno's `AppVersion` when
63+
`-Version` is not explicitly supplied.
6864

69-
1. **Never hardcode `FileVersion` or `AssemblyVersion` in the csproj** - let them auto-derive from `Version`
70-
2. **Let GitVersion and CI control the version** - the csproj's `<Version>` is just a fallback for local development builds
71-
3. **Test version detection** - after building, check the EXE properties to ensure FileVersion matches expectations
72-
4. **Use semantic versioning** - tags should follow `v{major}.{minor}.{patch}` format (e.g., `v0.4.0`)
73-
5. **Use `OpenClaw.Shared.AppVersionInfo` for any user-visible or wire-exposed version string** - never re-roll
74-
`typeof(...).Assembly.GetName().Version` or hardcode literals like `"v0.1.0"`. `AppVersionInfo` is the single
75-
source of truth driven by `<Version>`, used by the About page, Update dialog, support-context dump,
76-
`device.info` capability, MCP `serverVersion` handshake, and the update-check diagnostics.
65+
## Guardrails
7766

78-
## Runtime Version Resolution (AppVersionInfo)
79-
80-
`src/OpenClaw.Shared/AppVersionInfo.cs` exposes:
81-
82-
- `AppVersionInfo.Version` → bare string, e.g. `"0.4.7"`
83-
- `AppVersionInfo.DisplayVersion``"v"` prefix, e.g. `"v0.4.7"`
84-
85-
It resolves the version by:
86-
87-
1. Looking for the `OpenClaw.Tray.WinUI` assembly in the current `AppDomain` (so `dotnet test` and CLI siblings
88-
still report the tray's version rather than the testhost / dotnet host).
89-
2. Falling back to `Assembly.GetEntryAssembly()`, then to the Shared assembly.
90-
3. Reading `AssemblyInformationalVersionAttribute` (preferred) or `AssemblyVersion`.
91-
4. Stripping SourceLink build metadata (`+abc123`) **and** the SemVer pre-release suffix (`-beta.1`) so the
92-
displayed value matches what Updatum compares (Updatum reads the numeric `AssemblyVersion` only).
93-
94-
For tests that need a deterministic value regardless of host process, set the `internal` test hook:
95-
96-
```csharp
97-
AppVersionInfo.TestOverride = "9.9.9";
98-
```
67+
- Do not add `<Version>` release literals to product `.csproj` files.
68+
- Do not hardcode user-visible version strings like `vX.Y.Z` in active code or
69+
tests; use `AppVersionInfo`.
70+
- Keep release tags and `GitVersion.yml` as the versioning contract.
9971

10072
## References
10173

scripts/Get-OpenClawVersion.ps1

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<#
2+
.SYNOPSIS
3+
Prints the GitVersion-derived OpenClaw version.
4+
5+
.DESCRIPTION
6+
Uses the repository-local GitVersion.Tool manifest so local scripts and CI
7+
derive versions from the same GitVersion.yml/tag history as release builds.
8+
#>
9+
10+
[CmdletBinding()]
11+
param(
12+
[ValidateSet("SemVer", "MajorMinorPatch")]
13+
[string]$Variable = "SemVer",
14+
15+
[switch]$NoRestore
16+
)
17+
18+
Set-StrictMode -Version Latest
19+
$ErrorActionPreference = "Stop"
20+
21+
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
22+
Push-Location $repoRoot
23+
try {
24+
if (-not $NoRestore) {
25+
dotnet tool restore | Out-Host
26+
if ($LASTEXITCODE -ne 0) {
27+
throw "dotnet tool restore failed."
28+
}
29+
}
30+
31+
$gitVersionOutput = & dotnet tool run dotnet-gitversion -- /output json 2>&1
32+
if ($LASTEXITCODE -ne 0) {
33+
throw "GitVersion failed: $gitVersionOutput"
34+
}
35+
36+
$gitVersion = $gitVersionOutput | ConvertFrom-Json
37+
$value = $gitVersion.$Variable
38+
if ([string]::IsNullOrWhiteSpace($value)) {
39+
throw "GitVersion did not return '$Variable'."
40+
}
41+
42+
Write-Output $value
43+
}
44+
finally {
45+
Pop-Location
46+
}

scripts/build-inno-local.ps1

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,6 @@ function Resolve-InnoCompiler {
7373
throw "Inno Setup compiler (ISCC.exe) was not found. Install it, or rerun with -InstallInno."
7474
}
7575

76-
function Get-ProjectVersion {
77-
$projectPath = Join-Path $repoRoot "src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj"
78-
[xml]$project = Get-Content -LiteralPath $projectPath -Raw
79-
$project.Project.PropertyGroup |
80-
ForEach-Object { $_.Version } |
81-
Where-Object { $_ } |
82-
Select-Object -First 1
83-
}
84-
8576
function Get-RidForArch {
8677
param([string]$Architecture)
8778
if ($Architecture -eq "arm64") {
@@ -104,24 +95,36 @@ function Publish-ArchitecturePayload {
10495
Remove-Item -LiteralPath $publishDir, $setupPublishDir -Recurse -Force -ErrorAction SilentlyContinue
10596
New-Item -ItemType Directory -Path $publishDir | Out-Null
10697

107-
dotnet publish .\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj `
108-
-c $Configuration `
109-
-r $RuntimeIdentifier `
110-
--self-contained `
111-
-p:Version=$PublishVersion `
112-
-o $publishDir `
113-
-v:minimal
98+
$trayPublishArgs = @(
99+
".\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj",
100+
"-c", $Configuration,
101+
"-r", $RuntimeIdentifier,
102+
"--self-contained",
103+
"-o", $publishDir,
104+
"-v:minimal"
105+
)
106+
if ($PublishVersion) {
107+
$trayPublishArgs += "-p:Version=$PublishVersion"
108+
}
109+
110+
dotnet publish @trayPublishArgs
114111
if ($LASTEXITCODE -ne 0) {
115112
throw "Tray publish failed for $Architecture."
116113
}
117114

118-
dotnet publish .\src\OpenClaw.SetupEngine.UI\OpenClaw.SetupEngine.UI.csproj `
119-
-c $Configuration `
120-
-r $RuntimeIdentifier `
121-
--self-contained `
122-
-p:Version=$PublishVersion `
123-
-o $setupPublishDir `
124-
-v:minimal
115+
$setupPublishArgs = @(
116+
".\src\OpenClaw.SetupEngine.UI\OpenClaw.SetupEngine.UI.csproj",
117+
"-c", $Configuration,
118+
"-r", $RuntimeIdentifier,
119+
"--self-contained",
120+
"-o", $setupPublishDir,
121+
"-v:minimal"
122+
)
123+
if ($PublishVersion) {
124+
$setupPublishArgs += "-p:Version=$PublishVersion"
125+
}
126+
127+
dotnet publish @setupPublishArgs
125128
if ($LASTEXITCODE -ne 0) {
126129
throw "SetupEngine.UI publish failed for $Architecture."
127130
}
@@ -178,8 +181,11 @@ function Invoke-InnoCompiler {
178181
}
179182
}
180183

184+
$versionWasProvided = $PSBoundParameters.ContainsKey("Version")
185+
181186
if (-not $Version) {
182-
$Version = Get-ProjectVersion
187+
$versionScript = Join-Path $PSScriptRoot "Get-OpenClawVersion.ps1"
188+
$Version = & $versionScript -Variable SemVer
183189
}
184190

185191
if (-not $Version) {
@@ -198,7 +204,8 @@ Write-Host "No publish: $($NoPublish.IsPresent)"
198204
foreach ($architecture in $architectures) {
199205
$rid = Get-RidForArch $architecture
200206
if (-not $NoPublish) {
201-
Publish-ArchitecturePayload -Architecture $architecture -RuntimeIdentifier $rid -PublishVersion $Version
207+
$publishVersion = if ($versionWasProvided) { $Version } else { $null }
208+
Publish-ArchitecturePayload -Architecture $architecture -RuntimeIdentifier $rid -PublishVersion $publishVersion
202209
}
203210

204211
$payload = Assert-PayloadReady $architecture

src/Directory.Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212
<NuGetAuditMode>all</NuGetAuditMode>
1313
</PropertyGroup>
1414

15+
<ItemGroup>
16+
<PackageReference Include="GitVersion.MsBuild" Version="6.4.0" PrivateAssets="all" />
17+
</ItemGroup>
18+
1519
</Project>

0 commit comments

Comments
 (0)