Skip to content

Commit 4f10374

Browse files
authored
Merge pull request #508 from dotnet/perfImprovements
Invoke `GetBuildVersion` MSBuild task only once per project (or per repo)
2 parents f14ba78 + 52a4e31 commit 4f10374

8 files changed

+208
-54
lines changed

doc/globalproperties_log.png

79.3 KB
Loading

doc/msbuild.md

+48-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# MSBuild
22

3-
Installing the Nerdbank.GitVersioning package from NuGet into your
4-
MSBuild-based projects
3+
Installing the `Nerdbank.GitVersioning` package from NuGet into your MSBuild-based projects is the recommended way to add version information to your MSBuild project outputs including assemblies, native dll's, and packages.
54

65
## Public releases
76

@@ -12,11 +11,56 @@ in order to avoid the git commit ID being included in the NuGet package version.
1211

1312
From the command line, building a release version might look like this:
1413

15-
msbuild /p:PublicRelease=true
14+
```ps1
15+
msbuild /p:PublicRelease=true
16+
```
1617

1718
Note you may consider passing this switch to any build that occurs in the
18-
branch that you publish released NuGet packages from.
19+
branch that you publish released NuGet packages from.
1920
You should only build with this property set from one release branch per
2021
major.minor version to avoid the risk of producing multiple unique NuGet
2122
packages with a colliding version spec.
2223

24+
## Build performance
25+
26+
Repos with many projects or many commits between major/minor version bumps can suffer from compromised build performance due to the MSBuild task that computes the version information for each project.
27+
You can assess the impact that Nerdbank.GitVersioning has on your build time by running a build with the `-clp:PerformanceSummary` switch and look for the `Nerdbank.GitVersioning.Tasks.GetBuildVersion` task.
28+
29+
### Reducing `GetBuildVersion` invocations
30+
31+
If the `GetBuildVersion` task is running many times, yet you have just one (or a few) `version.json` files in your repository, you can reduce this task to being called just once per `version.json` file to *significantly* improve build performance.
32+
To do this, drop a `Directory.Build.props` file in the same directory as your `version.json` file(s) with this content:
33+
34+
```xml
35+
<Project>
36+
<PropertyGroup>
37+
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
38+
</PropertyGroup>
39+
</Project>
40+
```
41+
42+
This MSBuild property instructs all projects in or under that directory to share a computed version based on that directory rather than their individual project directories.
43+
Check the impact of this change by re-running MSBuild with the `-clp:PerformanceSummary` switch as described above.
44+
45+
If you still see many invocations, you may have a build that sets global properties on P2P references.
46+
Investigate this using the MSBuild `/bl` switch and view the log with the excellent [MSBuild Structured Log Viewer](https://msbuildlog.com) tool.
47+
Using that tool, search for `$task GetBuildVersion` and look at the global properties passed to the special `Nerdbank.GitVersioning.Inner.targets` project, as shown:
48+
49+
[![MSBuild Structure Logger screenshot](globalproperties_log.png)
50+
51+
Compare the set of global properties for each `Nerdbank.GitVersioning.Inner.targets` project to identify which properties are unique each time.
52+
Add the names of the unique properties to the `Directory.Build.props` file:
53+
54+
```xml
55+
<Project>
56+
<PropertyGroup>
57+
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
58+
</PropertyGroup>
59+
<ItemGroup>
60+
<NBGV_GlobalPropertiesToRemove Include="ChangingProperty1" />
61+
<NBGV_GlobalPropertiesToRemove Include="ChangingProperty2" />
62+
</ItemGroup>
63+
</Project>
64+
```
65+
66+
That should get you down to one invocation of the `GetBuildVersion` task per `version.json` file in your repo.

src/NerdBank.GitVersioning.Tests/NerdBank.GitVersioning.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Compile Include="..\Shared\**\*.cs" LinkBase="Shared" />
1313
</ItemGroup>
1414
<ItemGroup>
15-
<EmbeddedResource Include="..\Nerdbank.GitVersioning.Tasks\build\Nerdbank.GitVersioning.targets">
15+
<EmbeddedResource Include="..\Nerdbank.GitVersioning.Tasks\build\*.targets">
1616
<Visible>false</Visible>
1717
<Link>Targets\%(FileName)%(Extension)</Link>
1818
</EmbeddedResource>

src/Nerdbank.GitVersioning.Tasks/GetBuildVersion.cs

+66-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public GetBuildVersion()
2323
/// <summary>
2424
/// Gets or sets identifiers to append as build metadata.
2525
/// </summary>
26-
public string[] BuildMetadata { get; set; }
26+
public string BuildMetadata { get; set; }
2727

2828
/// <summary>
2929
/// Gets or sets the value of the PublicRelease property in MSBuild at the
@@ -33,15 +33,15 @@ public GetBuildVersion()
3333
public string DefaultPublicRelease { get; set; }
3434

3535
/// <summary>
36-
/// Gets or sets the path to the repo root. If null or empty, behavior defaults to using Environment.CurrentDirectory and searching upwards.
36+
/// Gets or sets the path to the repo root. If null or empty, behavior defaults to using <see cref="ProjectDirectory"/> and searching upwards.
3737
/// </summary>
3838
public string GitRepoRoot { get; set; }
3939

4040
/// <summary>
4141
/// Gets or sets the relative path from the <see cref="GitRepoRoot"/> to the directory under it that contains the project being built.
4242
/// </summary>
4343
/// <value>
44-
/// If not supplied, the directories from <see cref="GitRepoRoot"/> to <see cref="Environment.CurrentDirectory"/>
44+
/// If not supplied, the directories from <see cref="GitRepoRoot"/> to <see cref="ProjectDirectory"/>
4545
/// will be searched for version.json.
4646
/// If supplied, the value <em>must</em> fall beneath the <see cref="GitRepoRoot"/> (i.e. this value should not contain "..\").
4747
/// </value>
@@ -51,6 +51,11 @@ public GetBuildVersion()
5151
/// </remarks>
5252
public string ProjectPathRelativeToGitRepoRoot { get; set; }
5353

54+
/// <summary>
55+
/// Gets or sets the path to the project directory.
56+
/// </summary>
57+
public string ProjectDirectory { get; set; }
58+
5459
/// <summary>
5560
/// Gets or sets the optional override build number offset.
5661
/// </summary>
@@ -66,6 +71,12 @@ public GetBuildVersion()
6671
[Required]
6772
public string TargetsPath { get; set; }
6873

74+
/// <summary>
75+
/// Gets or sets the list of properties to be set in MSBuild.
76+
/// </summary>
77+
[Output]
78+
public ITaskItem[] OutputProperties { get; set; }
79+
6980
/// <summary>
7081
/// Gets or sets a value indicating whether the project is building
7182
/// in PublicRelease mode.
@@ -202,15 +213,15 @@ protected override bool ExecuteInner()
202213

203214
var cloudBuild = CloudBuild.Active;
204215
var overrideBuildNumberOffset = (this.OverrideBuildNumberOffset == int.MaxValue) ? (int?)null : this.OverrideBuildNumberOffset;
205-
var oracle = VersionOracle.Create(Directory.GetCurrentDirectory(), this.GitRepoRoot, cloudBuild, overrideBuildNumberOffset, this.ProjectPathRelativeToGitRepoRoot);
216+
var oracle = VersionOracle.Create(this.ProjectDirectory, this.GitRepoRoot, cloudBuild, overrideBuildNumberOffset, this.ProjectPathRelativeToGitRepoRoot);
206217
if (!string.IsNullOrEmpty(this.DefaultPublicRelease))
207218
{
208219
oracle.PublicRelease = string.Equals(this.DefaultPublicRelease, "true", StringComparison.OrdinalIgnoreCase);
209220
}
210221

211222
if (this.BuildMetadata != null)
212223
{
213-
oracle.BuildMetadata.AddRange(this.BuildMetadata);
224+
oracle.BuildMetadata.AddRange(this.BuildMetadata.Split(';'));
214225
}
215226

216227
if (IsMisconfiguredPrereleaseAndSemVer1(oracle))
@@ -266,6 +277,41 @@ protected override bool ExecuteInner()
266277
this.CloudBuildVersionVars = cloudBuildVersionVars.ToArray();
267278
}
268279

280+
var outputProperties = new Dictionary<string, PropertySet>(StringComparer.OrdinalIgnoreCase)
281+
{
282+
{ "BuildVersion", this.Version },
283+
{ "AssemblyInformationalVersion", this.AssemblyInformationalVersion },
284+
{ "AssemblyFileVersion", this.AssemblyFileVersion },
285+
{ "FileVersion", this.AssemblyFileVersion },
286+
{ "BuildVersionSimple", this.SimpleVersion },
287+
{ "PrereleaseVersion", this.PrereleaseVersion },
288+
{ "MajorMinorVersion", this.MajorMinorVersion },
289+
{ "AssemblyVersion", this.AssemblyVersion },
290+
{ "GitCommitId", this.GitCommitId },
291+
{ "GitCommitIdShort", this.GitCommitIdShort },
292+
{ "GitCommitDateTicks", this.GitCommitDateTicks },
293+
{ "GitVersionHeight", this.GitVersionHeight.ToString(CultureInfo.InvariantCulture) },
294+
{ "BuildNumber", this.BuildNumber.ToString(CultureInfo.InvariantCulture) },
295+
{ "BuildVersionNumberComponent", this.BuildNumber.ToString(CultureInfo.InvariantCulture) },
296+
{ "PublicRelease", this.PublicRelease.ToString(CultureInfo.InvariantCulture) },
297+
{ "BuildingRef", this.BuildingRef },
298+
{ "CloudBuildNumber", new PropertySet(this.CloudBuildNumber) { HonorPresetValue = true } },
299+
{ "SemVerBuildSuffix", this.BuildMetadataFragment },
300+
{ "NuGetPackageVersion", this.NuGetPackageVersion },
301+
{ "ChocolateyPackageVersion", this.ChocolateyPackageVersion },
302+
{ "Version", this.NuGetPackageVersion },
303+
{ "PackageVersion", this.NuGetPackageVersion },
304+
{ "NPMPackageVersion", this.NpmPackageVersion.ToString(CultureInfo.InvariantCulture) },
305+
{ "BuildVersion3Components", $"{this.MajorMinorVersion}.{this.BuildNumber}" },
306+
};
307+
this.OutputProperties = outputProperties.Select(kv =>
308+
{
309+
var item = new TaskItem(kv.Key);
310+
item.SetMetadata("Value", kv.Value.Value);
311+
item.SetMetadata("HonorPresetValue", kv.Value.HonorPresetValue ? "true" : "false");
312+
return item;
313+
}).ToArray();
314+
269315
return !this.Log.HasLoggedErrors;
270316
}
271317
catch (ArgumentOutOfRangeException ex)
@@ -292,5 +338,20 @@ private static bool IsMisconfiguredPrereleaseAndSemVer1(VersionOracle oracle)
292338
Requires.NotNull(oracle, nameof(oracle));
293339
return oracle.VersionOptions?.NuGetPackageVersion?.SemVer == 1 && oracle.PrereleaseVersion != SemanticVersionExtensions.MakePrereleaseSemVer1Compliant(oracle.PrereleaseVersion, 0);
294340
}
341+
342+
private struct PropertySet
343+
{
344+
public PropertySet(string value)
345+
{
346+
this.Value = value;
347+
this.HonorPresetValue = false;
348+
}
349+
350+
public string Value { get; set; }
351+
352+
public bool HonorPresetValue { get; set; }
353+
354+
public static implicit operator PropertySet(string value) => new PropertySet(value);
355+
}
295356
}
296357
}

src/Nerdbank.GitVersioning.Tasks/Nerdbank.GitVersioning.nuspec

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ IMPORTANT: The 3.x release may produce a different version height than prior maj
4343
<file src="$BaseOutputPath$netcoreapp2.0\Validation.dll" target="build\MSBuildCore\Validation.dll" />
4444

4545
<file src="build\Nerdbank.GitVersioning.targets" target="build\Nerdbank.GitVersioning.targets" />
46+
<file src="build\Nerdbank.GitVersioning.Common.targets" target="build\Nerdbank.GitVersioning.Common.targets" />
47+
<file src="build\Nerdbank.GitVersioning.Inner.targets" target="build\Nerdbank.GitVersioning.Inner.targets" />
4648
<file src="buildCrossTargeting\Nerdbank.GitVersioning.targets" target="buildCrossTargeting\Nerdbank.GitVersioning.targets" />
4749
<file src="tools\Create-VersionFile.ps1" target="tools\Create-VersionFile.ps1" />
4850
<file src="tools\Get-CommitId.ps1" target="tools\Get-CommitId.ps1" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<_NBGV_PlatformSuffix Condition=" '$(_NBGV_PlatformSuffix)' == '' and '$(MSBuildRuntimeType)' == 'Core' ">MSBuildCore/</_NBGV_PlatformSuffix>
5+
<_NBGV_PlatformSuffix Condition=" '$(_NBGV_PlatformSuffix)' == '' ">MSBuildFull/</_NBGV_PlatformSuffix>
6+
<NerdbankGitVersioningTasksPath Condition=" '$(NerdbankGitVersioningTasksPath)' == '' ">$(MSBuildThisFileDirectory)$(_NBGV_PlatformSuffix)</NerdbankGitVersioningTasksPath>
7+
</PropertyGroup>
8+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
4+
<Import Project="Nerdbank.GitVersioning.Common.targets"/>
5+
6+
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.GetBuildVersion"/>
7+
8+
<Target Name="GetBuildVersion_Properties"
9+
DependsOnTargets="GetBuildVersionCore"
10+
Returns="@(NBGV_PropertyItems)" />
11+
12+
<Target Name="GetBuildVersion_CloudBuildVersionVars"
13+
DependsOnTargets="GetBuildVersionCore"
14+
Returns="@(CloudBuildVersionVars)" />
15+
16+
<Target Name="GetBuildVersionCore">
17+
<Nerdbank.GitVersioning.Tasks.GetBuildVersion
18+
BuildingRef="$(_NBGV_BuildingRef)"
19+
BuildMetadata="$(BuildMetadata.Replace(',',';'))"
20+
DefaultPublicRelease="$(PublicRelease)"
21+
ProjectDirectory="$(ProjectDirectory)"
22+
GitRepoRoot="$(GitRepoRoot)"
23+
ProjectPathRelativeToGitRepoRoot="$(ProjectPathRelativeToGitRepoRoot)"
24+
OverrideBuildNumberOffset="$(OverrideBuildNumberOffset)"
25+
TargetsPath="$(MSBuildThisFileDirectory)">
26+
27+
<!-- All properties and items are to be exported to the calling project through items. -->
28+
<Output TaskParameter="OutputProperties" ItemName="NBGV_PropertyItems"/>
29+
<Output TaskParameter="CloudBuildVersionVars" ItemName="CloudBuildVersionVars" />
30+
31+
<!-- Export a couple of properties directly to support our tasks below. -->
32+
<Output TaskParameter="AssemblyInformationalVersion" PropertyName="AssemblyInformationalVersion" />
33+
<Output TaskParameter="NuGetPackageVersion" PropertyName="NuGetPackageVersion" />
34+
<Output TaskParameter="Version" PropertyName="BuildVersion" />
35+
<Output TaskParameter="GitCommitId" PropertyName="GitCommitId" />
36+
</Nerdbank.GitVersioning.Tasks.GetBuildVersion>
37+
38+
<Warning Condition=" '$(AssemblyInformationalVersion)' == '' " Text="Unable to determine the git HEAD commit ID to use for informational version number." />
39+
<Message Condition=" '$(AssemblyInformationalVersion)' != '' " Text="Building version $(BuildVersion) from commit $(GitCommitId)"/>
40+
<Message Condition=" '$(AssemblyInformationalVersion)' == '' " Text="Building version $(BuildVersion)"/>
41+
<Message Importance="low" Text="AssemblyInformationalVersion: $(AssemblyInformationalVersion)" />
42+
<Message Importance="low" Text="NuGetPackageVersion: $(NuGetPackageVersion)" />
43+
</Target>
44+
45+
</Project>

0 commit comments

Comments
 (0)