Skip to content

Commit

Permalink
Merge pull request #119 from AArnott/BuildNumberFromVersionJson
Browse files Browse the repository at this point in the history
Allow user control of build number integer
  • Loading branch information
AArnott authored May 30, 2017
2 parents 14d6ff9 + 4d80666 commit 2ad35fe
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 28 deletions.
8 changes: 7 additions & 1 deletion doc/versionJson.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.

Expand Down
36 changes: 35 additions & 1 deletion src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ 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"),
SemVer1NumericIdentifierPadding = 1,
};
this.WriteVersionFile(workingCopyVersion);
this.InitializeSourceControl();
var buildResult = await this.BuildAsync();
this.AssertStandardProperties(workingCopyVersion, buildResult);
}

[Fact]
public async Task GetBuildVersion_Without_Git_HighPrecisionAssemblyVersion()
{
Expand Down Expand Up @@ -381,6 +395,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()
{
Expand Down Expand Up @@ -832,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)
Expand Down Expand Up @@ -864,6 +893,11 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
}
}

private static string GetSemVer1PrereleaseTag(VersionOptions versionOptions)
{
return versionOptions.Version.Prerelease?.Replace('.', '-');
}

private async Task<BuildResults> BuildAsync(string target = Targets.GetBuildVersion, LoggerVerbosity logVerbosity = LoggerVerbosity.Detailed, bool assertSuccessfulBuild = true)
{
var eventLogger = new MSBuildLogger { Verbosity = LoggerVerbosity.Minimal };
Expand Down
19 changes: 18 additions & 1 deletion src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</EmbeddedResource>
<EmbeddedResource Include="Keys\*.snk" />
<EmbeddedResource Include="Keys\*.pfx" />
<EmbeddedResource Include="..\NerdBank.GitVersioning\version.schema.json" Link="version.schema.json" />
<EmbeddedResource Include="test.prj" />
<EmbeddedResource Include="repos\submodules.7z" />
</ItemGroup>
Expand All @@ -21,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="7z.NET" Version="1.0.3" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="2.0.11" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
<PackageReference Include="Microsoft.Build" Version="14.3" Condition=" '$(TargetFramework)' == 'net452' " />
<PackageReference Include="Xunit.Combinatorial" Version="1.1.12" />
Expand Down
121 changes: 121 additions & 0 deletions src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,125 @@ 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);
}

[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);
}
}
73 changes: 73 additions & 0 deletions src/NerdBank.GitVersioning.Tests/VersionSchemaTests.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
46 changes: 30 additions & 16 deletions src/NerdBank.GitVersioning/GitExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,26 +512,40 @@ private static void AddReachableCommitsFrom(Commit startingCommit, HashSet<Commi
private static Version GetIdAsVersionHelper(Commit commit, VersionOptions versionOptions, string repoRelativeProjectDirectory, int? versionHeight)
{
var baseVersion = versionOptions?.Version?.Version ?? Version0;
int buildNumber = baseVersion.Build;
int revision = baseVersion.Revision;

// 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)
if (revision < 0)
{
versionHeight = commit != null
? commit.GetHeight(c => 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);
}

/// <summary>
Expand Down
Loading

0 comments on commit 2ad35fe

Please sign in to comment.