Writing MSBuild Custom Task
Scenario: we have Subversion server to manage our source code and a build server (CruiseControl.NET) to manage our deployment. We have decided to automatically set the SVN revision number to our assembly version.
[assembly: System.Reflection.AssemblyVersion("1.2.3.0")] [assembly: System.Reflection.AssemblyFileVersion("1.2.3.0")]
So we will replace the last 0 with the current Subversion revision number. How to do this? One way to achieve this is to modify the AssemblyInfo.cs and read the modified number from that exists. The file modification is easy with MSBuild Community Task FileUpdate
<FileUpdate Files="Cic.P001001PropertiesAssemblyInfo.cs" Regex="(d+).(d+).(d+).(d+)" ReplacementText="$1.$2.$3.$(RevisionNumber)" />
But how to read it back? Well there is no easy way. I have written a custom MSBuidl task to achieve this like this:
<AssemblyInfoReader Path="PropertiesAssemblyInfo.cs" Property="AssemblyVersion"> <Output TaskParameter="Value" ItemName="ApplicationVersion" /> </AssemblyInfoReader>
Writing a custom MSBuild task is fairly easy. You have to Reference Microsoft.Build.Framework and Microsoft.Build.Utilities and implement Microsoft.Build.Framework.ITask. Just like this:
namespace Cic.MsBuildTasks { public class AssemblyInfoReader : Microsoft.Build.Framework.ITask { #region Private Varaibels private string path; private string property; private string value; #endregion #region Fields [Microsoft.Build.Framework.Required] public string Path { get { return path; } set { path = value; } } [Microsoft.Build.Framework.Required] public string Property { get { return property; } set { property = value; } } [Microsoft.Build.Framework.Output] public string Value { get { return this.value; } set { this.value = value; } } #endregion #region ITask Members private Microsoft.Build.Framework.IBuildEngine engine; Microsoft.Build.Framework.IBuildEngine Microsoft.Build.Framework.ITask.BuildEngine { get { return engine; } set { engine = value; } } bool Microsoft.Build.Framework.ITask.Execute() { string message; value = string.Empty; try { value = MyReadAssemblyInfoProperty(); message = string.Format( "AssemblyInfo property {0} read. Property value {1}", property, value); } catch (System.Exception e) { message = string.Format( "Error reading AssemblyInfo property {0}. Error: {1}", property, e.Message); } Microsoft.Build.Framework.BuildMessageEventArgs args = new Microsoft.Build.Framework.BuildMessageEventArgs( message, string.Empty, "AssemblyInfoReaderTask", Microsoft.Build.Framework.MessageImportance.Normal); engine.LogMessageEvent(args); return true; } private Microsoft.Build.Framework.ITaskHost host; Microsoft.Build.Framework.ITaskHost Microsoft.Build.Framework.ITask.HostObject { get { return host; } set { host = value; } } #endregion #region Internals private string MyReadAssemblyInfoProperty() { string propertyValue; // Eraly return if (!System.IO.File.Exists(path)) return ""; foreach (string line in System.IO.File.ReadAllLines(path)) { if (line.Contains(property)) { try { propertyValue = line.Remove(0, line.IndexOf('"') + 1); propertyValue = propertyValue.Remove( propertyValue.LastIndexOf('"'), propertyValue.Length - propertyValue.LastIndexOf('"')); // return matching property value return propertyValue; } catch { // Ignore errors } } } return string.Empty; } #endregion } }
9 Comments
David White
Two questions:
1. What is the significance of LogMessageEvent? Is that the way to return data to MSBuild? A little explanation (or link) might help there.
2. Is it necessary to declare the “host” property? Maybe I’m going blind, but I can’t see where it is used. 😉
Nick
Heya mate, think you need to escape the periods in your regex, otherwise they’ll match anything:
(d+)\.(d+)\.(d+)\.(d+)
AlexM
Your blog is interesting!
Keep up the good work!
Pingback:
Pingback:
Wim Hollebrandse
Hmm. And what happens when your SVN revision number for your repository hits 65535?
Well, your AssemblyInfo won’t compile anymore, that’s what.
Marcin Kawalerowicz
@Wim Hollebrandse: Good point. Thanks! Learned this the hard way? 😉
Wim Hollebrandse
Indeed Marcin,
Best thing is to use the AssemblyInformationalVersion attribute, as it doesn’t restrict you to 4 shorts (UInt16).
At the same time, when you stamp your assembly with it, it also allows you to retrieve the AssemblyInformationalVersion attribute using reflection.
An other common alternative is to use div and mod 10,000 respectively for the 3rd and 4th part of the version number.
Pingback: