From d4b12817afe1763abfb562e137a22666fc88901d Mon Sep 17 00:00:00 2001 From: James Maes Date: Sat, 4 Jul 2026 14:53:44 -0500 Subject: [PATCH] Build identity synced to releases: semver + git hash everywhere - Directory.Build.props owns the version: local builds are 0.1.0-dev, release builds get -p:Version= from CI (unchanged pipeline) - The SDK's built-in SourceLink stamps the commit as + metadata; new BuildVersion.Describe/Format renders "0.1.0-beta.6 (9ac6c2bde)" for releases and "0.1.0-dev ()" for local builds - About dialog and gclo --version share the formatter; the MSIX quad (which cannot carry prerelease or hash) is no longer consulted; the CLI no longer strips the hash and drops its hardcoded version - 10 formatter tests (126 total) Co-Authored-By: Claude Fable 5 --- Directory.Build.props | 10 ++++++ docs/CLI.md | 2 +- gclo.Cli/Program.cs | 17 +++------ gclo.Cli/gclo.Cli.csproj | 1 - gclo.Engine.Tests/BuildVersionTests.cs | 30 ++++++++++++++++ gclo.Engine/BuildVersion.cs | 49 ++++++++++++++++++++++++++ gclo/AboutDialog.xaml.cs | 28 ++++----------- 7 files changed, 100 insertions(+), 37 deletions(-) create mode 100644 gclo.Engine.Tests/BuildVersionTests.cs create mode 100644 gclo.Engine/BuildVersion.cs diff --git a/Directory.Build.props b/Directory.Build.props index f03de8c..9da913d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,4 +10,14 @@ true + + + 0.1.0 + dev + + diff --git a/docs/CLI.md b/docs/CLI.md index 666db8f..da5288f 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -234,5 +234,5 @@ done ```console $ gclo --version -0.1.0 +0.1.0-beta.6 (9ac6c2bde) ``` diff --git a/gclo.Cli/Program.cs b/gclo.Cli/Program.cs index fe55bc0..9a8c6e9 100644 --- a/gclo.Cli/Program.cs +++ b/gclo.Cli/Program.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using gclo.Engine; namespace gclo.Cli; @@ -102,16 +102,7 @@ internal static class Program } private static string Version() - { - Assembly assembly = typeof(Program).Assembly; - string version = - assembly.GetCustomAttribute()?.InformationalVersion - ?? assembly.GetName().Version?.ToString() - ?? "unknown"; - - // Strip the '+' suffix (e.g. a SourceLink commit hash); it is - // noise for a scripted version check. - int plus = version.IndexOf('+'); - return plus >= 0 ? version[..plus] : version; - } + // Shared with the GUI's About dialog: "0.1.0-beta.6 (9ac6c2b)" for + // releases, "0.1.0-dev ()" for local builds. + => BuildVersion.Describe(typeof(Program).Assembly); } diff --git a/gclo.Cli/gclo.Cli.csproj b/gclo.Cli/gclo.Cli.csproj index 31b9fc5..245e19d 100644 --- a/gclo.Cli/gclo.Cli.csproj +++ b/gclo.Cli/gclo.Cli.csproj @@ -8,7 +8,6 @@ gclo gclo.Cli true - 0.1.0 diff --git a/gclo.Engine.Tests/BuildVersionTests.cs b/gclo.Engine.Tests/BuildVersionTests.cs new file mode 100644 index 0000000..c6a846b --- /dev/null +++ b/gclo.Engine.Tests/BuildVersionTests.cs @@ -0,0 +1,30 @@ +using gclo.Engine; + +namespace gclo.Engine.Tests; + +/// Formatting of the version + git-hash build identity string. +public sealed class BuildVersionTests +{ + [Theory] + [InlineData(null, "unknown")] + [InlineData("", "unknown")] + [InlineData(" ", "unknown")] + [InlineData("1.2.3", "1.2.3")] + [InlineData("0.1.0-dev", "0.1.0-dev")] + [InlineData("0.1.0-beta.6+9ac6c2b1234567890abcdef", "0.1.0-beta.6 (9ac6c2b12)")] + [InlineData("1.2.3+abc", "1.2.3 (abc)")] + [InlineData("1.2.3+9ac6c2b1234567890.dirty", "1.2.3 (9ac6c2b12)")] + [InlineData("1.2.3+", "1.2.3")] + public void Format_ProducesReadableIdentity(string? informational, string expected) + { + Assert.Equal(expected, BuildVersion.Format(informational)); + } + + [Fact] + public void Describe_UsesThisAssembly_AndNeverThrows() + { + string described = BuildVersion.Describe(typeof(BuildVersionTests).Assembly); + + Assert.False(string.IsNullOrWhiteSpace(described)); + } +} diff --git a/gclo.Engine/BuildVersion.cs b/gclo.Engine/BuildVersion.cs new file mode 100644 index 0000000..71fb03f --- /dev/null +++ b/gclo.Engine/BuildVersion.cs @@ -0,0 +1,49 @@ +using System.Reflection; + +namespace gclo.Engine; + +/// +/// Human-readable build identity shared by the app's About dialog and the CLI's +/// version output: the semantic version plus the short git commit the build came +/// from, e.g. "0.1.0-beta.6 (9ac6c2b)" for releases or "0.1.0-dev (3a87f9e)" for +/// local builds. +/// +public static class BuildVersion +{ + private const int ShortHashLength = 9; + + /// Describes 's version and commit. + public static string Describe(Assembly assembly) + => Format(assembly.GetCustomAttribute()?.InformationalVersion + ?? assembly.GetName().Version?.ToString()); + + /// + /// Formats an informational version: the "+<metadata>" suffix that SourceLink + /// appends becomes a parenthesized short commit hash. + /// + public static string Format(string? informationalVersion) + { + if (string.IsNullOrWhiteSpace(informationalVersion)) + { + return "unknown"; + } + + int plus = informationalVersion.IndexOf('+'); + if (plus < 0) + { + return informationalVersion; + } + + string version = informationalVersion[..plus]; + string metadata = informationalVersion[(plus + 1)..]; + // Metadata may carry more than the sha (dot-separated); the hash is first. + int dot = metadata.IndexOf('.'); + string hash = dot >= 0 ? metadata[..dot] : metadata; + if (hash.Length > ShortHashLength) + { + hash = hash[..ShortHashLength]; + } + + return hash.Length == 0 ? version : $"{version} ({hash})"; + } +} diff --git a/gclo/AboutDialog.xaml.cs b/gclo/AboutDialog.xaml.cs index 08173c9..113c75c 100644 --- a/gclo/AboutDialog.xaml.cs +++ b/gclo/AboutDialog.xaml.cs @@ -18,29 +18,13 @@ namespace gclo } /// - /// Returns the app version formatted as "major.minor.build". Prefers the MSIX - /// package version; Package.Current throws when the app runs unpackaged - /// (no package identity), in which case the assembly version is used instead. + /// Returns the build identity synced to the release pipeline: the semantic + /// version CI injects for the tag (0.1.0-dev on local builds) plus the short + /// git commit, e.g. "0.1.0-beta.6 (9ac6c2b)". The MSIX package version is + /// deliberately not used — its numeric quad cannot carry prerelease tags or + /// commit hashes, so it cannot distinguish a local build from a release. /// private static string GetAppVersion() - { - try - { - var packageVersion = Windows.ApplicationModel.Package.Current.Id.Version; - return $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}"; - } - catch (Exception) - { - Version? assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version; - if (assemblyVersion is null) - { - return "1.0.0"; - } - - // Version.Build is -1 when the assembly version has fewer than three parts. - int build = assemblyVersion.Build < 0 ? 0 : assemblyVersion.Build; - return $"{assemblyVersion.Major}.{assemblyVersion.Minor}.{build}"; - } - } + => gclo.Engine.BuildVersion.Describe(Assembly.GetExecutingAssembly()); } } -- 2.54.0