diff --git a/docs/design/versioning.md b/docs/design/versioning.md index 76d57b39..4fb611af 100644 --- a/docs/design/versioning.md +++ b/docs/design/versioning.md @@ -23,7 +23,7 @@ Do not set `Version`, `FileVersion` or `InformationalVersion` in `Bravo.csproj`: | `AssemblyInformationalVersion` | `X.Y.Z.{height}[-tag]+{commit}` | diagnostics | | `AssemblyVersion` | `X.Y.0.0` | assembly identity (`assemblyVersion.precision: minor`) | | `NBGV_SimpleVersion` | `X.Y.Z` | WiX `-dVersion` | -| `NBGV_SemVer2` | `X.Y.Z[-tag]` | artifact names, git tag | +| `NBGV_SemVer2` | `X.Y.Z[-tag]` | artifact names, git tag, `AppVersion.SemanticVersion` | `{height}` is the number of commits since the numeric `X.Y.Z` last changed. It is a build counter: it makes every build uniquely identifiable and orders builds that share the same `X.Y.Z`. @@ -33,6 +33,12 @@ every build uniquely identifiable and orders builds that share the same `X.Y.Z`. - **The prerelease tag never reaches a numeric field.** `AssemblyFileVersion` and `NBGV_SimpleVersion` stay numeric in every state, so Windows Installer and `System.Version` keep working unchanged. +- **`SemVer2` is the version that the application and the artifacts report.** The +application reads it as `ThisAssembly.NuGetPackageVersion`, which equals `SemVer2` only while +`nuGetPackageVersion.semVer` is `2` in `version.json` and `NBGV_ThisAssemblyIncludesPackageVersion` is set in +`Bravo.csproj`. Without the first, the application reports the SemVer1 form `X.Y.Z-tag-0001-g{commit}`; without +the second, it does not compile. A test in `AppVersionTests` checks the shape. + - **The height resets only when the numeric `X.Y.Z` changes.** Adding, changing or removing the prerelease tag does not reset it. `AssemblyFileVersion` is therefore monotonic across `1.1.0-beta.1 → 1.1.0-beta.2 → 1.1.0`, which is what makes a preview and its final release — identical on `X.Y.Z` — orderable. @@ -43,12 +49,17 @@ to its final release changes `version.json` alone: such a filter would give the `AssemblyFileVersion`. - **`publicReleaseRefSpec` lists the branches that produce clean versions.** Outside them, `SemVer2` and -`NuGetPackageVersion` carry a `.g{commit}` suffix; numeric fields are unaffected. A tag checkout runs in +`NuGetPackageVersion` carry the commit id: `X.Y.Z-tag.g{commit}`, or `X.Y.Z-g{commit}` without a prerelease +tag; numeric fields and `AssemblyInformationalVersion` are unaffected. A tag checkout runs in detached HEAD and matches no branch pattern, so building from a tag requires adding the tag pattern. - **Artifact names come from `SemVer2`, not from `SimpleVersion`.** The two are identical for a release and differ only for a preview, where `SimpleVersion` drops the tag: naming artifacts from it would give a preview and the release that follows it the same file names. +- **NBGV reads `version.json` from `HEAD`, not from the working copy.** An edit takes effect from the commit +that contains it: a build or a test run on a dirty `version.json` still reflects the committed values, and only +the height resets. + - **The height needs full history.** Shallow clones make NBGV fail or compute a wrong number, so both pipelines check out with unlimited depth. diff --git a/src/Bravo.csproj b/src/Bravo.csproj index db2588e4..58229c26 100644 --- a/src/Bravo.csproj +++ b/src/Bravo.csproj @@ -24,6 +24,7 @@ en-US PerMonitorV2 true + true diff --git a/src/Infrastructure/AppVersion.cs b/src/Infrastructure/AppVersion.cs index e2de30bc..af44f33c 100644 --- a/src/Infrastructure/AppVersion.cs +++ b/src/Infrastructure/AppVersion.cs @@ -1,56 +1,27 @@ namespace Sqlbi.Bravo.Infrastructure; /// -/// Application version, stamped from version.json by Nerdbank.GitVersioning. +/// Provides application version information generated by Nerdbank.GitVersioning from version.json. /// internal static class AppVersion { - static AppVersion() - { - IsPrerelease = ThisAssembly.IsPrerelease; - IsPublicRelease = ThisAssembly.IsPublicRelease; - FileVersion = ThisAssembly.AssemblyFileVersion; - InformationalVersion = ThisAssembly.AssemblyInformationalVersion; - SemanticVersion = System.Version.Parse(FileVersion).ToString(3) + GetPrereleaseTag(InformationalVersion); - } - - /// - /// True if the build is a prerelease, false if it is a release. - /// - public static bool IsPrerelease { get; } - - /// - /// True if the build is a public release, false if it is a internal build (e.g. CI build). - /// - public static bool IsPublicRelease { get; } - /// - /// Four-part assembly file version Major.Minor.Patch.Height, where Height - /// is the version height used to distinguish builds of the same release. + /// Four-part file version in the form Major.Minor.Patch.Height, where Height + /// distinguishes builds of the same version. /// - public static string FileVersion { get; } + public static string FileVersion { get; } = ThisAssembly.AssemblyFileVersion; /// - /// Semantic version of the application, including the prerelease label when present - /// and excluding build metadata. e.g. 1.1.0-beta.1 or 1.1.0. + /// Semantic version of the application, including the prerelease label when present and + /// excluding build metadata, for example 1.1.0-beta.1 or 1.1.0. + /// Internal builds include the Git commit identifier, for example + /// 1.1.0-beta.1.g1c52e441d1, or 1.1.0-g1c52e441d1 without a prerelease label. /// - public static string SemanticVersion { get; } + public static string SemanticVersion { get; } = ThisAssembly.NuGetPackageVersion; /// - /// with the prerelease tag, if any, and the git commit id: - /// 1.1.0.14-beta.1+1c52e441d1. + /// Informational version of the application, including the file version, prerelease label + /// when present, and Git commit identifier, for example 1.1.0.14-beta.1+1c52e441d1. /// - public static string InformationalVersion { get; } - - internal static string GetPrereleaseTag(string informationalVersion) - { - var value = informationalVersion; - - var metadataIndex = value.IndexOf('+'); - if (metadataIndex >= 0) - value = value[..metadataIndex]; - - var prereleaseIndex = value.IndexOf('-'); - return prereleaseIndex < 0 ? string.Empty : value[prereleaseIndex..]; - } + public static string InformationalVersion { get; } = ThisAssembly.AssemblyInformationalVersion; } diff --git a/src/Infrastructure/AppWindow.cs b/src/Infrastructure/AppWindow.cs index c7c86f5f..b345f86e 100644 --- a/src/Infrastructure/AppWindow.cs +++ b/src/Infrastructure/AppWindow.cs @@ -320,7 +320,6 @@ private MemoryStream GetConfigJs() address = _serverAddressProvider.GetListeningAddress(), token = AppEnvironment.ApiAuthenticationToken, version = AppVersion.SemanticVersion, - informationalVersion = AppVersion.InformationalVersion, options = BravoOptions.CreateFromUserPreferences(), policies = _policies, culture = new diff --git a/src/Infrastructure/Extensions/StringExtensions.cs b/src/Infrastructure/Extensions/StringExtensions.cs index 0e475838..05cfc51b 100644 --- a/src/Infrastructure/Extensions/StringExtensions.cs +++ b/src/Infrastructure/Extensions/StringExtensions.cs @@ -16,14 +16,7 @@ internal static class StringExtensions public static string AppendApplicationVersion(this string value) { - var result = $"{value} - v{AppVersion.SemanticVersion}"; - - if (AppVersion.IsPrerelease || !AppVersion.IsPublicRelease) - { - result += $" ({AppVersion.InformationalVersion})"; - } - - return result; + return $"{value} - v{AppVersion.SemanticVersion}"; } /// diff --git a/src/Scripts/@types/global.d.ts b/src/Scripts/@types/global.d.ts index cf426624..d3749134 100644 --- a/src/Scripts/@types/global.d.ts +++ b/src/Scripts/@types/global.d.ts @@ -7,7 +7,6 @@ declare global { debug?: boolean, address: string version: string, - informationalVersion: string, options: Options, policies?: Policies, token?: string, diff --git a/src/Scripts/controllers/app.ts b/src/Scripts/controllers/app.ts index 14a054ff..d311a31b 100644 --- a/src/Scripts/controllers/app.ts +++ b/src/Scripts/controllers/app.ts @@ -30,7 +30,6 @@ import { DialogResponse } from '../view/dialog'; export interface AppVersionInfo { version: string - informationalVersion?: string downloadUrl?: string changelogUrl?: string } diff --git a/src/Scripts/controllers/debug.ts b/src/Scripts/controllers/debug.ts index 99ae05ca..b88fa0ce 100644 --- a/src/Scripts/controllers/debug.ts +++ b/src/Scripts/controllers/debug.ts @@ -38,7 +38,6 @@ export class Debug { debug: true, address: "http://localhost", version: "0.0.0-debug", - informationalVersion: "0.0.0.0-debug+0000000000", options: null, token: "", culture: { diff --git a/src/Scripts/main.ts b/src/Scripts/main.ts index f782b547..6830bb70 100644 --- a/src/Scripts/main.ts +++ b/src/Scripts/main.ts @@ -31,8 +31,7 @@ let pbiDesktop = new PBIDesktop(); let notificationCenter = new NotifyCenter(); let app = new App(new AppVersion({ - version: CONFIG.version, - informationalVersion: CONFIG.informationalVersion + version: CONFIG.version })); export { debug, host, optionsController, themeController, auth, telemetry, pbiDesktop, notificationCenter, logger, app }; \ No newline at end of file diff --git a/src/Scripts/view/options-dialog-about.ts b/src/Scripts/view/options-dialog-about.ts index 989704e5..6ae5ccba 100644 --- a/src/Scripts/view/options-dialog-about.ts +++ b/src/Scripts/view/options-dialog-about.ts @@ -83,7 +83,7 @@ export class OptionsDialogAbout { _(".copy-version", element).addEventListener("click", e => { e.preventDefault(); - navigator.clipboard.writeText(app.currentVersion.info.informationalVersion ?? app.currentVersion.toString()); + navigator.clipboard.writeText(app.currentVersion.toString()); }); _(".auto-check-option input", element).addEventListener("change", e => { diff --git a/test/Bravo.Tests/Infrastructure/AppVersionTests.cs b/test/Bravo.Tests/Infrastructure/AppVersionTests.cs index b6f77063..daa252db 100644 --- a/test/Bravo.Tests/Infrastructure/AppVersionTests.cs +++ b/test/Bravo.Tests/Infrastructure/AppVersionTests.cs @@ -5,19 +5,6 @@ namespace Bravo.Tests.Infrastructure; public class AppVersionTests { - [Theory] - [InlineData("1.1.0.11+ed89094be9", "")] - [InlineData("1.1.0.13-beta.1+57e453666e", "-beta.1")] - [InlineData("1.1.0.13-beta.1", "-beta.1")] - [InlineData("1.1.0.0-build.99+74e5ed577e", "-build.99")] - [InlineData("1.1.0.13", "")] - public void GetPrereleaseTag_ReadsTheTag(string informationalVersion, string expected) - { - var actual = AppVersion.GetPrereleaseTag(informationalVersion); - - Assert.Equal(expected, actual); - } - [Fact] public void FileVersion_IsNumericWithFourFields() { @@ -37,6 +24,36 @@ public void SemanticVersion_StartsWithTheNumericReleaseVersion() Assert.StartsWith(releaseVersion, AppVersion.SemanticVersion); } + [Fact] + public void SemanticVersion_HasNoBuildMetadata() + { + Assert.DoesNotContain("+", AppVersion.SemanticVersion); + } + + [Fact] + public void SemanticVersion_CarriesTheCommitIdOnlyOutsideAPublicRelease() + { + // SemanticVersion must be NBGV SemVer2, the value used for artifact names and installer telemetry: outside + // publicReleaseRefSpec it ends with 'g{commit}', where the commit is the build metadata of the informational + // version. + var metadataIndex = AppVersion.InformationalVersion.IndexOf('+'); + Assert.True(metadataIndex >= 0, $"'{AppVersion.InformationalVersion}' has no build metadata."); + + var commitId = AppVersion.InformationalVersion[(metadataIndex + 1)..]; + var isPublicRelease = ThisAssembly.IsPublicRelease; + var isPrerelease = ThisAssembly.IsPrerelease; + + if (isPublicRelease) + { + Assert.DoesNotContain(commitId, AppVersion.SemanticVersion); + } + else + { + var separator = isPrerelease ? '.' : '-'; + Assert.EndsWith($"{separator}g{commitId}", AppVersion.SemanticVersion); + } + } + [Fact] public void InformationalVersion_StartsWithFileVersion() { diff --git a/test/Bravo.Tests/Infrastructure/Extensions/StringExtensionsTests.cs b/test/Bravo.Tests/Infrastructure/Extensions/StringExtensionsTests.cs index 77580f6c..e636c999 100644 --- a/test/Bravo.Tests/Infrastructure/Extensions/StringExtensionsTests.cs +++ b/test/Bravo.Tests/Infrastructure/Extensions/StringExtensionsTests.cs @@ -64,8 +64,7 @@ public void AppendApplicationVersion_Test() { var actual = "Bravo for Power BI".AppendApplicationVersion(); - Assert.NotNull(actual); - Assert.StartsWith("Bravo for Power BI", actual); + Assert.Equal($"Bravo for Power BI - v{AppVersion.SemanticVersion}", actual); } [Fact] diff --git a/version.json b/version.json index 5dc29a5e..052767bd 100644 --- a/version.json +++ b/version.json @@ -4,6 +4,10 @@ "assemblyVersion": { "precision": "minor" }, + "nuGetPackageVersion": { + "semVer": 2, + "precision": "build" + }, "pathFilters": [ ":/" ],