Skip to content

Instantly share code, notes, and snippets.

@davkean
Created June 29, 2016 23:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davkean/593f96c1d3d9d6f668461c011eceb3d0 to your computer and use it in GitHub Desktop.
Save davkean/593f96c1d3d9d6f668461c011eceb3d0 to your computer and use it in GitHub Desktop.
Versioning...
.NET package versioning follows a stricter form of SemVer 2.0.
1) Non-experimental package versions MUST be in the form of MAJOR.MINOR.PATCH[-PRERELEASE.BUILDNUMBER].
Where we increment:
MAJOR when:
- we drop support for a platform
- we adopt a newer MAJOR version of an existing dependency
- We turn a quirk off by default
MINOR when:
- add public API surface area
- we add new behavior
- we adopt a newer MINOR version of an existing dependency
- we introduce a new dependency
[unresolved: What if we introduce a new dependency which causes an app to have to upgrade a dependency to new a major version? Perhaps a new dependency should be a major version?]
PATCH when:
- we make bug fixes
- add support for a newer platform
- adopt a newer PATCH version of an existing dependency
- anything that we change that is not listed above
When determining what to increment when there are multiple changes, choose the highest severity.
For pre-release packages:
PRERELEASE is one of alpha, beta or rc. Packages should start with alpha.
BUILDNUMBER is an ever increasing build number. Can be reset to 0 when MINOR is incremented.
Special restrictions for packages containing app-local .NET Framework facades:
MAJOR must be at least 4 (Why?)
MINOR must be at least 1 if MAJOR is 4
2) Experimental packages MUST be in the form of 0.MINOR.PATCH-exp.BUILDNUMBER
3) Assembly/contracts versions MUST be in the form of MAJOR.MINOR.PATCH.REVISION, where:
MAJOR.MINOR.PATCH match package MAJOR.MINOR.PATCH
REVISION is reserved and should be zero (Why?)
4) File versions MUST be in the form of MAJOR.MINOR.PATCH.REVISION, where:
MAJOR.MINOR.PATCH match package MAJOR.MINOR.PATCH
REVISION matches package BUILDNUMBER
FAQ:
1) What is the lifecycle of a package release?
At the start of a release, we pick the version of the package that we want to ship (making use of the above rules). Then throughout the product cycle, the version progresses like so:
Patch (4.0.1):
Package Version Assembly/Contract Version File Version
4.0.1-alpha.1235 4.0.1.0 4.0.1.1235
4.0.1-alpha.1236 4.0.1.0 4.0.1.1236
4.0.1-beta.1237 4.0.1.0 4.0.1.1237
4.0.1-beta.1238 4.0.1.0 4.0.1.1238
4.0.1-beta.1239 4.0.1.0 4.0.1.1239
4.0.1-rc.1240 4.0.1.0 4.0.1.1240
4.0.1-rc.1241 4.0.1.0 4.0.1.1241
4.0.1 4.0.1.0 4.0.1.1242
Minor (4.1.0):
Package Assembly/Contract Version File Version
4.1.0-alpha.1243 4.1.0.0 4.1.0.1243
4.1.0-beta.1244 4.1.0.0 4.1.0.1244
4.1.0-beta.1245 4.1.0.0 4.1.0.1245
4.1.0-rc.1246 4.1.0.0 4.1.0.1246
4.1.0-rc.1247 4.1.0.0 4.1.0.1247
4.1.0 4.1.0.0 4.1.0.1248
Major (5.0.0):
Package Assembly/Contract Version File Version
5.0.0-alpha.1249 5.0.0.0 5.0.0.1249
5.0.0-alpha.1250 5.0.0.0 5.0.0.1250
5.0.0-beta.1251 5.0.0.0 5.0.0.1251
5.0.0-beta.1252 5.0.0.0 5.0.0.1252
5.0.0-beta.1253 5.0.0.0 5.0.0.1253
5.0.0-rc.1254 5.0.0.0 5.0.0.1254
5.0.0-rc.1255 5.0.0.0 5.0.0.1255
5.0.0-rc.1256 5.0.0.0 5.0.0.1256
5.0.0-rc.1257 5.0.0.0 5.0.0.1257
5.0.0 5.0.0.0 5.0.0.1258
2) What do we consider as "dropping support for a platform"?
Removing a supported platform, is when we prevent a package from being installed to a project targeting, or prevent a package from running on a particular platform either directly, or indirectly.
Examples of directly dropping support for a platform:
Dropping Silverlight support
Dropping .NET Framework 3.5 (4.0 is not a drop-in replacement)
Dropping Windows Vista
Dropping Itanium or x86
Dropping Visual Studio 2012
Examples of indirectly dropping support for a platform:
Dropping NuGet 2.x (indirectly drops Visual Studio 2010)
Dropping .NET Framework 4.0 (indirectly drops Windows XP, Itanium)
Not examples of dropping a supported platform:
Dropping .NET Framework 4.5 (4.5.1/4.5.2/4.5.3 are compatible in-place releases)
[unresolved: There's feedback that updating to 4.5.1 from 4.5 should be considered a MAJOR]
Dropping Silverlight 4 (Silverlight 5 is a compatible in-place release)
Dropping NuGet 2.5 (2.6 is a compatible in-place release)
3) Why must the RTM version of an assembly be unique?
This enables us to uniquely identify, and if needed for a MSRC case, patch individual packages and assemblies. Without unique versions, we could inadvertently break an application by pushing them a version of an assembly that might contain behavioral, breaking changes or additional dependencies that are not present. For .NET Framework, we service using publisher policy, which is based on an assembly boundary. For ASP.NET vNext, servicing, while not yet defined, will likely be package based servicing.
4) But then why does it remain the same between pre-releases and RTM?
It's a tradeoff. We could choose to differentiate between pre-releases and RTM (by bumping revision), however, we feel the need for friendlier assembly names overrule this. If, on the off chance we decide that we want to service pre-releases affected by a security bug, we would roll them forward to RTM, which may or may not have a compatibility impact.
5) Why is the revision component (ie n in 4.0.0.n) reserved for an assembly/file version?
NuGet, when following SemVer 2.0, will not respect traditional four part versions for package versions, so while 1.2.3-beta.123 is legal, 1.2.3.4-beta.123 is not. This leaves the first three components to indicate the scope of a change, and excludes revision. While we could consider putting the build number in this field, we believe that this would "dirty" the assembly version number and make it harder for customers to deal with them when they are written in binding redirects, error messages and within Visual Studio.
However, we still reserve the right to use revision for app-local servicing as a last resort. Imagine this scenario:
We ship a package, System.Net.Http, 5 times from 1.0.0 -> 1.0.9. After shipping 1.0.9, we discover an MSRC that we absolutely must patch (think Heartbleed) and do so in 1.0.10, but also discover that we accidentally made a breaking change in 1.0.5 that prevents us from rolling forward all these versions (via app-local servicing) to 1.0.10 without breaking some applications.
In this case, we could make use of the revision to split this servicing band into two. One before the behavior change and one after the behavior change.
The packages would like the following (red represents the new packages that we would ship):
Package Assembly Version File Version Assembly Publisher Policy
1.0.0 1.0.0.0 1.0.0.123
1.0.1 1.0.1.0 1.0.1.124
1.0.2 1.0.2.0 1.0.2.125
1.0.3 1.0.3.0 1.0.3.126
1.0.4 1.0.4.0 1.0.4.127
1.0.4.1* 1.0.4.1 1.0.4.128 1.0.0 - 1.0.4 inclusive -> 1.0.4.1
1.0.5 1.0.5.0 1.0.5.128
1.0.6 1.0.6.0 1.0.6.129
1.0.7 1.0.7.0 1.0.7.130
1.0.8 1.0.8.0 1.0.8.131
1.0.9 1.0.9.0 1.0.9.132
1.0.10 1.0.10.0 1.0.10.133 1.0.5 - 1.0.9 inclusive -> 1.0.10.0
* Make note that this violates our/SemVer's rules above, however, is actually supported by NuGet.
[unresolved: How is AAPT going to solve this with their package servicing? Make use of the 4 component of the package version?]
6) Why must app-local façades start at 4.1.n.n?
To skip Fusion's lookup in the unification table, see this for more information.
7) I thought "+" was the adopted delimiter between pre-release and build number? We're starting to discuss git-based build labels for Roslyn, we may want to support non-linear build numbers (e.g. hashes)
+ appends additional build metadata that is not taken into account when comparing versions. For example the following are considered the same version:
 
1.0.0-beta+123
1.0.0-beta+124
1.0.0-beta+125
 
We don’t want that. We want to be able users to upgrade to later beta builds, so instead, we use:
 
1.0.0-beta.123
1.0.0-beta.124
1.0.0-beta.125
 
The last version is considered greater than the previous and can be installed over the top. When we move over to GIT, we could add additional metadata (via +) to refer to the GIT check-in that this build is based on, but that would be addition to the build number.
8) What is an experimental package?
SemVer states:
Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.
We should use these packages as experimental playgrounds where there is no guarantee around the design and stability of the API, nor whether the package will even make it to an RTM. An example of this is MultiValueDictionary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment