System.Version
The mscorlib System.Version class accepts 2, 3 or 4 integers to represent a version.
var v = new Version(2014010110, 7383783, 38989899, 893839893);
Console.WriteLine(v.ToString()); //shows 2014010110.7383783.38989899.893839893
The values are Major.Minor[.Build[.Revision]]
As we'll see shortly, actual assembly versions are much more limited!
SemVer
Semantic versioning can be mapped into the .net scheme.
In SemVer, the scheme is Major.Minor.Patch.
- Major is breaking changes
- Minor is backwards compatible changes including additions
- Patch is backwards compatible bug fixes.
So .net Build is equivalent to semver Patch, and revision, which is optional anyway, is disregarded. (The original .net convention was that build used the same source with different symbols).
Version attributes
The version attributes are normally in Properties/AssemblyInfo.cs (but could be anywhere).
You can also access AssemblyVersion and AssemblyFileVersion via the project properties - application - [Assembly Information...] button.
There are 3:
//CLR uses this as the version [assembly: AssemblyVersion("1.0.0.0")] //Not used by CLR, often the specific build [assembly: AssemblyFileVersion("1.0.0.0")] //If not present, == AssemblyVersion. [assembly: AssemblyInformationalVersion("v1.0 RC")]
AssemblyInformationalVersion used to error if it wasn't a System.Version (all ints). Since Visual Studio 2010, you can put in free-form strings, which is useful for tags like "RC".
To access these via code:
var executingAssembly = Assembly.GetExecutingAssembly(); var ver = executingAssembly.GetName().Version; //AssemblyVersion var fv = System.Diagnostics.FileVersionInfo.GetVersionInfo(executingAssembly.Location); Console.WriteLine(ver); Console.WriteLine(fv.FileVersion); //AssemblyFileVersion Console.WriteLine(fv.ProductVersion); //AssemblyInformationalVersion
There is also a fv.ProductMajorPart and fv.ProductMinorPart but these aren't populated if the AssemblyInformationalVersion can't be parsed into a System.Version.
The values- major, minor, build, revision - are ints, up to 2,147,483,647. But there's a big gotcha. For operating system reasons, the compiler limits values to 65,534, int16.
For AssemblyVersion, you get a CSC Error: "Error emitting 'System.Reflection.AssemblyVersionAttribute' attribute -- 'The version specified '65536.65535.65535.65535' is invalid'"
For AssemblyFileVersion, you get a CSC Warning "Assembly generation -- The version '65536.65535.65535.65535' specified for the 'file version' is not in the normal 'major.minor.build.revision' format". It will build, at least.
Versioning Strategy
- For all version types major and minor parts should be manually set.
- To simplify CLR versioning, we don't need to increment the AssemblyVersion except once, manually, at final release. For AssemblyVersion, just set major and minor (and perhaps build for semver). Normally build and revision will always be 0.0. We don't want any version numbers changing during developer builds, or even continuous integration builds unless they are automatically deployed to test.
- When a dll is published/deployed, we should increment the AssemblyFileVersion.
- We should be able to trace back to the build.
There are several candidates for traceable build and revision numbers, but none are "semver" (both build and revision are significant).
- Increment by date, as in wildcards (below): build is days since a specific date, revision is seconds since midnight. But there is no obvious connection between the dll and the build on the build-server.
- Date, except we can't fit "year-month-day-hour-minute-second" into an int16. You could overflow it: build is mmdd, revision is hhmm.
- Build name. TFS uses a buildDef_yyyymmdd.n format for the build name.
- Changeset number if numeric, and it is less than 65535.
Both build name and changeset number might be better set in AssemblyInformationalVersion.
Wildcards
For AssemblyVersion only, you can use wildcards for build and revision.
If you use it for file version, you get a warning:
CSC : warning CS1607: Assembly generation -- The version '1.2.0.*' specified for the 'file version' is not in the normal 'major.minor.build.revision' format
- AssemblyVersion build = number of days since 01/01/2000.
- AssemblyVersion revision = number of seconds since midnight.
If you build twice without changing, the revision goes up. If you build the next day without changes, the build goes up.
Wildcards are pretty useless.
Build Tasks
Build tasks run after source control get-latest, before compilation. They find the AssemblyInfo.cs files, flip the readonly flag, and find and replace the AssemblyFileVersion, then compile. The changed AssemblyInfo file should not be checked in. The process is not run in developer builds, only in "publish" builds.
MSBuild Extension Pack is a set of msbuild tasks, which is also available as a Nuget package (MSBuild.Extension.Pack). One task, MSBuild.ExtensionPack.VisualStudio.TfsVersion, edits the AssemblyFileVersion given a date or tfs-format build name.
Another project, Community TFS Build Extensions, made by some of the same people, hooks up into TFS 2012/2013 xaml workflows and includes a TfsVersion build activity.