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 😁.