BumpAssemblyVersions

Bumps assembly versions according to user provided specifications.


Keywords
version, versions, assembly, assemblies, bump, bumps, bumped, nuget, restore, restored, msbuild, provider, registry, may, should, reset, use, utc, universal, coordinated, time, stamp, timestamp, label, value, width, discard, create, new, include, wildcard, default, kind, file, files, informational, package, spec, specification, specifications, target, targets, template, major, minor, patch, build, release, prerelease, element, elements, import, item, group, more, significant, change, changed, single, multi, multiple, cross, targeting, dispatch, before, outer, inner, loop
License
GPL-3.0
Install
Install-Package BumpAssemblyVersions -Version 1.8.0

Documentation

BumpAssemblyVersions

BumpAssemblyVersions is an MSBuild 15 task that bumps the versions of a Visual Studio 2017 project. Testing done on CSharp projects only, i.e. .csproj or AssemblyInfo.cs files, at this time.

Objectives

I wanted to automate a seamless version bumping mechanism for my projects as I continued to work on them, yet with a variety of choices in terms of how I decided to bump the versions. Along these lines, I evaluated a couple of projects as potential fits for this purposes, both of which informed my approaching the problem at hand, but neither of which adequately solved the issue in its entirety, in my professional opinion, at least not without additional pain points.

I took at look at MSBump, which in and of itself, basically did what I wanted it to do. However, it lacked in terms of the versions I can influence, and in terms of the breadth of bumping strategies.

I also took at look at Precision Infinity's Automatic Versions 1, which did offer the breadth of versioning strategies, but which was too invasive, in my opinion, in terms of hooking into my projects, let alone the painful installation procedure.

Take aways from this, in and of itself, are, gone are the days of installed extensions, I think. If you are not delivering at least a VSIX, let alone a simple development only NuGet package, chances are your product will be left in the stone ages.

Given these two points, I drew from the best of both of these and delivered BumpAssemblyVersions, which has the flexibility of a development only NuGet available assembly version bumping package, as well as a breadth of version bumping strategies.

Usage

  1. Add the BumpAssemblyVersions NuGet package reference to your project.
  2. Edit your .csproj file with the appropriate assembly version instructions.

Caution: You may need to indicate PrivateAssets="All" to the PackageReference declaration in order to avoid carrying the BumpAssemblyVersions dependency with your project into unit testing and deployment phases.

Tested using NuGet v4.6+.

All versions are bumped just prior to the MSBuild Build step.

Instructional Options

There are several inputs that are considered during the version bumping process. We will cover the items first that should be in your project, followed by the specific instructions, followed by the options.

Version Bumping Form Factors

Generally speaking, two varieties of versions are considered during the version bumping task.

  1. AssemblyInfo based versions in the form of [assembly: AssemblyVersion("1.2.3.4")].
  2. Project file XML based versions in the form of <Version>1.2.3.4</Version> found in your .csproj files.

Default Version Bumping Candidates

A couple of default version bumping candidates are included out of the box:

  1. The custom task includes your .csproj file by default at first.
  2. The default set of included files also includes the AssemblyInfo.cs file when such a one exists.

Specifying Additional Files to Bump

Specifying additional files to bump is the simplest of use cases to consider. Remember, AssemblyInfo is not the only place where additional Assembly Attributes may be specified. The way to specify these in additional files is via the FilesToBump item, for example:

<ItemGroup>
    <FilesToBump Include="Properties\AdditionalAssemblyInfo.cs" />
</ItemGroup>

Note, these do not have to live in Properties nor do they have to be called AdditionalAssemblyInfo.cs. See also caveats [1] concerning mixing with different MSBuild conventions.

Bump Version Specifications

After the input files have been determined, then you must configure which versions to bump, and how you would like to bump them.

There are several known versions which may be bumped depending upon whether you are talking about the Project XML variety or the Assembly Attribute sort. These are enumerated as follows:

Version Kind Description
Version Usage with the .csproj file format, i.e. <Version>...</Version>.
FileVersion The .csproj analog to the AssemblyFileVersionAttribute.
InformationalVersion The .csproj analog to the AssemblyInformationalVersionAttribute.
PackageVersion Usage with the .csproj additions.
AssemblyVersion This one serves a dual purpose for both the .csproj file format, i.e. <AssemblyVersion>...</AssemblyVersion>, as well as for AssemblyVersionAttribute.
AssemblyFileVersion This one is for use with AssemblyFileVersionAttribute.
AssemblyInformationalVersion This one is for use with AssemblyInformationalVersionAttribute.

And which may be specified as follows, for example:

<ItemGroup>
    <BumpVersionSpec Include="FileVersion" ... />
</ItemGroup>

See caveats concerning mixing with different MSBuild conventions. Also refer to Microsoft documentation [12 13] concerning the project file format, assembly attributes, etc. For now, this is the best guidance I can offer anyone doing the math and figuring on connecting which versions with which other versions.

Bump Version Element Specifications

After you have determined the versions you would like to bump, the next thing is to decide on how you would like to bump those versions. To do this, there are four, sometimes five, version elements which you may configure, Major, Minor, Patch, Build, and, sometimes, Release.

You must choose not only the Version Provider, which we will postpone in discussion, but also the Version Provider Attributes, which require a little more in depth presentation up front.

Version elements in order of significants, most significant first:

Element Specification
Major MajorProviderTemplate
Minor MinorProviderTemplate
Patch PatchProviderTemplate
Build BuildProviderTemplate
Release ReleaseProviderTemplate

Version Provider Attributes

Unless otherwise indicated, attributes must all be prefixed with the desired Version Element, i.e. {Major|Minor|Patch|Build|Release}ProviderTemplate. The ProviderTemplate itself is always required, which prefixed element name must always be included.

Spec Attribute Values Description
CreateNew [6] true | false Indicates whether the version may be created afresh when it is discovered not to exist prior to build.
DefaultVersion [6] MAJOR.MINOR[.PATCH[.BUILD]][-RELEASE] Generally given in the specified form. MAJOR and MINOR elements are both required no matter what. PATCH and BUILD are both optional, but if BUILD is present, so must PATCH. RELEASE is entirely optional regardless.
IncludeWildcard [4, 5] true | false Indicates whether any of the elements may be the wildcard (*). This is assumed to be the last unspecified version element; for example, if Major and Minor elements are specified, then Patch is assumed to be the wildcard.
MayReset [4, 5] true | false Indicates whether the Version Element may reset. Reset occurs when any of the more significant elements have changed in any way.
UseUtc [4, 5] true | false Indicates whether to use Coordinated Universal Time, namely DateTime.UtcNow, or the converted universal time of the task at the time of the build.
Label [7] [a-zA-Z]+ Any textual label decorating the release label version provider.
ValueWidth [7] [0-9]+ Any integer numeric value indicating desired width of the release label incremental value.
ShouldDiscard [7] true | false Indicates whether the release label, whatever it happened to be, should be discarded.

Version Providers

The prefixed ProviderTemplate attribute is always required. The attribute value itself may be any unique prefix selecting one of the following Version Providers. For instance, DayOfYearVersionProvider, then you may shorten that to DayOfYear or even DayOf, and the task will select the correct provider. You may not, however, shorten that to Day as that would lead to a confusion between DayOfYearVersionProvider and DayVersionProvider, in this particular instance. Rinse and repeat for the several Version Elements.

Version Provider Description
DayOfYearVersionProvider [8, 11] Yields the Day of Year in the form d.
DayVersionProvider [8, 11] Yields the Day of the Month in the form d.
DeltaDays1900VersionProvider [8] Yields the number of Days that occurred since 1900.
DeltaDays1970VersionProvider [8] Yields the number of Days that occurred since 1970.
DeltaDays1980VersionProvider [8] Yields the number of Days that occurred since 1980.
DeltaDays1990VersionProvider [8] Yields the number of Days that occurred since 1990.
DeltaDays2000VersionProvider [8] Yields the number of Days that occurred since 2000.
DeltaDays2010VersionProvider [8] Yields the number of Days that occurred since 2010.
DeltaDays2020VersionProvider [8] Yields the number of Days that occurred since 2020.
HourMinuteMultipartVersionProvider [8, 11] Yields the Hour and Minute multipart element in the form hhMM.
IncrementVersionProvider [9]
MinutesSinceMidnightVersionProvider [8, 11] Yields the Minutes Since Midnight element.
MonthDayOfMonthMultipartVersionProvider [8, 11] Yields the Month and Day of the Month multipart element in the form MMDD.
MonthVersionProvider [8, 11] Yields the Month element in the form M.
NoOpVersionProvider [10] The No Op provider is used when no provider was specified. It assumes a pass through for whatever the version element might have been.
PreReleaseIncrementVersionProvider [9]
SecondsSinceMidnightVersionProvider [8, 11] Yields the Seconds Since Midnight element.
ShortYearDayOfYearMultipartVersionProvider [8, 11] Yields the Short Year and Day of Year multipart element in the form YYDDD.
ShortYearVersionProvider [8, 11] Yields the Short Year element in the form YY.
UnknownVersionProvider [10] The Unknown provider is used when an unknown specification was given by the consumer. There is no fall back position other than to assume failure at that point.
YearDayOfYearMultipartVersionProvider [8, 11] Yields the Year and Day of Year multipart element in the form YYYYDDD.
YearVersionProvider [8, 11] Yields the Year element in the form YYYY.

Examples

This is a lot of verbiage to digest, but I promise you it is not that difficult once you grasp the approach. All the same, let us illustrate with a couple of examples. These are not meant to be exhaustive by any means, but only suggestive as to how to approach usage in your own projects.

Refer to the Targets

The first thing you must do is specify the bump targets:

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    ...
    <Import Project="\path\to\package\build\BumpAssemblyVersions.targets" />
    ...
</Project>

For argument's sake, let's call \path\to\package\build something like $(ProjectDir)..\packages\BumpAssemblyVersions.1.0.1\build or path\to\your\global\packages\BumpAssemblyVersions\1.0.1\build, depending on how you add your package references, your version of NuGet, and so on. Basically, this is the relative path from your consumer project to the package. You may specify an absolute path if the package is landing elsewhere. You may use built-in predefined macros such as $(ProjectDir).

Refer to the targets similarly with the new project file format:

<Project Sdk="Microsoft.NET.Sdk">
    ...
    <Import Project="\path\to\package\build\BumpAssemblyVersions.targets" />
    ...
</Project>

If you are using the new style <PackageReference /> then it is likely the targets assets are already included and would not need to be imported. For example:

<ItemGroup>
    <PackageReference Include="BumpAssemblyVersions" Version="1.2.0" PrivateAssets="All">
        <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
        <!--                    ^^^^^ -->
    </PackageReference>
</ItemGroup>

Files to Bump

If you are building a legacy project format involving AssemblyInfo assembly attributes, these may not always exist in the AssemblyInfo.cs file, which is furnished by default. In this case, you may specify additional FilesToBump, such as:

<ItemGroup>
    <FilesToBump Condition="Exists('Properties\TestAssemblyInfo.cs')" Include="Properties\TestAssemblyInfo.cs" />
</ItemGroup>

In the above example, you may have a file called Properties\TestAssemblyInfo.cs in which additional assembly attributes have been specified. Assembly attributes may exist in any CSharp source file, so you are free to specify any number of additional FilesToBump beyond the default, conventional Properties\AssemblyInfo.cs.

Bump Version Specs

After that, it's all about furnishing your specification items. Here are just a few illustrative examples how you may approach the problem. We may note that the specifications may be furnished consistently, regardless whether we are talking about the new or old project file format. Let's have a look at the first example in a bit of depth.

<ItemGroup>
    <BumpVersionSpec Include="AssemblyVersion" UseUtc="false" IncludeWildcard="true"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
</ItemGroup>

In this example, AssemblyVersion is the indicated pattern, which serves a dual purposes for both legacy and the new project file versions in either the AssemblyVersionAttribute or the <AssemblyVersion /> project property. The specification will use local time since UseUtc is false, and it may IncludeWildcard. The PatchProviderTemplate is configured for Increment, which will select the IncrementVersionProvider, which also MayReset for this element only on account of the element specific notation. All other elements default to the NoOpVersionProvider passthrough.

We will not discuss the following examples in a great deal of further depth, only to present them and discuss a couple of highlights.

<ItemGroup>
    <BumpVersionSpec Include="AssemblyFileVersion" UseUtc="false"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
    <BumpVersionSpec Include="AssemblyInformationalVersion" UseUtc="false"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
</ItemGroup>

Both of these examples are specified for the assembly attributes AssemblyFileVersionAttribute and AssemblyInformationalVersionAttribute, respectively. Otherwise, the specification is consistent with the first.

<ItemGroup>
    <BumpVersionSpec Include="FileVersion" UseUtc="false"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
    <BumpVersionSpec Include="InformationalVersion" UseUtc="false"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
</ItemGroup>

Both of these examples are specified for the new project file format <FileVersion /> and <InformationalVersion />, respectively. Otherwise, the specification is consistent with the first.

Again, this is by no means an exhaustive set of examples, but only furnished for illustration purposes.

End to End Examples

Let's examine a couple of end to end, live fire examples, so to speak. These are actual projects that I've committed to the repository and which make references to the actual package which I published on my local system.

BumpAssemblyVersions.Usage.Net472

First, let's take a look at the BumpAssemblyVersions.Usage.Net472 project. I was pleasantly surprised to discover a build preparation task which is injected upon subscription.

<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
        <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.props'))" />
    <Error Condition="!Exists('..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets'))" />
</Target>

Then we automatically import the targets when they are determined to exist:

<Import Project="..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets"
        Condition="Exists('..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets')" />

Then we make the specifications. In this case, we will also make the specification condition in the same way the import is conditional. These are all decisions that you should make for yourselves, but it does seem like good practice to me:

<ItemGroup Condition="Exists('..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets')">
    <BumpVersionSpec Include="AssemblyVersion" IncludeWildcard="true"
                     PatchProviderTemplate="Increment" PatchProviderTemplateMayReset="true" />
    <BumpVersionSpec Include="AssemblyFileVersion" UseUtc="true" PatchProviderTemplate="MonthDayOfMonth"
                     BuildProviderTemplate="Increment" BuildProviderTemplateMayReset="true" />
    <BumpVersionSpec Include="AssemblyInformationalVersion" UseUtc="true" PatchProviderTemplate="MonthDayOfMonth"
                     BuildProviderTemplate="Increment" BuildProviderTemplateMayReset="true" />
</ItemGroup>

We see a lot of the same elements here that we have explained in previous examples, so I will not dwell on those. The AssemblyVersion specification is fairly self-explanatory. We see AssemblyFileVersion and AssemblyInformationalVersion incorporate the MonthDayOfMonthMultipartVersionProvider via the MonthDayOfMonth shorthand. Instead of resetting the Patch specification, we see that pattern declared for the BuildProviderTemplate. These have all been tested and work very well.

BumpAssemblyVersions.Usage.NetStandard

Next, let's take a look at the BumpAssemblyVersions.Usage.NetStandard project. First, we import the development only package reference.

<Import Project="$(ProjectDir)..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets"
        Condition="Exists('$(ProjectDir)..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets')" />

Next, we make the appropriate specifications:

<ItemGroup Condition="Exists('$(ProjectDir)..\packages\BumpAssemblyVersions.1.0.1\build\BumpAssemblyVersions.targets')">
  <BumpVersionSpec Include="Version" DefaultVersion="0.0.0.0" CreateNew="true" UseUtc="true" MayReset="true"
                   MajorProviderTemplate="YearVersion" MinorProviderTemplate="DayOfYear"
                   PatchProviderTemplate="HourMinute" BuildProviderTemplate="Increment" />
  <BumpVersionSpec Include="AssemblyVersion" DefaultVersion="0.0.0.0" CreateNew="true" UseUtc="true" MayReset="true"
                   MajorProviderTemplate="YearVersion" MinorProviderTemplate="DayOfYear"
                   PatchProviderTemplate="HourMinute" BuildProviderTemplate="Increment" />
  <BumpVersionSpec Include="FileVersion" DefaultVersion="0.0.0.0" CreateNew="true" UseUtc="true" MayReset="true"
                   MajorProviderTemplate="YearVersion" MinorProviderTemplate="DayOfYear"
                   PatchProviderTemplate="HourMinute" BuildProviderTemplate="Increment" />
  <BumpVersionSpec Include="InformationalVersion" DefaultVersion="0.0.0.0" CreateNew="true" UseUtc="true" MayReset="true"
                   MajorProviderTemplate="YearVersion" MinorProviderTemplate="DayOfYear"
                   PatchProviderTemplate="HourMinute" BuildProviderTemplate="Increment" />
  <BumpVersionSpec Include="PackageVersion" DefaultVersion="0.0.0.0" CreateNew="true" UseUtc="true" MayReset="true"
                   MajorProviderTemplate="YearVersion" MinorProviderTemplate="DayOfYear"
                   PatchProviderTemplate="HourMinute" BuildProviderTemplate="Increment" />
</ItemGroup>

Couple of points to make here. The initial project contained no version elements whatsoever. This tested that CreateNew does in fact work; which it did.

Next, MayReset now works as expected. This was a gap I had been meaning to correct, but had not quite gotten around to. It was simple enough to correct, and does work very well. Other than that, each of the templates and their specifications are working as expected.

An example of the resulting elements is as follows. Completely new PropertyGroup elements were injected for each version, which is perfectly fine for the time being. You may decide to clean that up in your projects before, during, or after your usage, but this is completely left to your discretion to do so.

<PropertyGroup>
    <PackageVersion>2018.249.1429.0</PackageVersion>
    <InformationalVersion>2018.249.1429.0</InformationalVersion>
    <FileVersion>2018.249.1429.0</FileVersion>
    <AssemblyVersion>2018.249.1429.0</AssemblyVersion>
    <Version>2018.249.1429.0</Version>
</PropertyGroup>

Known Issues

  • We discovered during usage testing that BAV gets confused when there is a hybrid usage involved, that is, some versions originating from .csproj and others originating from an AssemblyInfo.cs Attribute. We do not have a solution in mind for that at the moment, but we can say that containing your adoption to one approach or the other seems just fine for operational usage.

[1] With any of these inputs, usual MSBuild Condition rules apply, so you may mix and match the specified files to bump depending on Configuration or other criteria at your discretion.

[2] Not all Bump Version Specifications support wildcards; it is left to consumer discretion when to leverage wildcards.

[3] Not all Bump Version Specifications allow support for release labels; it is left to consumer discretion when to release labels.

[4] Attributes provided without Version Provider Prefixes [5] apply for all Elements given by the Specification.

[5] Some Bump Version Attributes may be specified for individual Version Providers as follows: [Major|Minor|Patch|Template|Release]ProviderTemplate]AttributeName. As indicated here, this prefix is optional; when not specified, the attribute applies for the whole Specification.

[6] These attributes are all Specification only attributes. That is, they apply for the Specification as a whole and are not part of the individual Elements specifications.

[7] These attributes apply only for the PreReleaseIncrementVersionProvider.

[8] All Version Providers support a Timestamp, but some depend on it as essential to their operation. In addition, you may specify whether to UseUtc either for the individual Elements or for the Specification as a whole.

[9] Some Version Providers support whether it May Reset, either for the individual Elements or for the Specification as a whole. If any of the more significant providers Changed in any way, then the affected provider will reset.

[10] Some Version Providers are For Internal Use Only.

[11] All version providers yield right justified zero padded textual results using an intuitive, appropriate number of digits.

[12] Document AssemblyInfo properties

[13] http://github.com/dotnet/sdk/blob/master/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.GenerateAssemblyInfo.targets