Table of Contents

About

The Ubiquity.NET.Versioning.Build.Tasks package provides automated support for build versioning using a Constrained Semantic Version (CSemVer).

Warning

As a Breaking change in .NET SDK 8 is now setting the build meta data for the InformationalVersion property automatically and without user consent. (A Highly controversial choice that was more easily handled via an OPT-IN pattern) Unfortunately, this was set ON by default and made into an 'OPT-OUT' scenario. This library will honor such a setting and does not alter/interfere with it in any way. (Though the results can, unfortunately, produce surprising behavior as it is not well documented).

If you wish to disable this behavior you can set an MSBUILD property to OPT-OUT as follows:
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>

This choice of ignoring the additional data is considered to have the least impact on those who are aware of the change and those who use this library to set an explicit build meta data string. (Principle of least surprise for what this library can control).

The default behavior added in this breaking change is to use the Repository ID (usually a GIT commit hash [FULL SHA form!]) as the build metadata. This is appended with a leading + if one isn't already in the InformationalVersion property. If build metadata is already included (Like from use of this task) the id is appended using a . instead. So it is ALWAYS appended unless the project has opted out of this behavior by setting the property as previously described.

Thus, it is strongly recommended that projects using this package OPT-OUT of the new behavior.

Overview

Officially, SemVer 2.0 doesn't consider or account for publicly available CI builds. SemVer is only concerned with official releases. This makes CI builds producing versioned packages challenging. Fortunately, someone has already defined a solution to using SemVer in a specially constrained way to ensure compatibility, while also allowing for automated CI builds. These new versions are called a Constrained Semantic Version (CSemVer).

A CSemVer is unique for each CI build and always increments while still supporting official releases. However, in the real world, there are often cases where there are additional builds that are distinct from official releases and CI builds. These include local developer builds and builds generated from a Pull Request (a.k.a Automated buddy build). Neither SemVer, nor CSemVer explicitly define any format for these cases. So this library defines a pattern of versioning that is fully compatible with CSemVer and allows for the additional build types in a way that retains precedence having the least surprising consequences. In particular, local build versions have a higher precedence than CI or release versions if all other components of the version match. This ensures that what you are building includes the dependent components you just built instead of the last one released publicly.

The following is a list of the version formats in descending order of precedence:

Build Type Format
Local build {BuildMajor}.{BuildMinor}.{BuildPatch}--ci-{CiBuildIndex}-ZZZ+{BuildMeta}
Pull Request {BuildMajor}.{BuildMinor}.{BuildPatch}--ci-{CiBuildIndex}-PRQ+{BuildMeta}
Official CI builds {BuildMajor}.{BuildMinor}.{BuildPatch}--ci-{CiBuildIndex}-BLD+{BuildMeta}
Official PreRelease {BuildMajor}.{BuildMinor}.{BuildPatch}-{PreReleaseName}[.PreReleaseNumber][.PreReleaseFix]+{BuildMeta}
Official Release {BuildMajor}.{BuildMinor}.{BuildPatch}+{BuildMeta}

That is the, CI BuildName is chosen to ensure the ordering matches the expected behavior for a build.

This project provides an MSBUILD task to automate the generation of these versions in an easy to use NuGet Package.

The package creates File and Assembly Versions and defines the appropriate MsBuild properties so the build will automatically incorporate them.

NOTE:
The automatic use of MsBuild properties requires using the new SDK attribute support for .NET projects. Where the build auto generates the assembly info. If you are using some other means to auto generate the assembly level versioning attributes. You can use the properties generated by this package to generate the attributes.

File and AssemblyVersions are computed based on the CSemVer "Ordered version", which is a 64 bit value that maps to a standard windows FILE VERSION Quad with each part consuming 16 bits. This ensures a strong relationship between the assembly/file versions and the packages as well as ensures that CI builds can function properly. Furthermore, this guarantees that each build has a different file and assembly version so that strong name signing functions properly to enable loading different versions in the same process.

Important

A file version quad representation of a CSemVer does NOT carry with it the CI information nor the build metadata. It only contains a single bit to indicate a Release vs. a CI build. In fact, the official CSemVer specs are silent on the use of this bit though the "playground" does indicate an ODD numbered revision is a CI build. However, that leads to problems (see this: Spec issue for more details). Thus, the Ubiquity.NET.Versioning.* libraries do NOT work that way. In these libraries, an odd numbered revision indicates a RELEASE build whereas an EVEN numbered form represents a CI build. This results in the correct behavior for comparisons of the File version. The file version comparisons MUST result in correct ordering or an underlying Windows OS and related APIs will NOT behave as expected. (A CI build is ALWAYS ordered less then a release build)

The Major, Minor and Patch versions are only updated in the primary branch at the time of a release. This ensures the concept that SemVer versions define released products. The version numbers used are stored in the repository in the BuildVersion.xml

Properties used to determine the version

CSemVer.Build uses MSBuild properties to determine the final version number.

Name Default Value Description
BuildMajor Read from BuildVersion.xml Major portion of the build number
BuildMinor Read from BuildVersion.xml Minor portion of the build number
BuildPatch Read from BuildVersion.xml Patch portion of the build number
PreReleaseName <Undefined> or value read from BuildVersion.xml if present PreRelease Name of the CSemVer
PreReleaseNumber <Undefined> or value read from BuildVersion.xml if present PreRelease Number of the CSemVer
PreReleaseFix <Undefined> or value read from BuildVersion.xml if present PreRelease Fix of the CSemVer
BuildMeta <undefined> Build meta for the version
CiBuildIndex ISO 8601 formated UTC time-stamp for the build or the commit ID for automated builds Provides a unique build to build value guaranteed to increase with each build
CiBuildName <see notes> CSemVer CI name
PackageVersionUsesShortForm <Undefined> => false Determines if the PackageVersion is set to the short form for legacy NuGet clients
GeneratedVersionInfoHeader <undefined> Full path of the header file to generate [No header generated if not set] [Only applies to a VCXPROJ file ]
GenerateAssemblyInfo <undefined> If set, creates $(IntermediateOutputPath)AssemblyVersionInfo.g.cs and includes it for compilation. Used for legacy CSPROJ files as new SDK projects handle this automatically now

CiBuildName

Unless explicitly provided, the CiBuildName is determined by a set of properties that indicate the nature of the build. The properties used (in evaluation order) are:

Name Default Value CiBuildName Description
IsPullRequestBuild <Undefined> PRQ if true Used to indicate if the build is from a pull request
IsAutomatedBuild <Undefined> BLD if true Used to indicate if the build is an automated build
IsReleaseBuild <Undefined> ZZZ if !true Used to indicate if the build is an official release build

These three values are determined by the automated build in some form. These are either explicit variables set for the build definition or determined on the fly based on values set by the build. Commonly a directory.build.props for a repository will specify these. The following is an example for setting them based on an AppVeyor build in the Directory.Build.props file:

<PropertyGroup>
    <!-- If running in APPVEYOR it is an automated build -->
    <IsAutomatedBuild Condition="'$(IsAutomatedBuild)'=='' AND '$(APPVEYOR)'!=''">true</IsAutomatedBuild>
    <IsAutomatedBuild Condition="'$(IsAutomatedBuild)'==''">false</IsAutomatedBuild>

    <!-- If it has a PR number associated it is a PR build -->
    <IsPullRequestBuild Condition="'$(IsPullRequestBuild)'=='' AND '$(APPVEYOR_PULL_REQUEST_NUMBER)'!=''">true</IsPullRequestBuild>
    <IsPullRequestBuild Condition="'$(IsPullRequestBuild)'==''">false</IsPullRequestBuild>

    <!-- Tags applied without a PR are release builds -->
    <IsReleaseBuild Condition="'$(IsReleaseBuild)'=='' AND '$(APPVEYOR_REPO_TAG)'=='true' AND '$(APPVEYOR_PULL_REQUEST_NUMBER)'==''">true</IsReleaseBuild>
    <IsReleaseBuild Condition="'$(IsReleaseBuild)'==''">false</IsReleaseBuild>
</PropertyGroup>

PackageVersionUsesShortForm

The MSBuild Property PackageVersionUsesShortForm is used by the task to determine the format to use for the PackageVersion property. The default is to use the long form supported by modern NuGet clients, however if your are in need of the legacy format support a project may set this property to true to observe the legacy behavior.

BuildVersion.xml

If the MSBuild property BuildMajor is not set, then the base build version is read from the repository file specified in the BuildVersion.xml, typically this is located at the root of a repository so that any child projects share the same versioning information. The location of the file is specified by an MSBuild property BuildVersionXml. The contents of the file are fairly simple and only requires a single BuildVersionData element with a set of attributes. The available attributes are:

Name Description
BuildMajor Major portion of the build number
BuildMinor Minor portion of the build number
BuildPatch Patch portion of the build number
PreReleaseName PreRelease Name of the CSemVer
PreReleaseNumber PreRelease Number of the CSemVer
PreReleaseFix PreRelease Fix of the CSemVer

Only the Major, minor and Patch numbers are required. Example:

<BuildVersionData
    BuildMajor = "5"
    BuildMinor = "0"
    BuildPatch = "0"
    PreReleaseName = "alpha"
/>

Generated Properties

Name Description
BuildTime Set to the current time (UTC ISO-8601 format) if not already set by build tooling)
IsAutomatedBuild Automated build system value to indicate this is an automated build
IsPullRequestBuild Automated build system value to indicate this is a build for an untrusted PR
IsReleaseBuild Automated build system value to indicate this is an official release build (No CI information)
CiBuildName If not set externally, this is set based on the kind of build
CiBuildIndex If not set externally, this is set based on the $(BuildTime) property by parsing the ISO-8601 string and computing an index from that
BuildMajor Major portion of the build; If not set externally, this is set based on the information in the $(BuildVersionXml) file
BuildMinor Minor portion of the build; If not set externally, this is set based on the information in the $(BuildVersionXml) file
BuildPatch Patch portion of the build; If not set externally, this is set based on the information in the $(BuildVersionXml) file
PreReleaseName PreRelease Name for the build [Optional]; If not set externally, this is set based on the information in the $(BuildVersionXml) file, which may not include a value for this
PreReleaseNumber PreRelease Number for the build [Optional]; If not set externally, this is set based on the information in the $(BuildVersionXml) file, which may not include a value for this
PreReleaseFix PreRelease Fix for the build [Optional]; If not set externally, this is set based on the information in the $(BuildVersionXml) file, which may not include a value for this
FullBuildNumber String form of the full CSemVer value for a build
ShortBuildNumber Short form of the CSemVer for use with legacy NuGet clients (Modern clients support the full name)
FileVersionMajor Major portion of the FileVersion number (Used for vcxproj files to generate the version header)
FileVersionMinor Minor portion of the FileVersion number (Used for vcxproj files to generate the version header)
FileVersionBuild Build portion of the FileVersion number (Used for vcxproj files to generate the version header)
FileVersionRevision Revision portion of the FileVersion number (Used for vcxproj files to generate the version header)

BuildTime

Ordinarily this is set for an entire solution by build scripting to ensure that all components using this build task report the same version number. If it is not set the current time at the moment of property evaluation for a project is used. This will result in a distinct CI version for each project in a solution. Whether, that is desired or not is left for the consumer. If it is not desired, then a centralized setting as a build property or environment variable is warranted.

Automated build flags

IsAutomatedBuild, IsPullRequestBuild, and IsReleaseBuild are normally set by an automated build script/action based on the build environment used and aid in determining the CI build name as previously described in CiBuildName.

CiBuildName

If not explicitly set this is determined by the automated build flags as described in the CiBuildName section of this document.

Detected Error Conditions

Targets file

Code Description
CSM001 BuildMajor is a required property, either set it as a global or in the build version XML
CSM002 BuildMinor is a required property, either set it as a global or in the build version XML
CSM003 BuildPatch is a required property, either set it as a global or in the build version XML
CSM004 FileVersion property not provided AND FileVersionMajor property not found to create it from
CSM005 FileVersion property not provided AND FileVersionMinor property not found to create it from
CSM006 FileVersion property not provided AND FileVersionBuild property not found to create it from
CSM007 FileVersion property not provided AND FileVersionRevision property not found to create it from

CreateVersionInfo Task

Code Description
CSM100 BuildMajor value must be in range [0-99999]
CSM101 BuildMinor value must be in range [0-49999]
CSM102 BuildPatch value must be in range [0-9999]
CSM103 PreReleaseName is unknown
CSM104 PreReleaseNumber value must be in range [0-99]
CSM105 PreReleaseFix value must be in range [0-99]
CSM1061 If CiBuildIndex is set then CiBuildName must also be set; If CiBuildIndex is NOT set then CiBuildName must not be set.
CSM107 CiBuildIndex does not match syntax defined by CSemVer
CSM108 CiBuildName does not match syntax defined by CSemVer

ParseBuildVersionXml Task

Code Description
CSM200 BuildVersionXml is required and must not be all whitespace
CSM201 Specified BuildVersionXml does not exist $(BuildVersionXml)
CSM202 BuildVersionData element does not exist in $(BuildVersionXml)
CSM203 [Warning] Unexpected attribute on BuildVersionData Element
CSM204 XML format of file specified by `$(BuildVersionXml)' is invalid

1 CSM106 is essentially an internal sanity test. The props/targets files ensure that $(CiBuildIndex) and $(CiBuildName) have a value unless $(IsReleaseBuild) is set. In that case the targets file will force them to empty. So, there's no way to test for or hit this condition without completely replacing/bypassing the props/targets files for the task. Which is obviously, an unsupported scenario 😁.