Monday, January 31, 2011

Code Analysis without Visual Studio 2010

This post provides step-by-step instructions on how to integrate Visual Studio 2010's code analysis into your build. If you've not yet done so, please read my first post for an overview of what will be achieved. If you'd prefer, you can integrate Visual Studio 2008's code analysis by following the instructions in my previous post. Whilst you will need a machine with Visual Studio 2010 installed, this won't be a requirement for building your project once it's set up correctly. I assume Visual Studio is installed in the default location - adjust paths as necessary.

Assume we are starting with the following directory structure:

Project
    Lib
    Src

Step 1: Copy Code Analysis Tooling

Copy the entire contents of C:\Program Files\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop to Lib\Code Analysis.

Copy the entire contents of C:\Program Files\MSBuild\Microsoft\VisualStudio\v10.0\CodeAnalysis to Lib\Code Analysis.

Copy the following files to Lib\Code Analysis:

  • C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualC\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualC.Dll
  • C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualStudio.CodeAnalysis.Sdk\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.CodeAnalysis.Sdk.dll
  • C:\Program Files\Microsoft Visual Studio 10.0\DIA SDK\bin\msdia100.dll
  • C:\Program Files\Microsoft Visual Studio 10.0\VC\redist\x86\Microsoft.VC100.CRT\msvcp100.dll
  • C:\Program Files\Microsoft Visual Studio 10.0\VC\redist\x86\Microsoft.VC100.CRT\msvcr100.dll

Step 2: Create Code Analysis Targets File

Create a file called CodeAnalysis.targets and put it in your Src directory. Here is a starting point for the contents of this file. You should tweak as necessary for your needs:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <!--
        Inject our own target before the code analysis runs.
        -->
        <RunCodeAnalysisDependsOn>
            ConfigureCodeAnalysis;
            $(RunCodeAnalysisDependsOn);
        </RunCodeAnalysisDependsOn>
        
        <!--
        Ensure code analysis is run
        -->
        <RunCodeAnalysis>True</RunCodeAnalysis>
        
        <!--
        Set this to false if you don't want all code analysis violations to be treated as errors.
        -->
        <CodeAnalysisTreatWarningsAsErrors>True</CodeAnalysisTreatWarningsAsErrors>
        <!--
        This should be set to resolve to the Lib directory, which must contain the code analysis tooling.
        -->
        <PathToLib>$(MSBuildProjectDirectory)\..\..\Lib\</PathToLib>
        <!--
        This should be set to resolve to the directory containing this targets file.
        -->
        <PathToTargets>$(MSBuildProjectDirectory)\..\</PathToTargets>
        
        <!--
        Setting these properties is required in order for the code analysis targets to execute correctly.
        Without setting these, it will look for the tooling under default installation directories instead
        -->
        <CodeAnalysisTargets>$(PathToLib)\Code Analysis\Microsoft.CodeAnalysis.Targets</CodeAnalysisTargets>
        <CodeAnalysisPath>$(PathToLib)\Code Analysis</CodeAnalysisPath>
        <!--
        Assign default code analysis rules
        -->
        <CodeAnalysisRuleSet>$(PathToTargets)CodeAnalysis.Default.ruleset</CodeAnalysisRuleSet>
    </PropertyGroup>
    <UsingTask AssemblyFile="$(PathToLib)\MSBuildSdcTasks\Microsoft.Sdc.Tasks.dll" TaskName="StringComparison"/>
    <Target Name="ConfigureCodeAnalysis">
        <!--
        Assume that any projects with ".Tests" in their names are test projects
        -->
        <StringComparison Comparison="Contains" Param1="$(AssemblyName)" Param2=".Tests">
            <Output TaskParameter="Result" PropertyName="IsTestProject"/>
        </StringComparison>
        <!--
        Assign different rules for test projects (more relaxed)
        -->
        <CreateProperty Condition="$(IsTestProject)" Value="$(PathToTargets)CodeAnalysis.Tests.ruleset">
            <Output TaskParameter="Value" PropertyName="CodeAnalysisRuleSet"/>
        </CreateProperty>
    </Target>
</Project>

Step 3: Create Rule Sets

Create files called CodeAnalysis.Default.ruleset and CodeAnalysis.Tests.ruleset in your Src directory. You can create these files with Visual Studio 2010 by choosing File / New / File / Code Analysis Rule Set. Alternatively, you can just copy the files from the example in the download.

Step 4: Enable Code Analysis for Relevant Projects

For every project that requires code analysis, open the .csproj file and insert the following before the import of Microsoft.CSharp.targets:

<Import Project="..\CodeAnalysis.targets" />

NOTE: it's very important that this be inserted before the import of Microsoft.CSharp.targets, not after.

Step 5: Tweak as Necessary

You may wish to tweak the CodeAnalysis.targets, CodeAnalysis.Default.ruleset, and CodeAnalysis.Tests.ruleset files in order to alter the rules that are enabled, the conditions in which sets of rules are used, etc. As mentioned above, you can use VS2010 to create and edit .ruleset files.

Code analysis is now integrated with your projects. Where you're building from is irrelevant - whether it's Visual Studio, the command line, or your build server. In all cases code analysis will execute for your project. You can download an example solution showing all this in action below.

Troubleshooting

If you get CA0001 Phx.FatalError, you likely need to register the msdia100.dll on the build machine:

regsvr32 msdia100.dll
You could possibly incorporate this into your build script, too, if your build user has sufficient rights.

Code Analysis without Visual Studio 2008

This post provides step-by-step instructions on how to integrate Visual Studio 2008's code analysis into your build. If you've not yet done so, please read my first post for an overview of what will be achieved. If you'd prefer, you can integrate Visual Studio 2010's code analysis by following the instructions in my next post. Whilst you will need a machine with Visual Studio 2008 installed, this won't be a requirement for building your project once it's set up correctly. I assume Visual Studio Team Edition is installed in the standard location - adjust paths as necessary.

Assume we are starting with the following directory structure:

Project
    Lib
    Src

Step 1: Copy Code Analysis Tooling

Copy the entire contents of C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop to Lib\Code Analysis.

Copy the entire contents of C:\Program Files\MSBuild\Microsoft\VisualStudio\v9.0\CodeAnalysis to Lib\Code Analysis.

Step 2: Create Code Analysis Targets File

Create a file called CodeAnalysis.targets and put it in your Src directory. Here is a starting point for the contents of this file. You should tweak as necessary for your needs:

<?xml version="1.0" encoding="utf-8"?>

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <!--
        Inject our own target before the build.
        -->
        <BuildDependsOn>
            ConfigureCodeAnalysis;
            $(BuildDependsOn);
        </BuildDependsOn>

        <RunCodeAnalysis>True</RunCodeAnalysis>
        
        <!--
        Set this to false if you don't want all code analysis violations to be treated as errors.
        -->
        <CodeAnalysisTreatWarningsAsErrors>True</CodeAnalysisTreatWarningsAsErrors>

        <!--
        These are the default code analysis rules that will be applied to any project importing this targets file.
        Note that the lack of $(CodeAnalysisRules) in the definition of CodeAnalysisRules means that individual
        projects cannot override these settings. This is intentional.
        -->
        <DefaultCodeAnalysisRules>+!Microsoft.Design#CA1012;-!Microsoft.Design#CA2210</DefaultCodeAnalysisRules>

        <!--
        These are the code analysis rules that will be applied to any tests project importing this targets file.
        Test projects are identified by having ".Tests" in their name.
        -->
        <TestsCodeAnalysisRules>+!Microsoft.Design#CA1012;-!Microsoft.Design#CA2210</TestsCodeAnalysisRules>

        <!--
        This should be set to resolve to the Lib directory, which must contain the code analysis tooling.
        -->
        <PathToLib>$(MSBuildProjectDirectory)\..\..\Lib</PathToLib>

        <!--
        Setting these properties is required in order for the code analysis targets to execute correctly.
        Without setting these, it will look for the tooling under default installation directories instead, which probably
        won't (and shouldn't) be present on the build server.
        -->
        <CodeAnalysisPath>$(PathToLib)\Code Analysis</CodeAnalysisPath>

        <CodeAnalysisRules>$(DefaultCodeAnalysisRules)</CodeAnalysisRules>
    </PropertyGroup>

    <UsingTask AssemblyFile="$(PathToLib)\MSBuildSdcTasks\Microsoft.Sdc.Tasks.dll" TaskName="StringComparison"/>

    <Target Name="ConfigureCodeAnalysis">
        <!--
        Assume that any projects with ".Tests" in their names are test projects
        -->
        <StringComparison Comparison="Contains" Param1="$(AssemblyName)" Param2=".Tests">
            <Output TaskParameter="Result" PropertyName="IsTestProject"/>
        </StringComparison>

        <!--
        Assign different rules for test projects (more relaxed)
        -->
        <CreateProperty Condition="$(IsTestProject)" Value="$(TestsCodeAnalysisRules)">
            <Output TaskParameter="Value" PropertyName="CodeAnalysisRules"/>
        </CreateProperty>
    </Target>
    
    <Import Project="..\Lib\Code Analysis\Microsoft.CodeAnalysis.targets" />
</Project>

NOTE: For the sake of brevity, I have only included a couple of rule definitions in the above snippet. Please download the example solution to obtain a more comprehensive list.

Step 3: Enable Code Analysis for Relevant Projects

For every project that requires code analysis, open the .csproj file and insert the following after the import of Microsoft.CSharp.targets:

<Import Project="..\CodeAnalysis.targets" />

NOTE: it's very important that this be inserted after the import of Microsoft.CSharp.targets, not before.

Step 4: Tweak as Necessary

You may wish to tweak the CodeAnalysis.targets file in order to alter the rules that are enabled, the conditions in which sets of rules are used, etc. The file is pretty self-explanatory but includes comments as well.

Code analysis is now integrated with your projects. Where you're building from is irrelevant - whether it's Visual Studio, the command line, or your build server. In all cases code analysis will execute for your project. You can download an example solution showing all this in action here.

Code Analysis without Visual Studio

The code analysis tool included in Visual Studio is like a code review, only it's super-fast and doesn't require the twisting of any arms. It checks your .NET binaries for common mistakes in various areas such as design, performance, and security. You can instruct it to treat specific issues as errors so that your project fails to build until problems are rectified. You can even write custom rules and plug them in, or just download some rules provided by the community.

Because it's fast and automated, it is feasible to have code analysis run *every time you build*. This is ideal because identifying these problems early can save you a bunch of time in the long run. Relying on developers running the tooling "when they get a chance" is bound to result in non-conformance to the rules. Violations will compound as time goes by and developers will end up not bothering to run the tooling because of the sheer number of violations present.

Visual Studio makes it easy to configure code analysis to run for individual projects on every build, but only when building within Visual Studio itself. What happens when you build outside of Visual Studio? What happens when you want the same set of rules configured for your projects to execute during command line builds, or on your continuous integration server?

The answer is that you need to do a little more work to get this going. There seems to be a fair amount of misinformation out there on this subject. And Microsoft don't seem to be advertising the fact that it's possible, instead preferring to recommend installing VS on your build server. Not only is that expensive financially, it's expensive in terms of time taken to set up and maintain your build servers.

In the following two posts I will provide step-by-step instructions for enabling code analysis within your 2008/2010 solution with the following properties:

  • The tooling is shared and self-contained. No need for developers to install anything, nor for you to install anything on your build servers.
  • The tooling is executed whether building in Visual Studio, the command line, or on your build server.
  • All the heavy lifting is encapsulated in a separate targets file, which can simply be imported by projects for which code analysis will be executed.
  • The rules executed can be customized based on the assembly under test. For example, you might relax rules for unit test projects.

The next post will describe how to enable code analysis integration for Visual Studio 2008, and the one following that will cover the same topic for Visual Studio 2010. The steps are slightly different because code analysis underwent some changes in 2010.

By the way, if you're wondering "why not just download FxCop and integrate that?" then the answer is that FxCop has fewer rules and has a slower engine than code analysis (in 2010 at least). In general, you can consider FxCop the outdated predecessor to code analysis.