From df0750d4e7f458de6d2ada2f9debc4a92c11b420 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 20 Apr 2017 13:43:44 -0700 Subject: [PATCH 1/4] Define BuildNumberFromVersionJson msbuild property This allows folks who want to do particularly custom things with their versioning stamps to have a bit more data from the version.json file with which to work. Fixes #115 --- .../BuildIntegrationTests.cs | 17 +++++++++++++++++ src/NerdBank.GitVersioning/VersionOracle.cs | 6 ++++++ .../build/Nerdbank.GitVersioning.targets | 1 + .../GetBuildVersion.cs | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs index c99fbd0f..73bc6a0e 100644 --- a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs +++ b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs @@ -381,6 +381,21 @@ public async Task GetBuildVersion_Minus1BuildOffset_NotYetCommitted() this.AssertStandardProperties(versionOptions, buildResult); } + [Theory] + [InlineData(0)] + [InlineData(21)] + public async Task GetBuildVersion_BuildNumberSpecifiedInVersionJson(int buildNumber) + { + var versionOptions = new VersionOptions + { + Version = SemanticVersion.Parse("14.0." + buildNumber), + }; + this.WriteVersionFile(versionOptions); + this.InitializeSourceControl(); + var buildResult = await this.BuildAsync(); + this.AssertStandardProperties(versionOptions, buildResult); + } + [Fact] public async Task PublicRelease_RegEx_Unsatisfied() { @@ -821,6 +836,7 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult Assert.Equal($"{version}", buildResult.BuildVersion); Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components); Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent); + Assert.Equal(versionOptions.Version.Version.Build != -1 ? versionOptions.Version.Version.Build.ToString() : string.Empty, buildResult.BuildNumberFromVersionJson); Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersionSimple); Assert.Equal(this.Repo.Head.Commits.First().Id.Sha, buildResult.GitCommitId); Assert.Equal(commitIdShort, buildResult.GitCommitIdShort); @@ -980,6 +996,7 @@ internal BuildResults(BuildResult buildResult, IReadOnlyList log public string PrereleaseVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("PrereleaseVersion"); public string MajorMinorVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("MajorMinorVersion"); public string BuildVersionNumberComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildVersionNumberComponent"); + public string BuildNumberFromVersionJson => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberFromVersionJson"); public string GitCommitIdShort => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitCommitIdShort"); public string GitVersionHeight => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitVersionHeight"); public string SemVerBuildSuffix => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("SemVerBuildSuffix"); diff --git a/src/NerdBank.GitVersioning/VersionOracle.cs b/src/NerdBank.GitVersioning/VersionOracle.cs index c8bc7bca..56d52789 100644 --- a/src/NerdBank.GitVersioning/VersionOracle.cs +++ b/src/NerdBank.GitVersioning/VersionOracle.cs @@ -159,6 +159,12 @@ public IEnumerable BuildMetadataWithCommitId /// public int BuildNumber => Math.Max(0, this.Version.Build); + /// + /// Gets the build number as it was specified in the version.json file. + /// + /// The version specified, or -1 if none was specified. + public int BuildNumberFromVersionOptions => this.VersionOptions?.Version?.Version?.Build ?? -1; + /// /// Gets or sets the major.minor version string. /// diff --git a/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets b/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets index e502e37d..bd88f045 100644 --- a/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets +++ b/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets @@ -69,6 +69,7 @@ + diff --git a/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs b/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs index a0c386df..a0adc782 100644 --- a/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs +++ b/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Linq; using Microsoft.Build.Framework; @@ -139,6 +140,12 @@ public GetBuildVersion() [Output] public int BuildNumber { get; private set; } + /// + /// Gets the build number as specified in the version.json file. + /// + [Output] + public string BuildNumberFromVersionJson { get; private set; } + /// /// Gets the BuildNumber to set the cloud build to (if applicable). /// @@ -174,6 +181,7 @@ protected override bool ExecuteInner() this.SimpleVersion = oracle.SimpleVersion.ToString(); this.MajorMinorVersion = oracle.MajorMinorVersion.ToString(); this.BuildNumber = oracle.BuildNumber; + this.BuildNumberFromVersionJson = oracle.BuildNumberFromVersionOptions != -1 ? oracle.BuildNumberFromVersionOptions.ToString(CultureInfo.InvariantCulture) : string.Empty; this.PrereleaseVersion = oracle.PrereleaseVersion; this.GitCommitId = oracle.GitCommitId; this.GitCommitIdShort = oracle.GitCommitIdShort; From 68405a7945b6fb0803ebd7286ba3003d167f0e0a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sun, 28 May 2017 15:39:28 -0700 Subject: [PATCH 2/4] Provide greater flexibility in where git height appears in version info --- .../BuildIntegrationTests.cs | 15 +++- .../GitExtensionsTests.cs | 19 ++++- .../NerdBank.GitVersioning.Tests.csproj | 2 + .../VersionOracleTests.cs | 84 +++++++++++++++++++ .../VersionSchemaTests.cs | 73 ++++++++++++++++ src/NerdBank.GitVersioning/GitExtensions.cs | 46 ++++++---- src/NerdBank.GitVersioning/SemanticVersion.cs | 21 ++++- src/NerdBank.GitVersioning/VersionOracle.cs | 30 +++++-- .../version.schema.json | 4 +- .../build/Nerdbank.GitVersioning.targets | 1 - .../GetBuildVersion.cs | 7 -- 11 files changed, 262 insertions(+), 40 deletions(-) create mode 100644 src/NerdBank.GitVersioning.Tests/VersionSchemaTests.cs diff --git a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs index 73bc6a0e..c4597996 100644 --- a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs +++ b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs @@ -98,6 +98,19 @@ public async Task GetBuildVersion_Without_Git() Assert.Equal("3.4.0", buildResult.AssemblyInformationalVersion); } + [Fact] + public async Task GetBuildVersion_WithThreeVersionIntegers() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8.9-beta.3"), + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var buildResult = await this.BuildAsync(); + this.AssertStandardProperties(workingCopyVersion, buildResult); + } + [Fact] public async Task GetBuildVersion_Without_Git_HighPrecisionAssemblyVersion() { @@ -836,7 +849,6 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult Assert.Equal($"{version}", buildResult.BuildVersion); Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components); Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent); - Assert.Equal(versionOptions.Version.Version.Build != -1 ? versionOptions.Version.Version.Build.ToString() : string.Empty, buildResult.BuildNumberFromVersionJson); Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersionSimple); Assert.Equal(this.Repo.Head.Commits.First().Id.Sha, buildResult.GitCommitId); Assert.Equal(commitIdShort, buildResult.GitCommitIdShort); @@ -996,7 +1008,6 @@ internal BuildResults(BuildResult buildResult, IReadOnlyList log public string PrereleaseVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("PrereleaseVersion"); public string MajorMinorVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("MajorMinorVersion"); public string BuildVersionNumberComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildVersionNumberComponent"); - public string BuildNumberFromVersionJson => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberFromVersionJson"); public string GitCommitIdShort => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitCommitIdShort"); public string GitVersionHeight => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitVersionHeight"); public string SemVerBuildSuffix => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("SemVerBuildSuffix"); diff --git a/src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs b/src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs index a5bf0eef..b13f66ae 100644 --- a/src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs +++ b/src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs @@ -172,7 +172,7 @@ public void GetIdAsVersion_MissingVersionTxt() } [Fact] - public void GetIdAsVersion_VersionFileNeverCheckedIn() + public void GetIdAsVersion_VersionFileNeverCheckedIn_3Ints() { this.AddCommits(); var expectedVersion = new Version(1, 1, 0); @@ -182,6 +182,23 @@ public void GetIdAsVersion_VersionFileNeverCheckedIn() Assert.Equal(expectedVersion.Major, actualVersion.Major); Assert.Equal(expectedVersion.Minor, actualVersion.Minor); Assert.Equal(expectedVersion.Build, actualVersion.Build); + + // Height is expressed in the 4th integer since 3 were specified in version.json. + // height is 0 since the change hasn't been committed. + Assert.Equal(0, actualVersion.Revision); + } + + [Fact] + public void GetIdAsVersion_VersionFileNeverCheckedIn_2Ints() + { + this.AddCommits(); + var expectedVersion = new Version(1, 1); + var unstagedVersionData = VersionOptions.FromVersion(expectedVersion); + string versionFilePath = VersionFile.SetVersion(this.RepoPath, unstagedVersionData); + Version actualVersion = this.Repo.GetIdAsVersion(); + Assert.Equal(expectedVersion.Major, actualVersion.Major); + Assert.Equal(expectedVersion.Minor, actualVersion.Minor); + Assert.Equal(0, actualVersion.Build); // height is 0 since the change hasn't been committed. Assert.Equal(this.Repo.Head.Commits.First().GetTruncatedCommitIdAsUInt16(), actualVersion.Revision); } diff --git a/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj b/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj index 97a8921b..cc792177 100644 --- a/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj +++ b/src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj @@ -9,6 +9,7 @@ + @@ -18,6 +19,7 @@ + diff --git a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs index 0869c2b1..a0947183 100644 --- a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs +++ b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs @@ -28,4 +28,88 @@ public void Submodule_RecognizedWithCorrectVersion() Assert.Equal("3ea7f010c3", oracleB.GitCommitIdShort); } } + + [Fact] + public void MajorMinorPrereleaseBuildMetadata() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8-beta.3+metadata.4"), + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + Assert.Equal("7.8", oracle.MajorMinorVersion.ToString()); + Assert.Equal(oracle.VersionHeight, oracle.BuildNumber); + + Assert.Equal("-beta.3", oracle.PrereleaseVersion); + ////Assert.Equal("+metadata.4", oracle.BuildMetadataFragment); + + Assert.Equal(1, oracle.VersionHeight); + Assert.Equal(0, oracle.VersionHeightOffset); + } + + [Fact] + public void MajorMinorBuildPrereleaseBuildMetadata() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8.9-beta.3+metadata.4"), + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + Assert.Equal("7.8", oracle.MajorMinorVersion.ToString()); + Assert.Equal(9, oracle.BuildNumber); + Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision); + + Assert.Equal("-beta.3", oracle.PrereleaseVersion); + ////Assert.Equal("+metadata.4", oracle.BuildMetadataFragment); + + Assert.Equal(1, oracle.VersionHeight); + Assert.Equal(0, oracle.VersionHeightOffset); + } + + [Fact] + public void HeightInPrerelease() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8.9-beta.{height}.foo"), + BuildNumberOffset = 2, + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + Assert.Equal("7.8", oracle.MajorMinorVersion.ToString()); + Assert.Equal(9, oracle.BuildNumber); + Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision); + + Assert.Equal("-beta." + (oracle.VersionHeight + oracle.VersionHeightOffset) + ".foo", oracle.PrereleaseVersion); + + Assert.Equal(1, oracle.VersionHeight); + Assert.Equal(2, oracle.VersionHeightOffset); + } + + [Fact(Skip = "Build metadata not yet retained from version.json")] + public void HeightInBuildMetadata() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8.9-beta+another.{height}.foo"), + BuildNumberOffset = 2, + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + Assert.Equal("7.8", oracle.MajorMinorVersion.ToString()); + Assert.Equal(9, oracle.BuildNumber); + Assert.Equal(oracle.VersionHeight + oracle.VersionHeightOffset, oracle.Version.Revision); + + Assert.Equal("-beta", oracle.PrereleaseVersion); + Assert.Equal("+another." + (oracle.VersionHeight + oracle.VersionHeightOffset) + ".foo", oracle.BuildMetadataFragment); + + Assert.Equal(1, oracle.VersionHeight); + Assert.Equal(2, oracle.VersionHeightOffset); + } } diff --git a/src/NerdBank.GitVersioning.Tests/VersionSchemaTests.cs b/src/NerdBank.GitVersioning.Tests/VersionSchemaTests.cs new file mode 100644 index 00000000..82233a01 --- /dev/null +++ b/src/NerdBank.GitVersioning.Tests/VersionSchemaTests.cs @@ -0,0 +1,73 @@ +using System.IO; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using Xunit; +using Xunit.Abstractions; + +public class VersionSchemaTests +{ + private readonly ITestOutputHelper Logger; + + private readonly JSchema schema; + + private JObject json; + + public VersionSchemaTests(ITestOutputHelper logger) + { + this.Logger = logger; + using (var schemaStream = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.version.schema.json"))) + { + this.schema = JSchema.Load(new JsonTextReader(schemaStream)); + } + } + + [Fact] + public void VersionField_BasicScenarios() + { + json = JObject.Parse(@"{ ""version"": ""2.3"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3-beta"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3-beta-final"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3-beta.2"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3-beta.0"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3-beta.01"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""1.2.3"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""1.2.3.4"" }"); + Assert.True(json.IsValid(this.schema)); + + json = JObject.Parse(@"{ ""version"": ""02.3"" }"); + Assert.False(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.03"" }"); + Assert.False(json.IsValid(this.schema)); + } + + [Fact] + public void VersionField_HeightMacroPlacement() + { + // Valid uses + json = JObject.Parse(@"{ ""version"": ""2.3.0-{height}"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3.0-{height}.beta"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3.0-beta.{height}"" }"); + Assert.True(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3.0-beta+{height}"" }"); + Assert.True(json.IsValid(this.schema)); + + // Invalid uses + json = JObject.Parse(@"{ ""version"": ""2.3.{height}-beta"" }"); + Assert.False(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3.0-beta-{height}"" }"); + Assert.False(json.IsValid(this.schema)); + json = JObject.Parse(@"{ ""version"": ""2.3.0-beta+height-{height}"" }"); + Assert.False(json.IsValid(this.schema)); + } +} diff --git a/src/NerdBank.GitVersioning/GitExtensions.cs b/src/NerdBank.GitVersioning/GitExtensions.cs index 4fee2ac7..3b098a60 100644 --- a/src/NerdBank.GitVersioning/GitExtensions.cs +++ b/src/NerdBank.GitVersioning/GitExtensions.cs @@ -512,26 +512,40 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory)) - : 0; - } + // The compiler (due to WinPE header requirements) only allows 16-bit version components, + // and forbids 0xffff as a value. + // The build number is set to the git height. This helps ensure that + // within a major.minor release, each patch has an incrementing integer. + // The revision is set to the first two bytes of the git commit ID. + if (!versionHeight.HasValue) + { + versionHeight = commit != null + ? commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory)) + : 0; + } - int build = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0); - Verify.Operation(build <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", build, MaximumBuildNumberOrRevisionComponent); - int revision = commit != null - ? Math.Min(MaximumBuildNumberOrRevisionComponent, commit.GetTruncatedCommitIdAsUInt16()) - : 0; + int adjustedVersionHeight = versionHeight.Value == 0 ? 0 : versionHeight.Value + (versionOptions?.BuildNumberOffset ?? 0); + Verify.Operation(adjustedVersionHeight <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", adjustedVersionHeight, MaximumBuildNumberOrRevisionComponent); + + if (buildNumber < 0) + { + buildNumber = adjustedVersionHeight; + revision = commit != null + ? Math.Min(MaximumBuildNumberOrRevisionComponent, commit.GetTruncatedCommitIdAsUInt16()) + : 0; + } + else + { + revision = adjustedVersionHeight; + } + } - return new Version(baseVersion.Major, baseVersion.Minor, build, revision); + return new Version(baseVersion.Major, baseVersion.Minor, buildNumber, revision); } /// diff --git a/src/NerdBank.GitVersioning/SemanticVersion.cs b/src/NerdBank.GitVersioning/SemanticVersion.cs index 41d7161e..55325851 100644 --- a/src/NerdBank.GitVersioning/SemanticVersion.cs +++ b/src/NerdBank.GitVersioning/SemanticVersion.cs @@ -23,12 +23,27 @@ public class SemanticVersion : IEquatable /// /// The regex pattern that a prerelease must match. /// - private static readonly Regex PrereleasePattern = new Regex(@"^-(?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*$"); + /// + /// Keep in sync with the regex for the version field found in the version.schema.json file. + /// + private static readonly Regex PrereleasePattern = new Regex("-(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*", RegexOptions.IgnoreCase); /// /// The regex pattern that build metadata must match. /// - private static readonly Regex BuildMetadataPattern = new Regex(@"^\+(?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*$"); + /// + /// Keep in sync with the regex for the version field found in the version.schema.json file. + /// + private static readonly Regex BuildMetadataPattern = new Regex("\\+(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*", RegexOptions.IgnoreCase); + + /// + /// The regular expression with capture groups for semantic versioning, + /// allowing for macros such as {height}. + /// + /// + /// Keep in sync with the regex for the version field found in the version.schema.json file. + /// + private static readonly Regex FullSemVerWithMacrosPattern = new Regex("^v?(?0|[1-9][0-9]*)\\.(?0|[1-9][0-9]*)(?:\\.(?0|[1-9][0-9]*)(?:\\.(?0|[1-9][0-9]*))?)?(?" + PrereleasePattern + ")?(?" + BuildMetadataPattern + ")?$", RegexOptions.IgnoreCase); /// /// Initializes a new instance of the class. @@ -90,7 +105,7 @@ public static bool TryParse(string semanticVersion, out SemanticVersion version) { Requires.NotNullOrEmpty(semanticVersion, nameof(semanticVersion)); - Match m = FullSemVerPattern.Match(semanticVersion); + Match m = FullSemVerWithMacrosPattern.Match(semanticVersion); if (m.Success) { var major = int.Parse(m.Groups["major"].Value); diff --git a/src/NerdBank.GitVersioning/VersionOracle.cs b/src/NerdBank.GitVersioning/VersionOracle.cs index 56d52789..6073e997 100644 --- a/src/NerdBank.GitVersioning/VersionOracle.cs +++ b/src/NerdBank.GitVersioning/VersionOracle.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -63,6 +64,9 @@ public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, IClo // Override the typedVersion with the special build number and revision components, when available. this.Version = repo?.GetIdAsVersion(relativeRepoProjectDirectory, this.VersionHeight) ?? this.VersionOptions?.Version.Version; this.Version = this.Version ?? new Version(0, 0); + this.VersionHeightOffset = this.VersionOptions?.BuildNumberOffset ?? 0; + + this.PrereleaseVersion = ReplaceMacros(this.VersionOptions?.Version.Prerelease ?? string.Empty); this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumber ?? new VersionOptions.CloudBuildNumberOptions(); @@ -145,7 +149,7 @@ public IEnumerable BuildMetadataWithCommitId /// /// Gets the prerelease version information. /// - public string PrereleaseVersion => this.VersionOptions?.Version.Prerelease ?? string.Empty; + public string PrereleaseVersion { get; } /// /// Gets the version information without a Revision component. @@ -155,16 +159,10 @@ public IEnumerable BuildMetadataWithCommitId : new Version(this.Version.Major, this.Version.Minor); /// - /// Gets the build number (git height + offset) for this version. + /// Gets the build number (i.e. third integer, or PATCH) for this version. /// public int BuildNumber => Math.Max(0, this.Version.Build); - /// - /// Gets the build number as it was specified in the version.json file. - /// - /// The version specified, or -1 if none was specified. - public int BuildNumberFromVersionOptions => this.VersionOptions?.Version?.Version?.Build ?? -1; - /// /// Gets or sets the major.minor version string. /// @@ -190,6 +188,13 @@ public IEnumerable BuildMetadataWithCommitId /// public int VersionHeight { get; } + /// + /// The offset to add to the + /// when calculating the integer to use as the + /// or elsewhere that the {height} macro is used. + /// + public int VersionHeightOffset { get; } + private string BuildingRef { get; } /// @@ -261,6 +266,8 @@ public IDictionary CloudBuildVersionVars private VersionOptions.CloudBuildNumberOptions CloudBuildNumberOptions { get; } + private int VersionHeightWithOffset => this.VersionHeight + this.VersionHeightOffset; + private static string FormatBuildMetadata(IEnumerable identifiers) => (identifiers?.Any() ?? false) ? "+" + string.Join(".", identifiers) : string.Empty; @@ -329,5 +336,12 @@ private static Version GetAssemblyVersion(Version version, VersionOptions versio precision >= VersionOptions.VersionPrecision.Revision ? version.Revision : 0); return assemblyVersion.EnsureNonNegativeComponents(4); } + + /// + /// Replaces any macros found in a prerelease or build metadata string. + /// + /// The prerelease or build metadata. + /// The specified string, with macros substituted for actual values. + private string ReplaceMacros(string prereleaseOrBuildMetadata) => prereleaseOrBuildMetadata?.Replace("{height}", this.VersionHeightWithOffset.ToString(CultureInfo.InvariantCulture)); } } diff --git a/src/NerdBank.GitVersioning/version.schema.json b/src/NerdBank.GitVersioning/version.schema.json index d14ba1ec..4d671f68 100644 --- a/src/NerdBank.GitVersioning/version.schema.json +++ b/src/NerdBank.GitVersioning/version.schema.json @@ -6,8 +6,8 @@ "properties": { "version": { "type": "string", - "description": "The default x.y-pre version to use as the basis for version calculations.", - "pattern": "^v?(?0|[1-9][0-9]*)\\.(?0|[1-9][0-9]*)(?:\\.(?0|[1-9][0-9]*))?(?-[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?(?\\+[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?$" + "description": "The major.minor-pre version to use as the basis for version calculations. If {height} is not used in this value and the value has fewer than the full major.minor.build.revision specified, \".{height}\" will be appended by the build.", + "pattern": "^v?(?0|[1-9][0-9]*)\\.(?0|[1-9][0-9]*)(?:\\.(?0|[1-9][0-9]*)(?:\\.(?0|[1-9][0-9]*))?)?(?-(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*)?(?\\+(?:[\\da-z\\-]+|\\{height\\})(?:\\.(?:[\\da-z\\-]+|\\{height\\}))*)?$" }, "assemblyVersion": { "description": "The x.y version to use particularly for the AssemblyVersionAttribute instead of the default. This is useful when maintaining assembly binding compatibility on the desktop .NET Framework is important even though AssemblyFileVersion may change.", diff --git a/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets b/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets index bd88f045..e502e37d 100644 --- a/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets +++ b/src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets @@ -69,7 +69,6 @@ - diff --git a/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs b/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs index a0adc782..77abd09b 100644 --- a/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs +++ b/src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs @@ -140,12 +140,6 @@ public GetBuildVersion() [Output] public int BuildNumber { get; private set; } - /// - /// Gets the build number as specified in the version.json file. - /// - [Output] - public string BuildNumberFromVersionJson { get; private set; } - /// /// Gets the BuildNumber to set the cloud build to (if applicable). /// @@ -181,7 +175,6 @@ protected override bool ExecuteInner() this.SimpleVersion = oracle.SimpleVersion.ToString(); this.MajorMinorVersion = oracle.MajorMinorVersion.ToString(); this.BuildNumber = oracle.BuildNumber; - this.BuildNumberFromVersionJson = oracle.BuildNumberFromVersionOptions != -1 ? oracle.BuildNumberFromVersionOptions.ToString(CultureInfo.InvariantCulture) : string.Empty; this.PrereleaseVersion = oracle.PrereleaseVersion; this.GitCommitId = oracle.GitCommitId; this.GitCommitIdShort = oracle.GitCommitIdShort; From 8f77eda4edb9df6823992302f1d8a885d6cfb117 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sun, 28 May 2017 16:02:06 -0700 Subject: [PATCH 3/4] Bump version to 2.0-beta The preceding commits constitute at least a minor breaking change and a higher level of flexibility --- src/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.json b/src/version.json index 953a0ecb..f75ab80c 100644 --- a/src/version.json +++ b/src/version.json @@ -1,6 +1,6 @@ { "$schema": "src\\NerdBank.GitVersioning\\version.schema.json", - "version": "1.6", + "version": "2.0-beta", "assemblyVersion": { "precision": "revision" }, From 4d80666c500dcab6ea6b6daaef2e47329dde9d90 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sun, 28 May 2017 22:21:35 -0700 Subject: [PATCH 4/4] Improve semver1 conversion from semver2 --- doc/versionJson.md | 8 +++- .../BuildIntegrationTests.cs | 8 +++- .../VersionOracleTests.cs | 37 +++++++++++++++++ src/NerdBank.GitVersioning/VersionOptions.cs | 6 +++ src/NerdBank.GitVersioning/VersionOracle.cs | 40 ++++++++++++++++++- .../version.schema.json | 7 ++++ 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/doc/versionJson.md b/doc/versionJson.md index 2a9e20e6..4e041d66 100644 --- a/doc/versionJson.md +++ b/doc/versionJson.md @@ -24,9 +24,10 @@ The content of the version.json file is a JSON serialized object with these prop "version": "x.y-prerelease", // required "assemblyVersion": "x.y", // optional. Use when x.y for AssemblyVersionAttribute differs from the default version property. "buildNumberOffset": "zOffset", // optional. Use when you need to add/subtract a fixed value from the computed build number. + "semVer1NumericIdentifierPadding": 4, // optional. Use when your -prerelease includes numeric identifiers and need semver1 support. "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master - "^refs/tags/v\\d\\.\\d" // we also release tags starting with vN.N + "^refs/tags/v\\d+\\.\\d+" // we also release tags starting with vN.N ], "cloudBuild": { "setVersionVariables": true, @@ -43,6 +44,11 @@ The content of the version.json file is a JSON serialized object with these prop The `x` and `y` variables are for your use to specify a version that is meaningful to your customers. Consider using [semantic versioning][semver] for guidance. +You may optionally supply a third integer in the version (i.e. x.y.z), +in which case the git version height is specified as the fourth integer, +which only appears in certain version representations. +Alternatively, you can include the git version height in the -prerelease tag using +syntax such as: `1.2.3-beta.{height}` The optional -prerelease tag allows you to indicate that you are building prerelease software. diff --git a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs index c4597996..e9bf90ff 100644 --- a/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs +++ b/src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs @@ -104,6 +104,7 @@ public async Task GetBuildVersion_WithThreeVersionIntegers() VersionOptions workingCopyVersion = new VersionOptions { Version = SemanticVersion.Parse("7.8.9-beta.3"), + SemVer1NumericIdentifierPadding = 1, }; this.WriteVersionFile(workingCopyVersion); this.InitializeSourceControl(); @@ -860,7 +861,7 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult string pkgVersionSuffix = buildResult.PublicRelease ? string.Empty : $"-g{commitIdShort}"; - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{versionOptions.Version.Prerelease}{pkgVersionSuffix}", buildResult.NuGetPackageVersion); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVer1PrereleaseTag(versionOptions)}{pkgVersionSuffix}", buildResult.NuGetPackageVersion); var buildNumberOptions = versionOptions.CloudBuild?.BuildNumber ?? new VersionOptions.CloudBuildNumberOptions(); if (buildNumberOptions.Enabled) @@ -892,6 +893,11 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult } } + private static string GetSemVer1PrereleaseTag(VersionOptions versionOptions) + { + return versionOptions.Version.Prerelease?.Replace('.', '-'); + } + private async Task BuildAsync(string target = Targets.GetBuildVersion, LoggerVerbosity logVerbosity = LoggerVerbosity.Detailed, bool assertSuccessfulBuild = true) { var eventLogger = new MSBuildLogger { Verbosity = LoggerVerbosity.Minimal }; diff --git a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs index a0947183..9a374cbd 100644 --- a/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs +++ b/src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs @@ -112,4 +112,41 @@ public void HeightInBuildMetadata() Assert.Equal(1, oracle.VersionHeight); Assert.Equal(2, oracle.VersionHeightOffset); } + + [Theory] + [InlineData("7.8.9-foo.25", "7.8.9-foo-0025")] + [InlineData("7.8.9-foo.25s", "7.8.9-foo-25s")] + [InlineData("7.8.9-foo.s25", "7.8.9-foo-s25")] + [InlineData("7.8.9-foo.25.bar-24.13-11", "7.8.9-foo-0025-bar-24-13-11")] + [InlineData("7.8.9-25.bar.baz-25", "7.8.9-0025-bar-baz-25")] + [InlineData("7.8.9-foo.5.bar.1.43.baz", "7.8.9-foo-0005-bar-0001-0043-baz")] + public void SemVer1PrereleaseConversion(string semVer2, string semVer1) + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse(semVer2), + BuildNumberOffset = 2, + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + oracle.PublicRelease = true; + Assert.Equal(semVer1, oracle.SemVer1); + } + + [Fact] + public void SemVer1PrereleaseConversionPadding() + { + VersionOptions workingCopyVersion = new VersionOptions + { + Version = SemanticVersion.Parse("7.8.9-foo.25"), + BuildNumberOffset = 2, + SemVer1NumericIdentifierPadding = 3, + }; + this.WriteVersionFile(workingCopyVersion); + this.InitializeSourceControl(); + var oracle = VersionOracle.Create(this.RepoPath); + oracle.PublicRelease = true; + Assert.Equal("7.8.9-foo-025", oracle.SemVer1); + } } diff --git a/src/NerdBank.GitVersioning/VersionOptions.cs b/src/NerdBank.GitVersioning/VersionOptions.cs index 4fadc3d8..961dad38 100644 --- a/src/NerdBank.GitVersioning/VersionOptions.cs +++ b/src/NerdBank.GitVersioning/VersionOptions.cs @@ -59,6 +59,12 @@ public class VersionOptions : IEquatable [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public int BuildNumberOffset { get; set; } + /// + /// Gets or sets the minimum number of digits to use for numeric identifiers in SemVer 1. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public int? SemVer1NumericIdentifierPadding { get; set; } + /// /// Gets or sets an array of regular expressions that describes branch or tag names that should /// be built with PublicRelease=true as the default value on build servers. diff --git a/src/NerdBank.GitVersioning/VersionOracle.cs b/src/NerdBank.GitVersioning/VersionOracle.cs index 6073e997..2f1f22f0 100644 --- a/src/NerdBank.GitVersioning/VersionOracle.cs +++ b/src/NerdBank.GitVersioning/VersionOracle.cs @@ -15,6 +15,11 @@ /// public class VersionOracle { + /// + /// A regex that matches on numeric identifiers for prerelease or build metadata. + /// + private static readonly Regex NumericIdentifierRegex = new Regex(@"(? /// The cloud build suppport, if any. /// @@ -249,7 +254,7 @@ public IDictionary CloudBuildVersionVars /// when is false. /// public string SemVer1 => - $"{this.Version.ToStringSafe(3)}{this.PrereleaseVersion}{this.SemVer1BuildMetadata}"; + $"{this.Version.ToStringSafe(3)}{this.PrereleaseVersionSemVer1}{this.SemVer1BuildMetadata}"; /// /// Gets a SemVer 2.0 compliant string that represents this version, including a +gCOMMITID suffix @@ -258,12 +263,19 @@ public IDictionary CloudBuildVersionVars public string SemVer2 => $"{this.Version.ToStringSafe(3)}{this.PrereleaseVersion}{this.SemVer2BuildMetadata}"; + /// + /// Gets the minimum number of digits to use for numeric identifiers in SemVer 1. + /// + public int SemVer1NumericIdentifierPadding => this.VersionOptions?.SemVer1NumericIdentifierPadding ?? 4; + private string SemVer1BuildMetadata => this.PublicRelease ? string.Empty : $"-g{this.GitCommitIdShort}"; private string SemVer2BuildMetadata => FormatBuildMetadata(this.PublicRelease ? this.BuildMetadata : this.BuildMetadataWithCommitId); + private string PrereleaseVersionSemVer1 => MakePrereleaseSemVer1Compliant(this.PrereleaseVersion, SemVer1NumericIdentifierPadding); + private VersionOptions.CloudBuildNumberOptions CloudBuildNumberOptions { get; } private int VersionHeightWithOffset => this.VersionHeight + this.VersionHeightOffset; @@ -343,5 +355,31 @@ private static Version GetAssemblyVersion(Version version, VersionOptions versio /// The prerelease or build metadata. /// The specified string, with macros substituted for actual values. private string ReplaceMacros(string prereleaseOrBuildMetadata) => prereleaseOrBuildMetadata?.Replace("{height}", this.VersionHeightWithOffset.ToString(CultureInfo.InvariantCulture)); + + /// + /// Converts a semver 2 compliant "-beta.5" prerelease tag to a semver 1 compatible one. + /// + /// The semver 2 prerelease tag, including its leading hyphen. + /// The minimum number of digits to use for any numeric identifier. + /// A semver 1 compliant prerelease tag. For example "-beta-0005". + private static string MakePrereleaseSemVer1Compliant(string prerelease, int paddingSize) + { + if (string.IsNullOrEmpty(prerelease)) + { + return prerelease; + } + + string paddingFormatter = "{0:" + new string('0', paddingSize) + "}"; + + string semver1 = prerelease; + + // Identify numeric identifiers and pad them. + Assumes.True(prerelease.StartsWith("-")); + semver1 = "-" + NumericIdentifierRegex.Replace(semver1.Substring(1), m => string.Format(CultureInfo.InvariantCulture, paddingFormatter, int.Parse(m.Groups[1].Value))); + + semver1 = semver1.Replace('.', '-'); + + return semver1; + } } } diff --git a/src/NerdBank.GitVersioning/version.schema.json b/src/NerdBank.GitVersioning/version.schema.json index 4d671f68..85076d43 100644 --- a/src/NerdBank.GitVersioning/version.schema.json +++ b/src/NerdBank.GitVersioning/version.schema.json @@ -32,6 +32,13 @@ "description": "A number to add to the git height when calculating the build number.", "default": 0 }, + "semVer1NumericIdentifierPadding": { + "type": "integer", + "description": "The minimum number of digits to use for numeric identifiers in SemVer 1.", + "default": 4, + "minimum": 1, + "maximum": 6 + }, "publicReleaseRefSpec": { "type": "array", "description": "An array of regular expressions that may match a ref (branch or tag) that should be built with PublicRelease=true as the default value. The ref matched against is in its canonical form (e.g. refs/heads/master)",