[.net] How can I auto increment the C# assembly version via our CI platform (Hudson)?

Myself and my group are horrendous at incrementing assembly version numbers and we frequently ship assemblies with 1.0.0.0 versions. Obviously, this causes a lot of headaches.

We're getting a lot better with our practices via our CI platform and I'd really like to set it up to auto increment the values within the assemblyinfo.cs file so that the versions of our assemblies are auto updated with the code changes in that assembly.

I had previously setup (before we found Hudson) a way to increment the value through either msbuild or the command line (can't remember), but with Hudson, that will update the SVN repository and trigger ANOTHER build. That would result in a slow infinite loop as Hudson polls SVN every hour.

Is having Hudson increment the version number a bad idea? What would be an alternative way to do it?

Ideally, my criteria for a solution would be one that:

  • Increments the build number in assemblyinfo.cs before a build
  • Only increments the build number in assemblies that have changed. This may not be possible as Hudson wipes out the project folder every time it does a build
  • Commits the changed assemblyinfo.cs into the code repository (currently VisualSVN)
  • Does not cause Hudson to trigger a new build the next time it scans for changes

Working this out in my head, I could easily come up with a solution to most of this through batch files / commands, but all of my ideas would cause Hudson to trigger a new build the next time it scans. I'm not looking for someone to do everything for me, just point me in the right direction, maybe a technique to get Hudson to ignore certain SVN commits, etc.

Everything I've found so far is just an article explaining how to get the version number automatically incremented, nothing takes into account a CI platform that could be spun into an infinite loop.

This question is related to .net msbuild continuous-integration hudson versioning

The answer is


Here's what I did, for stamping the AssemblyFileVersion attribute.

Removed the AssemblyFileVersion from AssemblyInfo.cs

Add a new, empty, file called AssemblyFileInfo.cs to the project.

Install the MSBuild community tasks toolset on the hudson build machine or as a NuGet dependency in your project.

Edit the project (csproj) file , it's just an msbuild file, and add the following.

Somewhere there'll be a <PropertyGroup> stating the version. Change that so it reads e.g.

 <Major>1</Major>
 <Minor>0</Minor>
 <!--Hudson sets BUILD_NUMBER and SVN_REVISION -->
 <Build>$(BUILD_NUMBER)</Build>
 <Revision>$(SVN_REVISION)</Revision>

Hudson provides those env variables you see there when the project is built on hudson (assuming it's fetched from subversion).

At the bottom of the project file, add

 <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')" />
  <Target Name="BeforeBuild" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
    <Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)" />
    <AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyFileInfo.cs" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyConfiguration="$(Configuration)" Condition="$(Revision) != '' " />
  </Target>

This uses the MSBuildCommunityTasks to generate the AssemblyFileVersion.cs to include an AssemblyFileVersion attribute before the project is built. You could do this for any/all of the version attributes if you want.

The result is, whenever you issue a hudson build, the resulting assembly gets an AssemblyFileVersion of 1.0.HUDSON_BUILD_NR.SVN_REVISION e.g. 1.0.6.2632 , which means the 6'th build # in hudson, buit from the subversion revision 2632.


Here is an elegant solution that requires a little work upfront when adding a new project but handles the process very easily.

The idea is that each project links to a Solution file that only contains the assembly version information. So your build process only has to update a single file and all of the assembly versions pull from the one file upon compilation.

Steps:

  1. Add a class to you solution file *.cs file, I named min SharedAssemblyProperties.cs
  2. Remove all of the cs information from that new file
  3. Cut the assembly information from an AssemblyInfo file: [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
  4. Add the statement "using System.Reflection;" to the file and then paste data into your new cs file (ex SharedAssemblyProperties.cs)
  5. Add an existing item to you project (wait... read on before adding the file)
  6. Select the file and before you click Add, click the dropdown next to the add button and select "Add As Link".
  7. Repeat steps 5 and 6 for all existing and new projects in the solution

When you add the file as a link, it stores the data in the project file and upon compilation pulls the assembly version information from this one file.

In you source control, you add a bat file or script file that simply increments the SharedAssemblyProperties.cs file and all of your projects will update their assembly information from that file.


I've never actually seen that 1.0.* feature work in VS2005 or VS2008. Is there something that needs to be done to set VS to increment the values?

If AssemblyInfo.cs is hardcoded with 1.0.*, then where are the real build/revision stored?

After putting 1.0.* in AssemblyInfo, we can't use the following statement because ProductVersion now has an invalid value - it's using 1.0.* and not the value assigned by VS:

Version version = new Version(Application.ProductVersion);

Sigh - this seems to be one of those things that everyone asks about but somehow there's never a solid answer. Years ago I saw solutions for generating a revision number and saving it into AssemblyInfo as part of a post-build process. I hoped that sort of dance wouldn't be required for VS2008. Maybe VS2010?


As a continuation of MikeS's answer I wanted to add that VS + Visual Studio Visualization and Modeling SDK needs to be installed for this to work, and you need to modify the project file as well. Should also be mentioned I use Jenkins as build server running on a windows 2008 R2 server box with version module, where I get the BUILD_NUMBER.

My Text Template file version.tt looks like this

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("_BuildVersion");
revision = revision == null ? "5.0.0.0" : revision;    
#>
using System.Reflection;
[assembly: AssemblyVersion("<#=revision#>")]
[assembly: AssemblyFileVersion("<#=revision#>")]

I have the following in the Property Groups

<PropertyGroup>
    <TransformOnBuild>true</TransformOnBuild>
    <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

after import of Microsoft.CSharp.targets, I have this (dependant of where you install VS

<Import Project="C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />

On my build server I then have the following script to run the text transformation before the actual build, to get the last changeset number on TFS

set _Path="C:\Build_Source\foo"

pushd %_Path% 
"%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe" history . /r /noprompt /stopafter:1 /Version:W > bar
FOR /f "tokens=1" %%foo in ('findstr /R "^[0-9][0-9]*" bar') do set _BuildVersion=5.0.%BUILD_NUMBER%.%%foo
del bar
popd

echo %BUILD_NUMBER%
echo %_BuildVersion%
cd C:\Program Files (x86)\Jenkins\jobs\MyJob\workspace\MyProject
MSBuild MyProject.csproj /t:TransformAll 
...
<rest of bld script>

This way I can keep track of builds AND changesets, so if I haven't checked anything in since last build, the last digit should not change, however I might have made changes to the build process, hence the need for the second last number. Of course if you make multiple check-ins before a build you only get the last change reflected in the version. I guess you could concatenate of that is required.

I'm sure you can do something fancier and call TFS directly from within the tt Template, however this works for me.

I can then get my version at runtime like this

Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion;

I am assuming one might also do this with a text template where you create the assembly attributes in question on the fly from the environment like AssemblyVersion.tt does below.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#
var build = Environment.GetEnvironmentVariable("BUILD_NUMBER");
build = build == null ? "0" : int.Parse(build).ToString();
var revision = Environment.GetEnvironmentVariable("SVN_REVISION");
revision = revision == null ? "0" : int.Parse(revision).ToString();    
#>
using System.Reflection;
[assembly: AssemblyVersion("1.0.<#=build#>.<#=revision#>")]
[assembly: AssemblyFileVersion("1.0.<#=build#>.<#=revision#>")]

I decided to use a couple of methods using a prebuild Powershell script(https://gist.github.com/bradjolicoeur/e77c508089aea6614af3) to increment on each successful build then in Global.asax I've going something like this:

  // We are using debug configuration, so increment our builds.
  if (System.Diagnostics.Debugger.IsAttached)
  {
      string version = System.Reflection.Assembly.GetExecutingAssembly()
                                                       .GetName()
                                                       .Version
                                                       .ToString();

      var psi = new ProcessStartInfo(@"svn", "commit -m \"Version: " + version + "\n \"");
      psi.WorkingDirectory = @"C:\CI\Projects\myproject";
      Process.Start(psi); 
  }

I still think the whole process is overcomplicated and I'm going to look into a more efficient method of achieving the same result. I wanted this mainly for passing the version into SVN and then into Jenkin's without too many addtional tools.


My solution doesn't require the addition of external tools or scripting languages --it's pretty much guaranteed to work on your build machine. I solve this problem in several parts. First, I have created a BUILD.BAT file that converts the Jenkins BUILD_NUMBER parameter into an environment variable. I use Jenkins's "Execute Windows batch command" function to run the build batch file by entering the following information for the Jenkins build:

     ./build.bat --build_id %BUILD_ID% -build_number %BUILD_NUMBER%

In the build environment, I have a build.bat file that starts as follows:

     rem build.bat
     set BUILD_ID=Unknown
     set BUILD_NUMBER=0
     :parse_command_line
     IF NOT "%1"=="" (
         IF "%1"=="-build_id" (
             SET BUILD_ID=%2
             SHIFT
             )
         IF "%1"=="-build_number" (
             SET BUILD_NUMBER=%2
             SHIFT
         )
         SHIFT
         GOTO :parse_command_line
     )
     REM your build continues with the environmental variables set
     MSBUILD.EXE YourProject.sln

Once I did that, I right-clicked on the project to be built in Visual Studio's Solution Explorer pane and selected Properties, select Build Events, and entered the following information as the Pre-Build Event Command Line, which automatically creates a .cs file containing build number information based on current environment variable settings:

     set VERSION_FILE=$(ProjectDir)\Properties\VersionInfo.cs
     if !%BUILD_NUMBER%==! goto no_buildnumber_set
     goto buildnumber_set
     :no_buildnumber_set
     set BUILD_NUMBER=0
     :buildnumber_set
     if not exist %VERSION_FILE% goto no_version_file
     del /q %VERSION_FILE%
     :no_version_file
     echo using System.Reflection; >> %VERSION_FILE%
     echo using System.Runtime.CompilerServices; >> %VERSION_FILE%
     echo using System.Runtime.InteropServices; >> %VERSION_FILE%
     echo [assembly: AssemblyVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%
     echo [assembly: AssemblyFileVersion("0.0.%BUILD_NUMBER%.1")] >> %VERSION_FILE%

You may need to adjust to your build taste. I build the project manually once to generate an initial Version.cs file in the Properties directory of the main project. Lastly, I manually include the Version.cs file into the Visual Studio solution by dragging it into the Solution Explorer pane, underneath the Properties tab for that project. In future builds, Visual Studio then reads that .cs file at Jenkins build time and gets the correct build number information out of it.


So, we have a project with one solution that contains several projects that have assemblies with different version numbers.

After investigating several of the above methods, I just implemented a build step to run a Powershell script that does a find-and-replace on the AssemblyInfo.cs file. I still use the 1.0.* version number in source control, and Jenkins just manually updates the version number before msbuild runs.

dir **/Properties/AssemblyInfo.cs | %{ (cat $_) | %{$_ -replace '^(\s*)\[assembly: AssemblyVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyVersion(`"`$2.$build`")"} | Out-File $_ -Encoding "UTF8" }
dir **/Properties/AssemblyInfo.cs | %{ (cat $_) | %{$_ -replace '^(\s*)\[assembly: AssemblyFileVersion\("(.*)\.\*"\)', "`$1[assembly: AssemblyFileVersion(`"`$2.$build`")"} | Out-File $_ -Encoding "UTF8" }

I added the -Encoding "UTF8" option because git started treating the .cs file as binary files if I didn't. Granted, this didn't matter, since I never actually commit the result; it just came up as I was testing.

Our CI environment already has a facility to associate the Jenkins build with the particular git commit (thanks Stash plugin!), so I don't worry that there's no git commit with the version number attached to it.


This is a simpler mechanism. It simply involves the addition of a Windows Batch command task build step before the MSBuild step and the use of a simple find and replace program (FART).

The Batch Step

fart --svn -r AssemblyInfo.cs "[assembly: AssemblyVersion(\"1.0.0.0\")]" "[assembly: AssemblyVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
fart --svn -r AssemblyInfo.cs "[assembly: AssemblyFileVersion(\"1.0.0.0\")]" "[assembly: AssemblyFileVersion(\"1.0.%BUILD_NUMBER%.%SVN_REVISION%\")]"
if %ERRORLEVEL%==0 exit /b 1
exit /b 0

If you are using source control other than svn change the --svn option for the appropriate one for your scm environment.

Download Fart


Hudson can be configured to ignore changes to certain paths and files so that it does not prompt a new build.

On the job configuration page, under Source Code Management, click the Advanced button. In the Excluded Regions box you enter one or more regular expression to match exclusions.

For example to ignore changes to the version.properties file you can use:

/MyProject/trunk/version.properties

This will work for languages other than C# and allows you to store your version info within subversion.


.NET does this for you. In your AssemblyInfo.cs file, set your assembly version to major.minor.* (for example: 1.0.*).

When you build your project the version is auto generated.

The build and revision numbers are generated based on the date, using the unix epoch, I believe. The build is based on the current day, and the revision is based on the number of seconds since midnight.


Examples related to .net

You must add a reference to assembly 'netstandard, Version=2.0.0.0 How to use Bootstrap 4 in ASP.NET Core No authenticationScheme was specified, and there was no DefaultChallengeScheme found with default authentification and custom authorization .net Core 2.0 - Package was restored using .NetFramework 4.6.1 instead of target framework .netCore 2.0. The package may not be fully compatible Update .NET web service to use TLS 1.2 EF Core add-migration Build Failed What is the difference between .NET Core and .NET Standard Class Library project types? Visual Studio 2017 - Could not load file or assembly 'System.Runtime, Version=4.1.0.0' or one of its dependencies Nuget connection attempt failed "Unable to load the service index for source" Token based authentication in Web API without any user interface

Examples related to msbuild

How can I install the VS2017 version of msbuild on a build server without installing the IDE? The default XML namespace of the project must be the MSBuild XML namespace The CodeDom provider type "Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider" could not be located Visual Studio 2013 error MS8020 Build tools v140 cannot be found Getting msbuild.exe without installing Visual Studio Found conflicts between different versions of the same dependent assembly that could not be resolved This project references NuGet package(s) that are missing on this computer NuGet auto package restore does not work with MSBuild The builds tools for v120 (Platform Toolset = 'v120') cannot be found error LNK2038: mismatch detected for '_MSC_VER': value '1600' doesn't match value '1700' in CppFile1.obj

Examples related to continuous-integration

Jenkins CI Pipeline Scripts not permitted to use method groovy.lang.GroovyObject Jenkins vs Travis-CI. Which one would you use for a Open Source project? Continuous Integration vs. Continuous Delivery vs. Continuous Deployment How do I clone a job in Jenkins? How to trigger Jenkins builds remotely and to pass parameters How to connect Bitbucket to Jenkins properly Managing SSH keys within Jenkins for Git How to uninstall Jenkins? How to set environment variables in Jenkins? Jenkins CI: How to trigger builds on SVN commit

Examples related to hudson

How/When does Execute Shell mark a build as failure in Jenkins? How to create and add users to a group in Jenkins for authentication? In Jenkins, how to checkout a project into a specific directory (using GIT) Jenkins - passing variables between jobs? How are environment variables used in Jenkins with Windows Batch Command? Jenkins/Hudson - accessing the current build number? Error - trustAnchors parameter must be non-empty Archive the artifacts in Jenkins Jenkins / Hudson environment variables How to trigger a build only if changes happen on particular set of files

Examples related to versioning

npm - how to show the latest version of a package Is there a way to get version from package.json in nodejs code? How to allow users to check for the latest app version from inside the app? Homebrew install specific version of formula? How to Store Historical Data Best Practice: Software Versioning Definition of "downstream" and "upstream" How to mark a method as obsolete or deprecated? How can I auto increment the C# assembly version via our CI platform (Hudson)? How to have an auto incrementing version number (Visual Studio)?