It is good starting point to understand what is and how to use StyleCop from http://blogs.msdn.com/sourceanalysis/pages/source-analysis-msbuild-integration.aspx.
Unfortunately, one of our customer use ClearCase and CruiseControl.NET as the build server practicing Continuous Integration. In my opinion and from experience with dealing with large number of projects in the script file, it is best to keep .csproj alone because it is hard to debug what is going on in the main MSBuild script if the custom tasks are embedded or custom targets are imported in .csproj files.
To avoid embedding
<UsingTask
AssemblyFile="$(StyleCopRoot)Microsoft.SourceAnalysis.dll"
TaskName="SourceAnalysisTask"/>
References used to research and learn
http://code.msdn.microsoft.com/sourceanalysis
http://blogs.msdn.com/sourceanalysis/pages/source-analysis-msbuild-integration.aspx
Assumption
1) The reader is somewhat familiar with MSBuild concept.
2) The reader is now using TFS build server and using other CI tools like
CruiseControl.Net.
About the project
Download: StyleCopDemo.zip
1) There are four batch files created at the root of the project that can be executed.
buildPass.bat - happy pass that passes StyleCop.
buildFail.bat - unhappy pass where StyleCop fails build.
buildExcludeFailFile.bat - demonstrates how to exlude files from being analyzed by StyleCop.
Clean.bat - removes all unnecessary files and directory.
StyleCopSetting.bat - for customizing StyleCop Settings.
2) Closer look at SourceAnalysisTask of Microsoft.SourceAnalysis.dll
Requirements
.Net 3.5
StyleCop
Step by step instruction
1) First install StyleCop. It IMPORTANT to enable "MSBuild files" feature during the installation since by default it is not included.

2) Unzip download sample project StyleCopDemo.zip to working directory.
3) Copy C:\Program Files\MSBuild\Microsoft\SourceAnalysis to contrib directory found at the root of StyleCopDemo directory
4) Open "Visual Studio 2008 Command Prompt"
5) Go to working directory of StyleCopDemo and execute buildpass.bat
build.demo.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--****************************************************************************************build.demo.targetsBuild script demonstrating how to integrate StyleCop to multiple projects****************************************************************************************--><Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5"
DefaultTargets="BuildDemoPass">
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// Set Deafult property values These properties can be overridden from msbuild command line. i.e. msbuild /p:AppRoot=c:\someproj\app\ build.demo.xml ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<PropertyGroup>
<ProjectRoot Condition=" '$(ProjectRoot)' == '' ">$(MSBuildProjectDirectory)\..\</ProjectRoot>
<BuildRoot Condition=" '$(BuildRoot)' == '' ">$(ProjectRoot)build\</BuildRoot>
<AppRoot Condition=" '$(AppRoot)' == '' ">$(ProjectRoot)src\app\</AppRoot>
<LibRoot Condition=" '$(LibRoot)' == '' ">$(ProjectRoot)src\lib\</LibRoot>
<ToolRoot Condition=" '$(ToolRoot)' == '' ">$(ProjectRoot)src\tool\</ToolRoot>
<ContribRoot>$(ProjectRoot)contrib\</ContribRoot>
<ProjectSolutionRoot>$(ProjectRoot)solution\</ProjectSolutionRoot>
<!-- StyleCop related stuff -->
<StyleCopRoot>$(ContribRoot)SourceAnalysis\v4.2\</StyleCopRoot>
<SourceAnalysisSettingsEditorEXE>$(StyleCopRoot)SourceAnalysisSettingsEditor.exe</SourceAnalysisSettingsEditorEXE>
<SourceAnalysisSettingsFile>$(BuildRoot)Settings.SourceAnalysis</SourceAnalysisSettingsFile>
</PropertyGroup>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// Import StyleCop assembly ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<UsingTask
AssemblyFile="$(StyleCopRoot)Microsoft.SourceAnalysis.dll"
TaskName="SourceAnalysisTask"/>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// Define projects here ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<ItemGroup>
<Project Include="$(AppRoot)StyleCopPassWebDemo\StyleCopPassWebDemo.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(LibRoot)StyleCopPassDemo1\StyleCopPassDemo1.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(LibRoot)StyleCopPassDemo2\StyleCopPassDemo2.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(ToolRoot)StyleCopFailWinServiceDemo\StyleCopFailWinServiceDemo.csproj">
<ProjectType>Fail</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
</ItemGroup>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// Define default values for SourceAnalysis-specific properties. ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<PropertyGroup>
<SourceAnalysisForceFullAnalysis Condition="'$(SourceAnalysisForceFullAnalysis)' == ''">true</SourceAnalysisForceFullAnalysis>
<SourceAnalysisCacheResults Condition="'$(SourceAnalysisCacheResults)' == ''">false</SourceAnalysisCacheResults>
<SourceAnalysisTreatErrorsAsWarnings Condition="'$(SourceAnalysisTreatErrorsAsWarnings)' == ''">false</SourceAnalysisTreatErrorsAsWarnings>
<SourceAnalysisEnabled Condition="'$(SourceAnalysisEnabled)' == ''">true</SourceAnalysisEnabled>
<SourceAnalysisOverrideSettingsFile Condition="'$(SourceAnalysisOverrideSettingsFile)' == ''">$(SourceAnalysisSettingsFile)</SourceAnalysisOverrideSettingsFile>
</PropertyGroup>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// BuildDemoPass Runs StyleCop on the source code that passes build. ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<Target
Name="BuildDemoPass">
<CreateItem
Include="%(Project.RootDir)%(Project.Directory)**\*.cs"
Condition=" '%(Project.ProjectType)' == 'Pass' ">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles" />
</CreateItem>
<SourceAnalysisTask
ProjectFullPath="$(MSBuildProjectFile)"
SourceFiles="@(SourceAnalysisFiles)"
ForceFullAnalysis="$(SourceAnalysisForceFullAnalysis)"
DefineConstants="$(DefineConstants)"
TreatErrorsAsWarnings="$(SourceAnalysisTreatErrorsAsWarnings)"
CacheResults="$(SourceAnalysisCacheResults)"
OverrideSettingsFile="$(SourceAnalysisOverrideSettingsFile)" />
</Target>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// BuildDemoFail Runs StyleCop on the source code that passes build. ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<Target
Name="BuildDemoFail">
<CreateItem
Include="%(Project.RootDir)%(Project.Directory)**\*.cs"
Condition=" '%(Project.ProjectType)' == 'Fail' ">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles" />
</CreateItem>
<SourceAnalysisTask
ProjectFullPath="$(MSBuildProjectFile)"
SourceFiles="@(SourceAnalysisFiles)"
ForceFullAnalysis="$(SourceAnalysisForceFullAnalysis)"
DefineConstants="$(DefineConstants)"
TreatErrorsAsWarnings="$(SourceAnalysisTreatErrorsAsWarnings)"
CacheResults="$(SourceAnalysisCacheResults)"
OverrideSettingsFile="$(SourceAnalysisOverrideSettingsFile)" />
</Target>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// EditStyleCopSetting Runs SourceAnalysisSettingsEditor.exe in order to customize the stylecop setting. ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<Target
Name="EditStyleCopSetting">
<Exec Command="$(SourceAnalysisSettingsEditorEXE) $(SourceAnalysisSettingsFile)" />
</Target>
<!-- ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// CleanAll Cleans Everything ////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////-->
<Target
Name="CleanAll">
<!-- execute Clean from solution target -->
<CreateItem Include="$(ProjectSolutionRoot)**\*.sln">
<Output TaskParameter="Include" ItemName="ProjectSolutions" />
</CreateItem>
<MSBuild
Projects="@(ProjectSolutions)"
Targets="Clean"
Properties="Configuration=Debug"
StopOnFirstFailure="false"
ContinueOnError="true"
Condition=" '@(ProjectSolutions)' != '' " />
<MSBuild
Projects="@(ProjectSolutions)"
Targets="Clean"
Properties="Configuration=Release"
StopOnFirstFailure="false"
ContinueOnError="true"
Condition=" '@(ProjectSolutions)' != '' " />
<!-- Remove bin and obj directories -->
<CreateItem
Include=
" $(AppRoot)**\*.csproj; $(LibRoot)**\*.csproj; $(ToolRoot)**\*.csproj;">
<Output TaskParameter="Include" ItemName="ProjectFiles" />
</CreateItem>
<CreateItem
Include=
" %(ProjectFiles.RootDir)%(ProjectFiles.Directory)bin; %(ProjectFiles.RootDir)%(ProjectFiles.Directory)obj;">
<Output TaskParameter="Include" ItemName="UnnecessaryDirectories" />
</CreateItem>
<RemoveDir Directories="@(UnnecessaryDirectories)" Condition=" Exists('%(UnnecessaryDirectories.Identity)') " />
</Target>
</Project>
There are two things I want to point out
1) UsingTask to acess SourceAnalysisTask in order to run StyleCop.
<UsingTask
AssemblyFile="$(StyleCopRoot)Microsoft.SourceAnalysis.dll"
TaskName="SourceAnalysisTask"/>
2) Using SourceAnalysisTask
<Target
Name="BuildDemoPass">
<CreateItem
Include="%(Project.RootDir)%(Project.Directory)**\*.cs"
Condition=" '%(Project.ProjectType)' == 'Pass' ">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles" />
</CreateItem>
<SourceAnalysisTask
ProjectFullPath="$(MSBuildProjectFile)"
SourceFiles="@(SourceAnalysisFiles)"
ForceFullAnalysis="$(SourceAnalysisForceFullAnalysis)"
DefineConstants="$(DefineConstants)"
TreatErrorsAsWarnings="$(SourceAnalysisTreatErrorsAsWarnings)"
CacheResults="$(SourceAnalysisCacheResults)"
OverrideSettingsFile="$(SourceAnalysisOverrideSettingsFile)" />
</Target>
Notice that in Microsoft.SourceAnalysis.Targets it is gather Compile items in order to check with StyleCop
<CreateItem Include="@(Compile)" Condition="'%(Compile.ExcludeFromSourceAnalysis)'!='true'">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles"/>
</CreateItem>
In my case, I am building my own first by defining
<ItemGroup>
<Project Include="$(AppRoot)StyleCopPassWebDemo\StyleCopPassWebDemo.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(LibRoot)StyleCopPassDemo1\StyleCopPassDemo1.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(LibRoot)StyleCopPassDemo2\StyleCopPassDemo2.csproj">
<ProjectType>Pass</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
<Project Include="$(ToolRoot)StyleCopFailWinServiceDemo\StyleCopFailWinServiceDemo.csproj">
<ProjectType>Fail</ProjectType>
<StyleCop>yes</StyleCop>
</Project>
</ItemGroup>
and gathering .cs projects. Notice that meta tag are defined. These tags are useful for debugging or filtering out unwanted projects for specific behavior. For example, during CreateItem of files to be to analyzed by StyleCop I only want files that will pass the build so I use Condition=" '%(Project.ProjectType)' == 'Pass' ".
In this build script I am using Project meta tag StyleCop. This is useful for deugging or disabling projects from being analyzed by StyleCop. Change the conditon to Condition=" '%(Project.ProjectType)' == 'Pass' and '%(Project.StyleCop)' == yes'". In real build script, there are bunch of these metag tags like FxCop, UnitTest, Package, NDepend, and NCover so that specific projects are not accountable to be part of specific task. I found it to be very useful in deugging large projects and be able to maintain.
<CreateItem
Include="%(Project.RootDir)%(Project.Directory)**\*.cs"
Condition=" '%(Project.ProjectType)' == 'Pass' ">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles" />
</CreateItem>
For most of the projects I am only concerned about .cs files there other files that are compilerable and above CreateItem of SourceAnalysisFiles can be improved to include files that need to be checked. This type of control is what I like since I can control what project and what files can be checked.
Notice that in Microsoft.SourceAnalysis.Targets SourceAnalysisFiles are crated if the meta tag on Compile item meets Condition="'%(Compile.ExcludeFromSourceAnalysis)'!='true'". Only way I know you can do this is open .csproj file and add meta tag to Compile item but this is not maintaineable in my opinion.
So again my technique descibed in this article can over come this issue by adding Exclude list to CreateItem.
<Target
Name="ExcludeFailFile">
<CreateItem
Include="%(Project.RootDir)%(Project.Directory)**\*.cs"
Exclude=
"
%(Project.RootDir)%(Project.Directory)**\Service1.cs; %(Project.RootDir)%(Project.Directory)**\Program.cs; %(Project.RootDir)%(Project.Directory)**\Service1.Designer.cs">
<Output TaskParameter="Include" ItemName="SourceAnalysisFiles" />
</CreateItem>
<SourceAnalysisTask
ProjectFullPath="$(MSBuildProjectFile)"
SourceFiles="@(SourceAnalysisFiles)"
ForceFullAnalysis="$(SourceAnalysisForceFullAnalysis)"
DefineConstants="$(DefineConstants)"
TreatErrorsAsWarnings="$(SourceAnalysisTreatErrorsAsWarnings)"
CacheResults="$(SourceAnalysisCacheResults)"
OverrideSettingsFile="$(SourceAnalysisOverrideSettingsFile)" />
</Target>
SourceAnalysisViolations.xml
Notice that after buildpass.bat or buildfail.bat, and buildExcludeFailFile.bat SourceAnalysisViolations.xml is created in build directory. This file can be picked up by CruiseControl.Net and create nice report using stylesheets. I couldn't find any stylesheets for this and I will work on it when I could get to it.
Settings.SourceAnalysis
SourceAnalysisSettingsEditor.exe
Notice in build.demo.xml there is SourceAnalysisSettingsFile property. It is customized setting for this demo. Much similar to FxCop rules you can include or exclude rules.
From command prompt type StyleCopSetting.bat to see the settings in GUI.

For StyleCopDemo project followings are disabled.
- Dacumentation Rules - Disabled all of them
- Ordering Rules - SA1200
- Spacing Rules - SA1027
- Hungarian - add underscore (_)
- Naming Rules - SA1301
- Readability Rules - SA1101
- Layout Rules - SA1505, SA1508
Conclusion
StyleCop is like FxCop except it runs before compilation of the projects and there are some overlaps between what StyleCop checks vs FxCop checks. I think there are some debate over StyleCop is too rigid and such. My personal stand on this is that any type of Best Practices that can be enforced is good thing. FxCop is used on all our projects and it has helped tramendously in helping coding standards and I am very happy to see tools like StyleCop to help even more. Much like FxCop, StyleCop has settings or rules which can be disabled so rigidity of the rules can be losened up or customized for the company's needs and wants.
For future StyleCop I would like to see something similar to FxCop where files can be dropped from GUI just as FxCop GUI allows dll or exe to be droped and saved to file. This allows very clean automation and ability to control what gets checked.
1 comment:
Nice entry, I just blogged on creating custom rules for StyleCop, requiring instance variables to be prefixed with an underscore.
http://scottwhite.blogspot.com/2008/11/creating-custom-stylecop-rules-in-c.html
I agree with your comments, I would like to see Microsoft make a CLI for StyleCop and it would be nice if they created a plugin for CruiseControl.Net as they have for TFS (CodePlex).
Post a Comment