Skip to content

Commit bab373d

Browse files
authored
Merge pull request #132 from onovotny/semver-choice
Add option to choose SemVer option
2 parents e1be204 + d3cbb70 commit bab373d

File tree

7 files changed

+200
-10
lines changed

7 files changed

+200
-10
lines changed

doc/versionJson.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ The content of the version.json file is a JSON serialized object with these prop
2525
"assemblyVersion": "x.y", // optional. Use when x.y for AssemblyVersionAttribute differs from the default version property.
2626
"buildNumberOffset": "zOffset", // optional. Use when you need to add/subtract a fixed value from the computed build number.
2727
"semVer1NumericIdentifierPadding": 4, // optional. Use when your -prerelease includes numeric identifiers and need semver1 support.
28+
"nugetPackageVersion": {
29+
"semVer": 1 // optional. Set to either 1 or 2 to control how the NuGet package version string is generated. Default is 1.
30+
},
2831
"publicReleaseRefSpec": [
2932
"^refs/heads/master$", // we release out of master
3033
"^refs/tags/v\\d+\\.\\d+" // we also release tags starting with vN.N

src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public BuildIntegrationTests(ITestOutputHelper logger)
7272
}
7373
}
7474

75+
private string CommitIdShort => this.Repo.Head.Commits.First().Id.Sha.Substring(0, 10);
76+
7577
protected override void Dispose(bool disposing)
7678
{
7779
Environment.SetEnvironmentVariable("_NBGV_UnitTest", string.Empty);
@@ -578,11 +580,15 @@ public async Task BuildNumber_SetInCI(VersionOptions versionOptions, IReadOnlyDi
578580

579581
[Theory]
580582
[PairwiseData]
581-
public async Task BuildNumber_VariousOptions(bool isPublic, VersionOptions.CloudBuildNumberCommitWhere where, VersionOptions.CloudBuildNumberCommitWhen when, [CombinatorialValues(0, 1, 2)] int extraBuildMetadataCount)
583+
public async Task BuildNumber_VariousOptions(bool isPublic, VersionOptions.CloudBuildNumberCommitWhere where, VersionOptions.CloudBuildNumberCommitWhen when, [CombinatorialValues(0, 1, 2)] int extraBuildMetadataCount, [CombinatorialValues(1, 2)] int semVer)
582584
{
583585
var versionOptions = BuildNumberVersionOptionsBasis;
584586
versionOptions.CloudBuild.BuildNumber.IncludeCommitId.Where = where;
585587
versionOptions.CloudBuild.BuildNumber.IncludeCommitId.When = when;
588+
versionOptions.NuGetPackageVersion = new VersionOptions.NuGetPackageVersionOptions
589+
{
590+
SemVer = semVer,
591+
};
586592
this.WriteVersionFile(versionOptions);
587593
this.InitializeSourceControl();
588594

@@ -828,7 +834,7 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
828834
{
829835
int versionHeight = this.Repo.GetVersionHeight(relativeProjectDirectory);
830836
Version idAsVersion = this.Repo.GetIdAsVersion(relativeProjectDirectory);
831-
string commitIdShort = this.Repo.Head.Commits.First().Id.Sha.Substring(0, 10);
837+
string commitIdShort = this.CommitIdShort;
832838
Version version = this.Repo.GetIdAsVersion(relativeProjectDirectory);
833839
Version assemblyVersion = GetExpectedAssemblyVersion(versionOptions, version);
834840
var additionalBuildMetadata = from item in buildResult.BuildResult.ProjectStateAfterBuild.GetItems("BuildMetadata")
@@ -839,6 +845,8 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
839845
expectedBuildMetadata += "." + string.Join(".", additionalBuildMetadata);
840846
}
841847

848+
string expectedBuildMetadataWithoutCommitId = additionalBuildMetadata.Any() ? $"+{string.Join(".", additionalBuildMetadata)}" : string.Empty;
849+
842850
Assert.Equal($"{version}", buildResult.AssemblyFileVersion);
843851
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{versionOptions.Version.Prerelease}{expectedBuildMetadata}", buildResult.AssemblyInformationalVersion);
844852

@@ -858,10 +866,15 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
858866
Assert.Equal(versionOptions.Version.Prerelease, buildResult.PrereleaseVersion);
859867
Assert.Equal(expectedBuildMetadata, buildResult.SemVerBuildSuffix);
860868

861-
string pkgVersionSuffix = buildResult.PublicRelease
862-
? string.Empty
863-
: $"-g{commitIdShort}";
864-
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVer1PrereleaseTag(versionOptions)}{pkgVersionSuffix}", buildResult.NuGetPackageVersion);
869+
// NuGet is now SemVer 2.0 and will pass additional build metadata if provided
870+
bool semVer2 = (versionOptions?.NuGetPackageVersion ?? VersionOptions.NuGetPackageVersionOptions.DefaultInstance).SemVer == 2;
871+
string pkgVersionSuffix = buildResult.PublicRelease ? string.Empty : $"-g{commitIdShort}";
872+
if (semVer2)
873+
{
874+
pkgVersionSuffix += expectedBuildMetadataWithoutCommitId;
875+
}
876+
877+
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVerAppropriatePrereleaseTag(versionOptions)}{pkgVersionSuffix}", buildResult.NuGetPackageVersion);
865878

866879
var buildNumberOptions = versionOptions.CloudBuild?.BuildNumber ?? new VersionOptions.CloudBuildNumberOptions();
867880
if (buildNumberOptions.Enabled)
@@ -893,9 +906,11 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
893906
}
894907
}
895908

896-
private static string GetSemVer1PrereleaseTag(VersionOptions versionOptions)
909+
private static string GetSemVerAppropriatePrereleaseTag(VersionOptions versionOptions)
897910
{
898-
return versionOptions.Version.Prerelease?.Replace('.', '-');
911+
return (versionOptions.NuGetPackageVersion ?? VersionOptions.NuGetPackageVersionOptions.DefaultInstance).SemVer == 1
912+
? versionOptions.Version.Prerelease?.Replace('.', '-')
913+
: versionOptions.Version.Prerelease;
899914
}
900915

901916
private async Task<BuildResults> BuildAsync(string target = Targets.GetBuildVersion, LoggerVerbosity logVerbosity = LoggerVerbosity.Detailed, bool assertSuccessfulBuild = true)

src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.IO;
2+
using System.Linq;
23
using LibGit2Sharp;
34
using Nerdbank.GitVersioning;
45
using Nerdbank.GitVersioning.Tests;
@@ -12,6 +13,8 @@ public VersionOracleTests(ITestOutputHelper logger)
1213
{
1314
}
1415

16+
private string CommitIdShort => this.Repo.Head.Commits.First().Id.Sha.Substring(0, 10);
17+
1518
[Fact]
1619
public void Submodule_RecognizedWithCorrectVersion()
1720
{
@@ -149,4 +152,85 @@ public void SemVer1PrereleaseConversionPadding()
149152
oracle.PublicRelease = true;
150153
Assert.Equal("7.8.9-foo-025", oracle.SemVer1);
151154
}
155+
156+
[Fact]
157+
public void DefaultNuGetPackageVersionIsSemVer1PublicRelease()
158+
{
159+
VersionOptions workingCopyVersion = new VersionOptions
160+
{
161+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
162+
SemVer1NumericIdentifierPadding = 2,
163+
};
164+
this.WriteVersionFile(workingCopyVersion);
165+
this.InitializeSourceControl();
166+
var oracle = VersionOracle.Create(this.RepoPath);
167+
oracle.PublicRelease = true;
168+
Assert.Equal($"7.8.9-foo-25", oracle.NuGetPackageVersion);
169+
}
170+
171+
[Fact]
172+
public void DefaultNuGetPackageVersionIsSemVer1NonPublicRelease()
173+
{
174+
VersionOptions workingCopyVersion = new VersionOptions
175+
{
176+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
177+
SemVer1NumericIdentifierPadding = 2,
178+
};
179+
this.WriteVersionFile(workingCopyVersion);
180+
this.InitializeSourceControl();
181+
var oracle = VersionOracle.Create(this.RepoPath);
182+
oracle.PublicRelease = false;
183+
Assert.Equal($"7.8.9-foo-25-g{this.CommitIdShort}", oracle.NuGetPackageVersion);
184+
}
185+
186+
[Fact]
187+
public void NpmPackageVersionIsSemVer1()
188+
{
189+
VersionOptions workingCopyVersion = new VersionOptions
190+
{
191+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
192+
SemVer1NumericIdentifierPadding = 2
193+
};
194+
this.WriteVersionFile(workingCopyVersion);
195+
this.InitializeSourceControl();
196+
var oracle = VersionOracle.Create(this.RepoPath);
197+
oracle.PublicRelease = true;
198+
Assert.Equal("7.8.9-foo-25", oracle.NpmPackageVersion);
199+
}
200+
201+
[Fact]
202+
public void CanSetSemVer2ForNuGetPackageVersionPublicRelease()
203+
{
204+
VersionOptions workingCopyVersion = new VersionOptions
205+
{
206+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
207+
NuGetPackageVersion = new VersionOptions.NuGetPackageVersionOptions
208+
{
209+
SemVer = 2,
210+
}
211+
};
212+
this.WriteVersionFile(workingCopyVersion);
213+
this.InitializeSourceControl();
214+
var oracle = VersionOracle.Create(this.RepoPath);
215+
oracle.PublicRelease = true;
216+
Assert.Equal($"7.8.9-foo.25", oracle.NuGetPackageVersion);
217+
}
218+
219+
[Fact]
220+
public void CanSetSemVer2ForNuGetPackageVersionNonPublicRelease()
221+
{
222+
VersionOptions workingCopyVersion = new VersionOptions
223+
{
224+
Version = SemanticVersion.Parse("7.8.9-foo.25"),
225+
NuGetPackageVersion = new VersionOptions.NuGetPackageVersionOptions
226+
{
227+
SemVer = 2,
228+
}
229+
};
230+
this.WriteVersionFile(workingCopyVersion);
231+
this.InitializeSourceControl();
232+
var oracle = VersionOracle.Create(this.RepoPath);
233+
oracle.PublicRelease = false;
234+
Assert.Equal($"7.8.9-foo.25.g{this.CommitIdShort}", oracle.NuGetPackageVersion);
235+
}
152236
}

src/NerdBank.GitVersioning/VersionOptions.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ public class VersionOptions : IEquatable<VersionOptions>
6565
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
6666
public int? SemVer1NumericIdentifierPadding { get; set; }
6767

68+
/// <summary>
69+
/// Gets or sets the options around NuGet version strings
70+
/// </summary>
71+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
72+
public NuGetPackageVersionOptions NuGetPackageVersion { get; set; }
73+
6874
/// <summary>
6975
/// Gets or sets an array of regular expressions that describes branch or tag names that should
7076
/// be built with PublicRelease=true as the default value on build servers.
@@ -131,6 +137,7 @@ public bool Equals(VersionOptions other)
131137

132138
return EqualityComparer<SemanticVersion>.Default.Equals(this.Version, other.Version)
133139
&& EqualityComparer<AssemblyVersionOptions>.Default.Equals(this.AssemblyVersion, other.AssemblyVersion)
140+
&& EqualityComparer<NuGetPackageVersionOptions>.Default.Equals(this.NuGetPackageVersion, other.NuGetPackageVersion)
134141
&& EqualityComparer<CloudBuildOptions>.Default.Equals(this.CloudBuild ?? CloudBuildOptions.DefaultInstance, other.CloudBuild ?? CloudBuildOptions.DefaultInstance)
135142
&& this.BuildNumberOffset == other.BuildNumberOffset;
136143
}
@@ -150,8 +157,61 @@ internal bool IsDefaultVersionTheOnlyPropertySet
150157

151158
internal bool ShouldSerializeAssemblyVersion() => !(this.AssemblyVersion?.IsDefault ?? true);
152159

160+
internal bool ShouldSerializeNuGetPackageVersion() => !(this.NuGetPackageVersion?.IsDefault ?? true);
161+
153162
internal bool ShouldSerializeCloudBuild() => !(this.CloudBuild?.IsDefault ?? true);
154163

164+
/// <summary>
165+
/// The class that contains settings for the <see cref="NuGetPackageVersion" /> property.
166+
/// </summary>
167+
public class NuGetPackageVersionOptions : IEquatable<NuGetPackageVersionOptions>
168+
{
169+
/// <summary>
170+
/// Default value for <see cref="SemVer"/>
171+
/// </summary>
172+
private const float DefaultNuGetPackageVersion = 1.0f;
173+
174+
/// <summary>
175+
/// The default (uninitialized) instance.
176+
/// </summary>
177+
public static readonly NuGetPackageVersionOptions DefaultInstance = new NuGetPackageVersionOptions();
178+
179+
/// <summary>
180+
/// Initializes a new instance of the <see cref="NuGetPackageVersionOptions" /> class.
181+
/// </summary>
182+
public NuGetPackageVersionOptions()
183+
{
184+
}
185+
186+
/// <summary>
187+
/// Gets or sets the version of SemVer (e.g. 1 or 2) that should be used when generating the package version.
188+
/// </summary>
189+
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
190+
[DefaultValue(DefaultNuGetPackageVersion)]
191+
public float SemVer { get; set; } = DefaultNuGetPackageVersion;
192+
193+
/// <inheritdoc />
194+
public override bool Equals(object obj) => this.Equals(obj as NuGetPackageVersionOptions);
195+
196+
/// <inheritdoc />
197+
public bool Equals(NuGetPackageVersionOptions other)
198+
{
199+
return other != null
200+
&& this.SemVer == other.SemVer;
201+
}
202+
203+
/// <inheritdoc />
204+
public override int GetHashCode()
205+
{
206+
return this.SemVer.GetHashCode();
207+
}
208+
209+
/// <summary>
210+
/// Gets a value indicating whether this instance is equivalent to the default instance.
211+
/// </summary>
212+
internal bool IsDefault => this.Equals(DefaultInstance);
213+
}
214+
155215
/// <summary>
156216
/// Describes the details of how the AssemblyVersion value will be calculated.
157217
/// </summary>

src/NerdBank.GitVersioning/VersionOptionsContractResolver.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ
2020
property.ShouldSerialize = instance => ((VersionOptions)instance).ShouldSerializeAssemblyVersion();
2121
}
2222

23+
if (property.DeclaringType == typeof(VersionOptions) && member.Name == nameof(VersionOptions.NuGetPackageVersion))
24+
{
25+
property.ShouldSerialize = instance => ((VersionOptions)instance).ShouldSerializeNuGetPackageVersion();
26+
}
27+
2328
if (property.DeclaringType == typeof(VersionOptions) && member.Name == nameof(VersionOptions.CloudBuild))
2429
{
2530
property.ShouldSerialize = instance => ((VersionOptions)instance).ShouldSerializeCloudBuild();

src/NerdBank.GitVersioning/VersionOracle.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public IDictionary<string, string> CloudBuildVersionVars
242242
/// <summary>
243243
/// Gets the version to use for NuGet packages.
244244
/// </summary>
245-
public string NuGetPackageVersion => this.SemVer1;
245+
public string NuGetPackageVersion => (this.VersionOptions?.NuGetPackageVersion ?? VersionOptions.NuGetPackageVersionOptions.DefaultInstance).SemVer == 1 ? this.SemVer1 : this.SemVer2;
246246

247247
/// <summary>
248248
/// Gets the version to use for NPM packages.
@@ -271,11 +271,21 @@ public IDictionary<string, string> CloudBuildVersionVars
271271
private string SemVer1BuildMetadata =>
272272
this.PublicRelease ? string.Empty : $"-g{this.GitCommitIdShort}";
273273

274+
/// <summary>
275+
/// Gets the build metadata that is appropriate for SemVer2 use.
276+
/// </summary>
277+
/// <remarks>
278+
/// We always put the commit ID in the -prerelease tag for non-public releases.
279+
/// But for public releases, we don't include it in the +buildMetadata section since it may be confusing for NuGet.
280+
/// See https://github.com/AArnott/Nerdbank.GitVersioning/pull/132#issuecomment-307208561
281+
/// </remarks>
274282
private string SemVer2BuildMetadata =>
275-
FormatBuildMetadata(this.PublicRelease ? this.BuildMetadata : this.BuildMetadataWithCommitId);
283+
(this.PublicRelease ? string.Empty : this.GitCommitIdShortForNonPublicPrereleaseTag) + FormatBuildMetadata(this.BuildMetadata);
276284

277285
private string PrereleaseVersionSemVer1 => MakePrereleaseSemVer1Compliant(this.PrereleaseVersion, SemVer1NumericIdentifierPadding);
278286

287+
private string GitCommitIdShortForNonPublicPrereleaseTag => (string.IsNullOrEmpty(this.PrereleaseVersion) ? "-" : ".") + $"g{this.GitCommitIdShort}";
288+
279289
private VersionOptions.CloudBuildNumberOptions CloudBuildNumberOptions { get; }
280290

281291
private int VersionHeightWithOffset => this.VersionHeight + this.VersionHeightOffset;

src/NerdBank.GitVersioning/version.schema.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
"minimum": 1,
4040
"maximum": 6
4141
},
42+
"nugetPackageVersion": {
43+
"type": "object",
44+
"description": "Details for how and what the generated version for NuGet packages will be.",
45+
"properties": {
46+
"semVer": {
47+
"type": "integer",
48+
"description": "The version of SemVer (e.g. 1 or 2) that should be used when generating the package version.",
49+
"default": 1,
50+
"minimum": 1,
51+
"maximum": 2
52+
}
53+
}
54+
},
4255
"publicReleaseRefSpec": {
4356
"type": "array",
4457
"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)",

0 commit comments

Comments
 (0)