One of the hardest build processes to implement on existing .Net solutions is Code Analysis.  The main reason for this is the seemingly impossible task of fixing the issues that are flagged when turning analysis on for the first time.  At the end of the day, a rigorous code review and check in policies that run specified rules will be give the code a more consistent and maintainable state. Here is how I implemented Code Analysis for a Solution that has 150+ projects of varying types.

Requirements to implement:

  • MSBuild 4.0 – There are several new properties and functions used to do this.  I am also targeting the 4.0 framework here.

Every Project Needs a Custom Import

As code bases, become large, there is an ever increasing need to manage or extend projects from a centralized location.  Since projects are MSBuild based, it is quite simple to do this. 

  1. Create a MSBuild project file that contains common settings.  Here is an example of mine:
  1. <Project xmlns=http://schemas.microsoft.com/developer/msbuild/2003&#8221; ToolsVersion=“4.0”>
  2.     <!–
  3.      Global properties defined in this file
  4.      –>
  5.     <PropertyGroup>
  6.         <RootDir>$(MSBuildThisFileDirectory)</RootDir>
  7.         <Framework4Dir>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\</Framework4Dir>
  8.      </PropertyGroup>
  9.  
  10.  
  11.     <PropertyGroup>
  12.         <ExternalDir>$(RootDir)Externals\</ExternalDir>
  13.         <Azure1dot2Dir>$(ExternalDir)Azure\v1.2\</Azure1dot2Dir>
  14.      </PropertyGroup>  
  15.  
  16.  
  17.     <PropertyGroup>
  18.     <SpecificReferencePaths>
  19.         $(Azure1dot2Dir);
  20.         $(Framework4Dir)
  21.     </SpecificReferencePaths>    
  22. </PropertyGroup>
  23.  
  24.  
  25. <PropertyGroup>
  26.     <AssemblySearchPaths>$(SpecificReferencePaths)</AssemblySearchPaths>
  27. </PropertyGroup>
  28.  
  29. <PropertyGroup>
  30.         <Configuration Condition=” ‘$(Configuration)’ == ” “>Debug</Configuration>
  31.         <Platform Condition=” ‘$(Platform)’ == ” “>AnyCPU</Platform>
  32.         <BuildInParallel Condition=“‘$(BuildInParallel)’==””>false</BuildInParallel>
  33.     <SkipNonexistentProjects Condition=“‘$(SkipNonexistentProjects)’==””>false</SkipNonexistentProjects>
  34.     <RunCodeAnalysis Condition=“‘$(RunCodeAnalysis)’ == ””>false</RunCodeAnalysis>
  35. </PropertyGroup>
  36.      
  37.      
  38. </Project>

The important area in regards to code analysis is below.  Notice that there are two properties that can be set:  RunCodeAnalysis and CodeAnalysisRuleset.

  1. <RunCodeAnalysis Condition=“‘$(RunCodeAnalysis)’ == ””>False</RunCodeAnalysis>
  2. <CodeAnalysisRuleSet>$(RootDir)Ruleset.ruleset</CodeAnalysisRuleSet>
  1. Now at the top of each project file you will want to add an imports statement to this file.  In order for me to do this quickly, I wrote a small console application that opened up each project file as an XML document add inserted the node as appropriate.  Below are the first three lines of each project (build.setting is my file as shown above):
  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <Project ToolsVersion=“4.0” DefaultTargets=“Build” xmlns=http://schemas.microsoft.com/developer/msbuild/2003&#8221;>
  3.   <Import Project=“$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.setting))\build.setting” />

Creating a Global Suppressions File

  1. At the root of your solution create a file named GlobalSuppressions.cs.  This file will be used to capture all suppressions from the code analysis rules.  If you have specific GlobalSuppressions files on a per project basis, move the contents to this root file and delete those files.
  2. After this is complete, add this root file as a linked file to each of your projects.  In order for any suppressions to take effect they must be compiled into the code during the build process.

Items for customization: 

  • The suppressions file can be customized to a different name through the following property in MSBuild:  CodeAnalysisModuleSuppressionsFile
  • In the import file, you can include the suppressions file in the Compile ItemGroup.  This will allow you not to have any linked files and it will always be there upon build.  The downside to this is that suppressions cannot be directly added from the Warnings/Errors Window and will have to be done manually.  I chose to do it this way to have a baseline of suppressions.

Creating a Centralized Ruleset

  1. This is pretty straightforward, just create a new ruleset and place it in the root directory. 
  2. Match the name of the ruleset file to the property in the custom settings file.
  3. Define your rules that you want to turn on and set the appropriate warning and error levels

Removing project level code analysis configurations

Projects may or may not have code analysis turned on.  Remove any code analysis properties from the project files as these settings will overwrite the default settings.  You can always place a condition argument for each of these properties to determine what you want them to do if a global property has not been set.

Capturing initial suppressions

At this point all configurations should be in place for you to run code analysis rules.  Make sure that the the RunCodeAnalysis property is set to true and build your solution.  It may take a lot longer to build than you normally are used to.  Depending on the levels that you set up for the code rules, they should be displayed in the errors window.

After the build is complete you can right-click on any rule that failed and choose to suppress to project suppression file.  Since the GlobalSuppressions file is linked, you should see it added to that file.  Suppress and clean up.   It is that easy.

Hint:  Sort the broken rules by description and then add them to the project suppressions file.  That way they can be grouped together by rule.  This will enable you to later on fix a core group of rules and remove them from baseline suppressions.

Maintaining and Improving the Code Analysis Process

  1. Once the initial suppression is done, remove the linked file from each of the projects and add it to the Compile ItemGroup in the Imports file.  this solves 2 purposes.
    • It will still get compiled in each project and suppressions will take place.
    • It will also allow for project level suppressions if needed.   I would also recommend that you change the name of the baseline suppressions file so that there are no conflicts.
  2. Have a gatekeeper on the ruleset and baseline suppression file.
  3. Task developers to fix a number of suppressions in the baseline per day.  It will go quickly.
  4. Be diligent in code reviews and don’t allow suppressions in source unless absolutely necessary.  Also, be on the lookout when project level suppression files turn up.

What are ways that you handle static code analysis?  Do you customize project files and use an imports file?

Advertisements