And I thought MSBuild was just mouthwash!
When I saw MSBuild for the first time I thought - "Yeah, good improvement over the standard makefile - we now have XML (surprise!!) and it is extensible". The real import of word “extensible” struck home when I started writing custom tasks. The concept of being able to call an army of reusable objects from a makefile – well we’ll start giving them more respect and start calling them proj files - is quite empowering. But (ahh there’s the but) there’s one pain point. Writing a task means writing a new class which means more testing, maintenance etc etc. And what if all you wanted to do was to say hey! (Well I can’t really think of 101 reasons to say “hey” in a program … but you get the point). So I wrote this object that executes C# code passed to it as a parameter.
So now to greet someone all you need to do is:
<ExecuteCode Code =""Wotcher!"" />
Yeah I know the " spoils the effect L
You could also get something back from the object. Say you wanted a property in your proj file that had the value of current time:
<ExecuteCode
Code ="DateTime dt = DateTime.Now;
ReturnValue = dt.ToString();">
<Output TaskParameter ="OutputString" PropertyName ="Time" />
</ExecuteCode>
Now $(Time) can be used like any other property in your proj file. I had hard-coded a few standard assemblies and “using directives” for ease of use of the object. For the sake of extensibility I take in the assemblies to be referenced and the namespaces that’ll figure in the “using directives” as parameters of the Task. So if you are strictly against hard-coding anything you could take them as parameters like this:
<ExecuteCode
Code ="DateTime dt = DateTime.Now;
ReturnValue = dt.ToString();"
UsingDirectives ="System"
ReferenceAssemblies="System.dll">
<Output TaskParameter ="OutputString" PropertyName ="CurrentDate" />
</ExecuteCode>
Behind the Scenes
ExecuteCode is an object that adds some boilerplate code to the parameter that gets passed in, compiles it and then uses reflection to load up the assembly and execute the method.
Compiling CSharp code is rather easy:
CSharpCodeProvider csc = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.GenerateInMemory = true;
string program = ConstructProgram();
CompilerResults cr = csc.CompileAssemblyFromSource(cp, program);
ConstructProgram() is a function that adds the boilerplate code to the code that was passed in (like using directives, class name, method name, closing braces)
We can also set the assemblies to be referenced in the CompilerParameters object like this
cp.ReferencedAssemblies.Add("System.dll");
Arggh! Errors!!
//Run
if (cr.Errors.HasErrors == false)
{
Assembly assembly = cr.CompiledAssembly;
Object o = assembly.CreateInstance(ClassName);
Type t = o.GetType();
MethodInfo mi = t.GetMethod(MethodName);
OutputString = (string)mi.Invoke(o, null);
returnValue = true;
}
else
{
foreach (CompilerError err in cr.Errors)
{
Log.LogError("(" + err.Line + "," + err.Column + "): " + err.ErrorText);
}
Log.LogMessage(program);
returnValue = false;
}
If you don’t have any errors in the code that you passed in(fat chance!) then load the assembly and invoke the method or else spew out the errors.
Giving something back
Also notice that the method need not be Main and hence can have any signature. The ConstructProgram() method adds a “return ReturnValue” statement. So to return something from your code you just need to set the ReturnValue variable. Notice that the output property OutputString is set to the return value of the method.
Comments
Anonymous
September 20, 2005
Good one Srinu...
Thanks
VenuAnonymous
November 10, 2005
True power of MSBuild also manifests in TeamBuild... If you look real sceptically, TeamBuild is just a wrapper layer that executes MSBuild on a remote machine.
But thats half the truth, the true power of TeamBuild shows up in the report it generates. As John Lawrence states the whole Team System story of intergration shows itself in that report....Anonymous
March 19, 2007
Thanks the DateTime.Now is a very useful example. I am currently using this in conjunction with the msbuild community task <AssemblyInfo> target to automatically add a time stamp to the AssemblyInfo.cs file in the AssemblyDescription section at build time. Very Useful thanks!Anonymous
March 20, 2007
I was able to find a Time task in the contributed msbuild targets, it's easy to use, here is an example! <Target Name="SetTimeStamp"> <Time> <Output TaskParameter="Month" PropertyName="Month" /> <Output TaskParameter="Day" PropertyName="Day" /> <Output TaskParameter="Year" PropertyName="Year" /> <Output TaskParameter="Hour" PropertyName="Hour" /> <Output TaskParameter="Minute" PropertyName="Minute" /> </Time> <Message Text="build Time=$(Year)/$(Month)/$(Day)_$(Hour):$(Minute)"></Message> </Target>