We are currently packaging up a set of UX libraries as NuGet packages to go on our internal NuGet server. The assemblies that make up the core of this framework are all in a single Visual Studio solution, however it makes sense to distribute them as a set of NuGet packages as you might not need all the parts in a given project. Hence we have a package structure as follows…
There has been much thought on the versioning strategy of these packages. We did consider independent versioning of each of these fundamental packages, but decided it was worth the effort, keeping their versions in sync was reasonable i.e. the packages have the same version number and are released as a set.
Now this might not be the case for future ‘extension’ packages, but it is an OK assumption for now, especially as it makes the development cycle quicker/easier. This framework is young and rapidly changing, there are often changes in a control that needs associated changes in the common assembly; it is hence good that a developers does not have to check-in a change on the common package before they can make an associated changed to the control package whist debugging a control prior to it being released.
However, this all meant it was important to make sure the package dependencies and versions are set correctly.
We are using Git for this project (though this process is just as relevant for TFVC) with a development branch and a master branch. Each branch has its own CI triggered build
- Development branch build …
- Builds the solution
- Runs Unit tests
- Does SonarQube analysis
- DOES NOT store any built artifacts
- [Is used to validate Pull requests]
- Master branch build …
- Versions the code
- Builds the solution
- Runs Unit tests
- Creates the NuGet Packages
- Stores the created packages (to be picked up by a Release pipeline for publishing to our internal NuGet server)
So within the Master build we need to do some versioning, this needs to be done to different files to make sure the assemblies and the NuGet packages are ‘stamped’ with the build version.
We get this version for the build number variable, $(Build.BuildNumber), we use the format $(Major).$(Minor).$(Year:yy)$(DayOfYear).$(rev:r) e.g. 1.2.16123.3
- $(Major) and $(Minor) build variables we manage (actually our release pipeline updates the $(Minor) on every successful release to production using a VSTS task)
- $(Year:yy)$(DayOfYear) gives a date in the form 16123
- and $(rev:r) is a count of builds on a given day
We have chosen to use this number format to version both the assemblies and Nuget packages, if you have different plans, such as semantic versioning , you will need to modify this process a bit.
The assemblies themselves are easy to version, we just need to set the correct value in their assemblyinfo.cs or assemblyinfo.vb files. I used my Assembly versioning VSTS task to do this
The packages turn out to be a bit more complex. Using the standard NuGet Packager task there is a checkbox to say to use the build number as the version. This works just fine versioning the actual package, adding the –Version flag to the package command to override the value in the project .nuspec file. However it does not help with managing the versions of any dependant packages in the solution, and here is why. In our build …
- AssemblyInfo files updated
- The solution is built, so we have version stamped DLLs
- We package the first ‘common’ Nuget package (which has no dependencies on other projects in the solution) and it is versioned using the –version setting, not the value in it’s nuspec file.
- We package the ‘next’ Nuget package, the package picks up the version from the –version flag (as needed), but it also needs to add a dependency to a specific version of the ‘common’ package. We pass the –IncludeReferencedProjects argument to make sure this occurs. However, Nuget.exe gets this version number from the ‘common’ packages .nuspec file NOT the package actually built in the previous step. So we end up with a mismatch.
The bottom line is we need to manage the version number in the .nuspec file of each package. So more custom VSTS extensions are needed.
Initially I reused my Update XML file task, passing in some XPath to select the node to update, and this is a very valid approach if using semantic versioning as it is a very flexible way yo build the version number. However, in the end I added an extra task to my versioning VSTS extension for Nuget to make my build neater and consistent with my other versions steps.
Once all the versioning was done I could create the packages. I ended up with a build process as shown below
A few notes about the NuGet packaging
- Each project I wish to create a Nuget package for has a nuspec file of the same ‘root’ name in the same folder as the csproj eg. mypackage.csproj and mypackage.nuspec. This file contains all descriptions, copyright details etc.
- I am building each package explicitly, I could use wildcards in the ‘Path/Pattern to nuspec files’ property, I choose not to at this time. This is down to the fact I don’t want to build all the solution’s package at this point in time.
- IMPORTANT I am passing in the .csproj file names, not the .nuspec file names to the ‘Path/Pattern to nuspec files’ property. I found I had to do this else the –IncludeReferencedProjects was ignored. The Nuget documentation seems to suggest as long as the .csproj and .nuspec files have the same ‘root’ name then you could reference the .nuspec file but this was not my experience
- I still set the flag to use the build version to version the package – this is not actually needed as the .nuspec file has already been update
- I pass in the –IncludeReferencedProjects argument via the advanced parameters, to pick up the project dependancies.
So now I have a reliable way to make sure my NuGet packages have consistent version numbers