I am a big fan of using NuGet for managing dependencies between different .NET projects. Lately I’ve been focusing on using it for managing internal dependencies within an organization. Most organizations end up with some shared code that is used across multiple applications: logging, security, database access, common domain models, etc. A great way to handle this shared code is to publish it as an internal NuGet package, and then point all of the consuming applications at that package. Additionally, TeamCity can be used to ensure that new packages are created automatically whenever the shared code is updated, and those packages are instantly made available to consumers.
This setup works great, but it can be confusing as there are a lot of moving parts. One of the most confusing parts is the amount of different version / build numbers you end up juggling. This post explains how to configure everything so that the same version number is used in every piece across the entire process.
These are the different version numbers that we will be aligning:
- Revision number from Source Control
- Build number used by TeamCity
- Version number of shared code assemblies
- Version number of the NuGet package
All versions flow from TeamCity
The easiest way to achieve version parity among all of these pieces is to have the versioning controlled by TeamCity, as it is aware of all of the other pieces. Within the TC build configuration, you want to set the Build Number Format thusly:
This ends up setting your build number to the following pieces:
Major / Minor – the major / minor versions of the project (arbitrarily set to 1.0 here)
Revision - the revision number from source control that was pulled for this build
Build Counter - an autoincrementing number that TeamCity uses to keep track of individual builds
With this configuration, TeamCity build versions end up looking like this:
Already we have achieved some parity and traceability: TeamCity versions now refer back to the source control revision number that was used to create that build. But we aren’t done yet.
Pushing the version to .NET assemblies
The next step is to ensure that the .NET assemblies created during this build use the same version number, and it requires a bit of custom scripting. In order to achieve this, we are going to add a build step to TeamCity that uses the AssemblyInfo task from MSBuild Community Tasks to generate an AssemblyInfo file on the fly that contains this version number.
This requires some preparation within the Visual Studio project that will be built. We want to separate out the AssemblyVersion attribute from all of the other assembly attributes that are usually stored in AssemblyInfo.cs. I usually create a separate file called AssemblyVersionInfo.cs that looks like this:
using System.Reflection; [assembly: AssemblyVersion("0.0.0.0")]
Keeping the AssemblyVersion attribute separate means that we can overwrite this file during the build process and none of the other assembly attributes will be lost. Also, if you have multiple assemblies for your shared library, you can link each project to this same file so that you only have to overwrite it in one spot. Having the file in source control contain 0.0.0.0 as a version makes it obvious when there is a problem with the build process, or when a sneaky developer has released code that was built directly on their machine.
Now that the version file is in place, you can create a simple MSBuild script to create a new AssemblyInfo file on the fly that contains the version from TeamCity. I usually store this MSBuild script alongside the solution in source control, and it usually looks something like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask AssemblyFile="MSBuild.Community.Tasks.dll" TaskName="MSBuild.Community.Tasks.AssemblyInfo" /> <Target Name="Main"> <Error Condition="'$(BUILD_NUMBER)' == ''" Text="Must supply BUILD_NUMBER" /> <AssemblyInfo CodeLanguage="CS" OutputFile="Properties\AssemblyVersionInfo.cs" AssemblyVersion="$(BUILD_NUMBER)" /> </Target> </Project>
This script generates an AssemblyInfo file that contains an AssemblyVersion attribute with a value of $(BUILD_NUMBER). TeamCity automatically sets this variable to the current build number when it executes MSBuild. As long as you execute this script in a Build Step before your Visual Studio solution is compiled, your assemblies should end up with the same version from TeamCity.
Alternative – the AssemblyInfo Patcher
TeamCity does come out of the box with a build feature called AssemblyInfo Patcher. This is obviously much simpler than the above and in simple scenarios can achieve the same results. However, if you are doing anything interesting with AssemblyInfo files (such as consolidating them among projects), the patcher won’t be able to find the files correctly and update them. So I end up still going the complicated MSBuild route most of the time as it gives a lot more control. But if you can get away with the patcher, go for it!
Creating NuGet packages with the same version
This used to be harder, and would involve similar MSBuild scripts that we used above to execute “nuget pack” via the command line, passing the version as a command line argument. However, as of TeamCity 7.0, NuGet integration is built in, and it works great. Now there is a build runner called NuGet Pack. You create a build step with this runner, point it at your csproj and/or nuspec files, and viola you have NuGet packages created from your build. TeamCity will even include them in the build artifacts for you automatically. All you have to do to get the NuGet packages to have the same version as the TeamCity build is to set the Nuget Pack Version property like this:
If you set everything up correctly, you should have TeamCity, NuGet, and your .NET assemblies all referring to the same version. And, as a bonus, that version includes a pointer back to the source control revision that was used to build them. This level of traceability between these different artifacts will come in handy when you are troubleshooting build issues or trying to determine the audit trail of a particular change. Enjoy!