.NET Core Ecosystem v2 - Versioning
|10/03/2018||The implementation doesn't include the SHA on the version strings anymore, because: 1) The chaining of targets to plug that into SemVer1 strings were causing generation of incorrect NuSpec files and no clear path to fix it was found and, 2) VS CoreXT build system doesn't support it.|
.NET Core repositories should use SemVer2 for their asset versioning scheme. Package versions take the general form of:
MAJOR, MINOR, and PATCH versions are rigid in their requirements. Please refer to the SemVer2 documentation for details. PRERELEASE and BUILD are optional and leave a fair bit of room for organizations to implement what they want. The only caveat is that build metadata cannot be used to differentiate two different packages. Build metadata should not be used when determining version precedence. There are two primary questions:
- What goes in the PRERELEASE and BUILD fields and when are they used?
- Should we use date agnostic versioning vs. date varying versioning?
Date Agnostic vs. Date-Varying Build Versioning
In the context of the .NET Core product, a build version number is date agnostic if successive builds of the same commit produce the same build version numbers. Historically, .NET Core has had repositories that implement date agnostic versioning through build numbers that increment based on commit depth, as well as repositories that base their build number off of the date and number of builds prior on the day (date based versioning).
Both types of versioning have advantages and disadvantages:
Date Agnostic Versioning
- Assets with identical version metadata can be reproduced without outside input (e.g. providing a build id)
- Parallel, independent legs of the same build do not require parent orchestration and will generate coherent versioning. Separate parts of the build can potentially be respun as required.
- We already have 'date agnostic' versioning at the end of a product cycle.
- Build metadata typically includes SHA, which makes identification of the source of bits easy.
- Some related infrastructure systems may not support the ingestion of multiple assets with the same name/version. For example, MyGet, NuGet, or VSTS support overwriting to varying degrees. You're left to deal with these scenarios on a case-by-case basis. For example:
- Introduce a new meaningless commit to bump the version number
- Successive builds of the same SHA, if they were to have different outputs (e.g. a checked vs. debug build) may require a clean of the cache.
- Temporarily unlock a feed to enable re-publishing (may not be possible, and bad practice)
- Add logic into all processes to gracefully understand and handle overwrite cases (e.g. are bits the same? then okay)
- Telemetry systems must understand reruns of the same build.
- Most of the .NET Core builds aren't bit-for-bit identical at the same commit given the same inputs. If they were, then a partial-respin can gracefully handle dealing with overwrites.
- Date agnostic version doesn't encode inputs (e.g. checked build vs. release build). This can make it tricky to run 'non-standard' build scenarios if they interact with external systems.
- Generating the same package version in multiple builds with different outputs bits means that package caches must be cleared.
- Respins are easier. No overwriting except for release versions at the end of the product cycle. Release versions at the end of the cycle typically avoid external overwrite-averse systems.
- Non-standard builds (e.g. different input parameters) do not collide with standard builds at the same SHA when dealing with external systems.
- File versions must be ever increasing to correctly layout new files for servicing (MSI requirement)
- Does not affect the determinism of the build. Input dates are just another build parameter (e.g. OfficialBuildId) and rerunning a build with the same input date should produce equivalent binaries.
- Requires orchestration to produce a coherent set of versions across multiple build legs
- More difficult to reproduce version identical bits (need to know input parameters)
- Source SHA not easily identifiable based on asset version.
Date agnostic versioning is more hassle than it's worth, though having the SHA in the output version number is also useful. We should combine a SHA in the build metadata with the build date+revision (short data + number of builds so far today) to generate a date-varying, unique, identifiable build.
There is often concern around build determinism when date-varying versioning is used. It is important to note that date-varying versioning does not affect the ability to have deterministic builds in either the local dev or official build lab scenarios. The date is either a provided parameter or obtained from git information, meaning that setting it to a specific value at a specific commit can enable the production of the same outputs over and over again. Date varying versioning only says that by default this input varies from build to build.
Build kind is determined based on global build properties
|false||""||""||Local developer build|
|true||""||""||PR validation build|
||""||Daily official build|
||"prerelease"||Final pre-release official build|
||"release"||Release official build|
Version parts specified by repository
The repository specifies a 3-part version prefix in
VersionPrefix build property (MAJOR.MINOR.PATCH),
or 2 parts using
MinorVersion properties (the patch number defaults to 0 in such case).
If neither of these properties are specified the default is 1.0.0.
The versioning scheme defined below imposes the following limits on these version parts:
- MAJOR version is in range [0-65535]
- MINOR version is in range [0-654]
- PATCH version is in range [0-9999]
Package Version comprises of a three-part version number (PACKAGE_MAJOR, PACKAGE_MINOR, PACKAGE_PATCH) and optional pre-release labels.
The pre-release label is determined based on the following table:
|Build kind||Pre-release labels||Package Version example|
|Local developer build default||"dev"||"1.2.3-dev"|
|PR validation build||"ci"||"1.2.3-ci"|
|Daily official build||PRERELEASE_LABELS.SHORT_DATE.REVISION||"1.2.3-preview.1.12345.1"|
|Final pre-release official build||PRERELEASE_LABELS."final"||"1.2.3-beta.1.final"|
|Release official build||""||"1.2.3"|
In official builds, the value of PRERELEASE_LABELS is derived as in the following way:
SemanticVersioningV1is set to true, it will be
PreReleaseVersionIterationmay be empty
SemanticVersioningV1is not set to true, and
PreReleaseVersionIterationis empty, it will be
SemanticVersioningV1is not set to true, and
PreReleaseVersionIterationis non empty, it will be
In official builds the values of SHORT_DATE and REVISION are derived from build parameter
OfficialBuildId with format
20yymmdd.r like so:
- REVISION is set to
- SHORT_DATE is set to
yy* 1000 + 50 *
dd. In year 2018 the value is in range [18051, 18631].
In PR validation and local developer builds SHORT_DATE and REVISION are not included in the package version,
DotNetUseShippingVersion is true, in which case the values of
dd are derived from the current date
r = 1. Note that non-official builds with
DotNetUseShippingVersion set to true are non-deterministic.
If a package is designated to be a release-only package (
PreReleaseVersionLabel is empty) its package version does not include
any pre-release labels when produced by an official build. Every official build of such package must produce a unique PATCH_NUMBER.
This versioning policy is not applied to developer and PR validation builds as it relies on the availability of a unique
Some projects want to remain producing pre-release packages even if the repository is running a final stable build because
they don't ship or aren't ready to ship stable. Those projects can set
SuppressFinalPackageVersion property to
PATCH_NUMBER is defined as (SHORT_DATE -
VersionBaseShortDate) * 100 +
Repository shall only change the value of
VersionBaseShortDate at the same time as it increments MAJOR or MINOR version.
The three-part package version is based on the value of
|Package version part||Value||Condition|
Assembly Version is a four-part version number (ASSEMBLY_MAJOR, ASSEMBLY_MINOR, ASSEMBLY_PATCH, ASSEMBLY_REVISION).
PATCH_NUMBER_HI is defined as PATCH_NUMBER / 50000.
PATCH_NUMBER_LO is defined as PATCH_NUMBER % 50000.
|Assembly version part||Value||Condition|
File Version is a four-part version number (FILE_MAJOR.FILE_MINOR.FILE_PATCH.FILE_REVISION) and must increase every official build. This is especially important when building MSIs.
If build property
AutoGenerateAssemblyVersion is true then File Version is the same as Assembly Version, otherwise:
|File version part||Value|
|FILE_MINOR||MINOR * 100 + PATCH / 100|
|FILE_PATCH||(PATCH % 100) * 100 +
It is recommended for global tools projects to build assemblies with auto-generated assembly version and pack as release-only packages,
AutoGenerateAssemblyVersion to true and clear
It is recommended for msbuild task projects and projects building analyzers to build assemblies with auto-generated assembly version,
AutoGenerateAssemblyVersion to true.
Library projects that target .NET Standard or .NET Framework shall keep
AutoGenerateAssemblyVersion set to false to avoid the need for updating binding redirects of the consuming .NET Framework apps every time a new build is consumed.
In cases where SemVer2 cannot be used (e.g. old versions of NuGet), we can fall back to SemVer1.
In SemVer1, there is no built in build metadata, and the pre-release field may only contain
[0-9A-Za-z-]. To comply, cases where
are used in SemVer2's prerelease field are replaced with
The repository opts into SemVer1 fallback by setting
SemanticVersioningV1 property to true.
Examples of SemVer1 package versions:
"1.2.3-dev", "1.2.3-ci", "1.2.3-beta-12345-01", "1.2.3-beta-final", "1.2.3".
Note that the number in
-01 suffix is zero-padded to two characters.
Assembly Informational Version Generation
Assembly informational version is generated by .NET SDK. If the project references SourceLink package (added by Arcade SDK by default) the assembly informational version will include the full commit SHA. An example of a version string that the assembly will include:
NuGet Package Repository Information
Packages produced by projects that reference SourceLink package and use standard NuGet Pack target for packaging will include repository URL and commit SHA metadata in their nuspec files:
<repository type="git" url="https://github.com/dotnet/roslyn" commit="d37ac834f69e1a771626813da3b820502309462d" />
The Arcade SDK implements here the Versioning schema described in this document. This section provides a brief outline of how to use the implementation and how it works. More documentation is available in the respective .target/.props file.
Arcade onboarded repos use this implementation automatically by using the Arcade SDK. If you use the SDK you won't have to do anything else to use the format strings described in the proposal.
Below is a list of the main parameters that control the logic.
|OfficialBuildId||Arcade||ID of current build. The accepted format is
|DotNetFinalVersionKind||Arcade||Specify the kind of version being generated:
|PreReleaseVersionLabel||Arcade||Pre-release label to be used on the string. E.g.,
|PreReleaseVersionIteration||Arcade||Numeric pre-release iteration to be used on the pre-release suffix string. E.g.,
|VersionPrefix||.NET||Specify the leading part of the version string. If empty and both
|MajorVersion||Arcade||Major version to use in
|MinorVersion||Arcade||Minor version to use in
|ContinuousIntegrationBuild||.NET||Specify whether the build is happening on a CI server (PR build or official build).|
This is the list of properties set by Arcade SDK versioning implementation. The values are available only after
GetAssemblyVersion target has been executed.
|OfficialBuild||Arcade||Boolean indicating that the current build is official. Set to
|VersionSuffix||.NET||The suffix part of the version string, including the pre-release portion.|