Showing posts with label msbuild. Show all posts
Showing posts with label msbuild. Show all posts

Wednesday, June 29, 2011

Build Server Code Analysis Settings

I've been looking at ways to improve the reporting of Code Analysis as part of our Team Build. During my research I found that the RunCodeAnalysis setting as defined in the TFSBuild.proj differs significantly from the local MSBuild project schema options (see this post for a detailed break down of project level analysis settings).

Specifically, TFSBuild defines RunCodeAnalysis as "Always", "Default" and "Never" while MSBuild defines this as a simple true/false Boolean.  To make sense of this I hunted this down to this section of the Microsoft.TeamFoundation.Build.targets file:

<Target Name="CoreCompileSolution">

  <PropertyGroup>
    <CodeAnalysisOption Condition=" '$(RunCodeAnalysis)'=='Always'">RunCodeAnalysis=true</CodeAnalysisOption>
    <CodeAnalysisOption Condition=" '$(RunCodeAnalysis)'=='Never'">RunCodeAnalysis=false</CodeAnalysisOption>
    <!-- ... -->
  </PropertyGroup>
  <!-- ... -->
</Target>

From this we can infer that "Default" setting does not provide a value to the runtime, while "Always" and "Never" map to True/False respectively.  But this raises the question, what's the difference between Default and Always, and what should I specify to effectively run Code Analysis?

To answer this question, let's take an example of a small project with two projects.  Both projects are configured in the csproj with default settings.  For the purposes of the demo, I've altered the output folder to be at a folder called Output at the root of the solution:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>..\Output</OutputPath>
  <DefineConstants>DEBUG;TRACE</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
</PropertyGroup>

When we specify code analysis to run with the "Always" setting:

Msbuild.exe Example.sln /p:RunCodeAnalysis=True

The Output folder contains the following files:

CodeAnalysisExperiment1.dll 
CodeAnalysisExperiment1.dll.CodeAnalysisLog.xml 
CodeAnalysisExperiment1.dll.lastcodeanalysissucceeded 
CodeAnalysisExperiment1.pdb 
CodeAnalysisExperiment2.dll 
CodeAnalysisExperiment2.dll.CodeAnalysisLog.xml 
CodeAnalysisExperiment2.dll.lastcodeanalysissucceeded 
CodeAnalysisExperiment2.pdb

Close inspection of the console output shows that the Minimal ruleset was applied to my empty projects, despite having not configured either project for code analysis.

DefaultRuleset

If I tweak the project settings slightly such that one project explicitly declares RunCodeAnalysis as True and the other as False

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> 
  <DebugSymbols>true</DebugSymbols> 
  <DebugType>full</DebugType> 
  <Optimize>false</Optimize> 
  <OutputPath>..\Output</OutputPath> 
  <DefineConstants>DEBUG;TRACE</DefineConstants> 
  <ErrorReport>prompt</ErrorReport> 
  <WarningLevel>4</WarningLevel> 
  <RunCodeAnalysis>false</RunCodeAnalysis> 
</PropertyGroup>

and…

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> 
  <DebugSymbols>true</DebugSymbols> 
  <DebugType>full</DebugType> 
  <Optimize>false</Optimize> 
  <OutputPath>..\Output</OutputPath> 
  <DefineConstants>DEBUG;TRACE</DefineConstants> 
  <ErrorReport>prompt</ErrorReport> 
  <WarningLevel>4</WarningLevel> 
  <RunCodeAnalysis>true</RunCodeAnalysis> 
</PropertyGroup>

...and then specify the "Default" setting:

msbuild Example.sln

The Output folder now contains the following files:

CodeAnalysisExperiment1.dll 
CodeAnalysisExperiment1.dll.CodeAnalysisLog.xml 
CodeAnalysisExperiment1.dll.lastcodeanalysissucceeded 
CodeAnalysisExperiment1.pdb 
CodeAnalysisExperiment2.dll 
CodeAnalysisExperiment2.pdb

From this we can infer that the "Default" setting looks to the project settings to determine if analysis should be done, whereas the "Always" setting will override the analysis and "Never" will disable analysis altogether.

For my purposes, I have some projects (unit tests, etc) that I don't necessarily want to run code analysis on -- for me, "Always" is a bit of a deal breaker.  However, there's an interesting lesson to take away here: command line arguments passed to msbuild are carried to all targets.  Which means we can override most project settings this way:

// use 'all rules' for projects that have analysis turned on 
msbuild.exe Example.sln /p:CodeAnalysisRuleSet="Allrules.set"

// use a custom dictionary during code analysis 
msbulid.exe Example.sln /p:CodeAnalysisDictionary="..\AnalysisDictionary.xml"

// redirect output to a different folder 
msbulid.exe Example.sln /p:OutputPath="..\Output2"

Cheers!

Happy Coding.

submit to reddit

Thursday, January 27, 2011

Using Xslt in MSBuild

Recently, I had the opportunity to integrate some of my Xslt skills into our MSBuild script.  As MSBuild is a relatively new technology for me (compared to NAnt) as I ventured into this task I was reminded that there’s always more than one way to get the job done.  Prior to .NET 4.0, MSBuild didn’t natively support Xslt capabilities so the developer community independently produced their own solutions.  So far, I’ve identified the following three implementations:

I thought it would be fun to contrast the different implementations.

MSBuild Community Tasks: Xslt

Hosted on tigris.org, the msbuildtasks implementation was the task I ultimately ended up using. Of the three listed, this one seems to be the most feature complete – but working with it is extremely frustrating because there is no online help.  Fortunately, there is a help file that ships with the installer that provides decent coverage for the Task (albeit a bit buried).

The interesting feature that sets this Task apart is that it aggregates your xml files into a single in-memory document and performs the transformation once.  Keep in mind that this has an impact on how you structure your XPath queries.  For example, an expression like count(//element-name) will count all instances in the aggregated contents.

Because it is an aggregator, your xml documents will be loaded as children elements of a root element.  As such, you must supply the name for the Root element. If you are only transforming a single file, the root element can be left blank (“”).

My only complaint about this implementation is that it only supports writing the transformation to a file. For my purposes, I would have really liked the ability to pipe the output into a Property or ItemGroup. To achieve this, you must read the contents of the file into your script. Not a big deal, just an extra step.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
   <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />

   <Target Name="Example">

      <ItemGroup>
        <XmlFiles Include="*.xml" />
      </ItemGroup>

      <PropertyGroup>
        <XslFile>example.xslt</XslFile>
      </PropertyGroup>

      <!-- perform our xslt transformation for all xml files -->
      <Xslt
         Inputs="@(XmlFiles)"
         Xsl="$(XslFile)"
         RootTag="Root"
         Output="xslt-result.html"/>

      <!-- read the file into a property -->
      <ReadLinesFromFile File="xslt-result.html">
         <Output TaskParameter="Lines" ItemName="MyOuput"/>
      </ReadLinesFromFile>  

      <!-- display the output (with no delimiter) -->
      <Message Text="@(MyOutput,'')" />

    </Target>

</Project>

In reading the help documentation, I noticed that this task will pass the extended meta-data of the Inputs into the document as parameters to the Xslt.  This looks interesting and I may want to revisit this in a future post.

MSBuild Extension Pack: XmlTask

I really liked this implementation but unfortunately our build server wasn’t using this set of extensions, so I had to retrofit to use the community tasks version.

This task boasts a few interesting features:

  1. The output of the transformation can write to a file or a property/item.
  2. Ability to validate the Xml.
  3. Ability to suppress the Xml declaration (though you can do this in your xslt, see below)

This example is comparable to above but uses the Output element of the Task to route the transformation into an ItemGroup (MyOutput).

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
   <Import Project="$(MSBuildExtensions)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" />
            
   <Target Name="Example">

      <ItemGroup>
        <XmlFiles Include="*.xml" />
      </ItemGroup>

      <PropertyGroup>
        <XslFile>example.xslt</XslFile>
      </PropertyGroup>

      <MSBuild.ExtensionPack.Xml.XmlTask 
          TaskAction="Transform" 
          XmlFile="%(XmlFiles.Identity)" 
          XslTransformFile="$(XslFile)"
          >
          <Output 
             ItemName="MyOutput" 
             TaskParameter="Output"
             />
      </MSBuild.ExtensionPack.Xml.XmlTask>        
        
      <Message Text="@(MyOutput, '')" />
        
   </Target>

</Project>

Note that I can switch between using multiple and a single input files simply by changing the XmlFile attribute from an ItemGroup to a Property.  Unlike the Community Tasks implementation, my Xslt stylesheet doesn’t need special consideration for an artificial root element which makes the stylesheet shorter to develop and easier to maintain.

MSBuild 4.0: XsltTransformation

Last but not least is the newcomer on the block, MSBuild 4.0’s XsltTransformation Task.

This task, to my knowledge, can only write the transformation to a file.  If you’re only transforming a single file this won’t be a problem for you, but if you have multiple files that you want to concatenate into a single output, it’ll take some finesse.

I solved this problem by creating a unique output file for each transformation.  Aside from some extra files to clean-up, the task worked great.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
            
   <Target Name="Example">

      <ItemGroup>
        <XmlFiles Include="*.xml" />
      </ItemGroup>

      <PropertyGroup>
        <XslFile>example.xslt</XslFile>
      </PropertyGroup>

      <!-- Perform the transform for each file, but
              write the output of each transformation to its
              own file. -->
      <XslTransformation
         OutputPaths="output.%(XmlFiles.FileName).html"
         XmlInputPaths="%(XmlFiles.Identity)"
         XslInputPath="$(XslFile)"
         />
 
      <!-- Read all our transformation outputs back into an ItemGroup -->
      <ReadLinesFromFile File="output.%(XmlFiles.FileName).html">
         <Output TaskParameter="Lines" ItemName="MyOutput" />
      </ReadLinesFromFile>
        
      <Message Text="@(MyOutput, '')" />
        
   </Target>

</Project>

This implementation also supports passing parameters, which may come in handy.

Some Xslt Love

And for completeness sake, here’s a skeleton Xslt file that supresses the xml-declaration.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    
    <xsl:output method="html" omit-xml-declaration="yes" />
    
    <xsl:template match="/">
        <p>your output here</p>
    </xsl:template>

</xsl:stylesheet>

So go out there and unleash some Xslt-Fu.

submit to reddit

Tuesday, January 25, 2011

MSBuild for NAnt Junkies

alien-autopsyOver the last seven years any time I needed to write a build script, I've turned to NAnt as the tool to get the job done. I’ve relied on NAnt so much that the NAnt task reference page is usually on my Most Visited page in Chrome. The concepts behind NAnt are straight forward and once you’ve understand the basics it’s a great skill to have.

My current project is using MSBuild as the principle build script and this week I had the pleasure of having to tweak it. Although I’m a little late to adopting MSBuild as a scripting tool, this was a good opportunity to get to know its idiosyncrasies and learn a new technology. My verdict? They share a lot of similar characteristics, but if you’re familiar with NAnt you’ll find that MSBuild has a few foreign concepts that take some time to wrap your head around.

While this is still new territory for me, I thought it would be fun to compare the two and share my findings here for my reference (and for yours!).

The Basics

Structure

At the surface, MSBuild and NAnt appear to related as they share a common lexicon and overall structure. The scripts are comprised of Projects and Targets.

NAnt:
<?xml version="1.0"?>
<project default="MyTarget" >
    
    <target name="MyTarget">
        <echo message="Hello World" />
    </target>

</project>
MSBuild:
<Project DefaultTargets="MyTarget"
      xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <Target Name="MyTarget">
        <Message Text="Hello World" />
    </Target>

</Project>

Execution of the scripts is nearly identical, with only minor differences in the input arguments:

nant -buildfile:example.build MyTarget
msbuild example.csproj /target:MyTarget

Tasks

Both NAnt and MSBuild share a rich Task functionality set and support the ability for writing your own Tasks if the default tasks aren’t enough.  They also support writing custom tasks inline using your favourite .NET language.

Properties

The most notable difference between the two technologies starts to appear in how they handle Properties.

NAnt has a fixed schema such that all properties are defined exactly the same way.  You reference your properties in your scripts by prefacing them with ${property-name}

Using properties with NAnt
<property name="myproperty" value="hello world" />

<echo message='${myproperty}" />

MSBuild on the other hand leverages the self-describing nature of XML to describe properties. This can really throw you off, since your properties won’t adhere to a known schema which means there’s no IntelliSense support. This seems like an odd move as MSBuild is the underlying file structure for Visual Studio project files, but it provides an opportunity to provide additional meta-data about the properties, which I’ll explain later in this post.

Rather than use an element named “property”, any element that appears inside a PropertyGroup element is a property.  Similarly to NAnt, you reference properties using a $(property-name) syntax except be sure to note this is round brackets instead of curly braces.

Using Properties with MSBuild:
<PropertyGroup>
    <MyProperty>Hello World</MyProperty>
</PropertyGroup>

<Message Text="$(MyProperty)" />

MSBuild also supports the concept of ItemGroups, which equate roughly to multi-valued properties. The closest equivalent in NAnt would be filesets, though they are only limited to certain tasks, like a list of files and folders to be used with the Copy task.

Much like Properties, the element name represents the name of the ItemGroup. You reference an ItemGroup using a @(ItemGroup-Name) syntax. By default, ItemGroup are evaluated as semi-colon delimited lists.

Using ItemGroups with MSBuild:
<ItemGroup>
    <MyList Include="one" />
    <MyList Include="two" />
    <MyList Include="three" />
</ItemGroup>

<Message Text="@(MyList)" />

Produces the output:

one;two;three

By default, when accessing items in an ItemGroup using the @(item-group-name) syntax, the items are concatenated using a semi-colon delimiter. You can specify different delimiters as well, for example the following produces a comma-delimited list.

<Message Text="@(MyList, ',')" />

Advanced Stuff

Conditions

There are times where you want to conditionally invoke a target, task or configure a property and both tools accommodate this.  NAnt describes conditions in the form of two attributes: “if” or “unless” – unless is the opposite of if. NAnt also supports a very impressive list of built-in functions including string, file, date and environment routines.  MSBuild doesn’t have extensive support for inline expressions but does offer a set of Property Functions and standard boolean logic.  However, MSBuild has a very long list of Tasks and community extensions that provide coverage in this area. 

To assist with the conditional expressions you may write, NAnt also ships with out of the box properties.  MSBuild has considerably more reserved properties, though most of them seem centered around the Visual Studio compilation process.

Output

Sometimes your build script needs to control flow of execution based on the outcome of the preceding tasks.  In the NAnt world, a lot of the default Tasks expose an attribute that contains the output.

This NAnt example directs the exit code of the Exec task to the property myOutput.

<target name="RunProgram">

    <exec
        program="batch.bat"
        resultproperty="MyOutput"
        />

    <echo message="${MyOutput}" />
</target>

MSBuild has a similar strategy, except there’s a finer level of control: all Tasks can contain an Output element that dictates where to direct output of the Task.  This is advantageous if there are multiple output values that you want to collect from the Task.

This MSBuild example uses the Output element to put the value of the Exec Task’s ExitCode into the Property MyOutput:

<Target Name="RunProgram">

    <Exec Command="batch.bat">
        <Output
            TaskParameter="ExitCode"
            PropertyName="MyOutput" />
    </Exec>
    
    <Message Text="$(MyOutput)" />

</Target>

MetaData

Earlier I mentioned that MSBuild leverages the self-describing nature of XML to provide additional meta-data about properties (more specifically Item Groups).  This loose format to describe our properties means that we can embed a lot of extra data that can be leveraged by custom Tasks.  After this example, it’s clear to see how MSBuild and Visual Studio use meta-data to assist in the compilation of our projects.

We can embed meta data by adding custom children elements below each Item:

<ItemGroup>
    <StarWarsMovie Include="episode3.xml">
        <Number>III</Number>
        <Name>Revenge of the Sith</Main>
        <Awesome>no</Awesome>
    </StarWarsMovie>
    <StarWarsMovie Include="episode4.xml">
        <Number>III</Number>
        <Name>A New Hope</Name>
        <Awesome>yes</Awesome>
    </StarWarsMovie>
    <StarWarsMovie Include="episode5.xml">
        <Number>III</Number>
        <Name>The Empire Strikes Back</Name>
        <Awesome>yes</Awesome>
    </StarWarsMovie>
</ItemGroup>

And we can access the meta data of each individual Item using the %(meta-name) syntax.  For example, we can spit out only the awesome Star Wars Films by filtering them by their meta-data.

<Message Text="@(StarWarsMovie)"
         Condition=" '%(Awesome)' == 'yes'"/>

The above displays the names of the episodes that are awesome (“episode4.xml;episode5.xml”) but there’s a really interesting and powerful aspect happening with the above statement.  The “%” character accesses the individual items before they are appended to the output, which is very similar to a for loop.  So where “@(StarWarsMovie)” sends the entire ItemGroup to the Message task, we can execute the Message Task in a loop using this syntax:

<Message Text="%(StarWarsMovie.Name)" 
         Condition=" '%(Awesome)' == 'yes'"/>

Which produces the output:

A New Hope
The Empire Strikes Back

There’s also a set of baked-in meta-data properties available to all ItemGroups.  The most notable is the Identity meta-property which gives us the name of the individual Item.

Transforms

Another interesting feature that MSBuild provides is the ability to convert the contents of a list into another format using a transform syntax @( ItemGroup –> expected-format).  When combined with Meta-Data, we’re able to create rich representations of our data. Sticking with my Star Wars theme (hey, at least I'm consistent), the following transform:

@(StarWarsMovies -> 'Star Wars Episode %(Number): %(Name)', ', ')

Creates a comma-delimited list:

Star Wars Episode III: Revenge of the Sith, Star Wars Epsiode IV: A New Hope, Star Wars Epside V: The Empire Strikes Back

Wrap up

While MSBuild shares a lot of common ground with NAnt, there are certainly a lot of interesting features that warrant a closer look.

Happy Coding.

submit to reddit