Skip to content

Custom MSBuild Integration

Christophe HEISER edited this page Aug 16, 2015 · 1 revision

StyleCop provides a default MSBuild task and targets file which can be used in most common scenarios to integrate StyleCop into an MSBuild-based build environment. This article describes how to write a custom MSBuild task for StyleCop to enable scenarios which are not possible with the default MSBuild task provided with the tool.

Creating a Custom MSBuild Task for StyleCop

To enable advanced MSBuild scenarios, it is necessary to write a custom MSBuild task to wrap the StyleCop toolset. To get started, create a Class Library project in Visual Studio, create a new class within this project, and copy and paste the sample code from below into this class. This provides a default MSBuild task for StyleCop which can be modified to suit your needs.

Code
namespace MyCustomStyleCopTask
{
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
public sealed class StyleCopTask : Task
{
    private const string MSBuildErrorCode = null;
    private const string MSBuildSubCategory = null;
    private const int DefaultViolationLimit = 10000;

    private ITaskItem[] inputSourceFiles = new ITaskItem[0];
    private ITaskItem inputProjectFullPath;
    private ITaskItem[] inputAdditionalAddinPaths = new ITaskItem[0];
    private bool inputForceFullAnalysis;
    private string[] inputDefineConstants = new string[0];
    private bool inputTreatErrorsAsWarnings;
    private bool inputCacheResults;
    private ITaskItem inputOverrideSettingsFile;
    private ITaskItem outputFile;
    private ITaskItem maxViolationCount;

    private bool succeeded = true;
    private int violationCount;
    private int violationLimit;

    public ITaskItem[] SourceFiles
    {
        get { return this.inputSourceFiles; }
        set { this.inputSourceFiles = value; }
    }

    public ITaskItem ProjectFullPath
    {
        get { return this.inputProjectFullPath; }
        set { this.inputProjectFullPath = value; }
    }

    public ITaskItem[] AdditionalAddinPaths
    {
        get { return this.inputAdditionalAddinPaths; }
        set { this.inputAdditionalAddinPaths = value; }
    }

    public bool ForceFullAnalysis
    {
        get { return this.inputForceFullAnalysis; }
        set { this.inputForceFullAnalysis = value; }
    }

    public string[] DefineConstants
    {
        get { return this.inputDefineConstants; }
        set { this.inputDefineConstants = value; }
    }

    public bool TreatErrorsAsWarnings
    {
        get { return this.inputTreatErrorsAsWarnings; }
        set { this.inputTreatErrorsAsWarnings = value; }
    }

    public bool CacheResults
    {
        get { return this.inputCacheResults; }
        set { this.inputCacheResults = value; }
    }

    public ITaskItem OverrideSettingsFile
    {
        get { return this.inputOverrideSettingsFile; }
        set { this.inputOverrideSettingsFile = value; }
    }

    public ITaskItem OutputFile
    {
        get { return this.outputFile; }
        set { this.outputFile = value; }
    }

    public ITaskItem MaxViolationCount
    {
        get { return this.maxViolationCount; }
        set { this.maxViolationCount = value; }
    }

    public override bool Execute()
    {
        // Clear the violation count and set the violation limit for the project.
        this.violationCount = 0;
        this.violationLimit = 0;

        if (this.maxViolationCount != null)
        {
            int.TryParse(this.maxViolationCount.ItemSpec, out this.violationLimit);
        }

        if (this.violationLimit == 0)
        {
            this.violationLimit = DefaultViolationLimit;
        }

        // Get settings files (if null or empty use null filename so it uses right default).
        string overrideSettingsFileName = null;
        if (this.inputOverrideSettingsFile != null && this.inputOverrideSettingsFile.ItemSpec.Length > 0)
        {
            overrideSettingsFileName = this.inputOverrideSettingsFile.ItemSpec;
        }

        // Get addin paths.
        List<string> addinPaths = new List<string>();
        foreach (ITaskItem addinPath in this.inputAdditionalAddinPaths)
        {
            addinPaths.Add(addinPath.GetMetadata("FullPath"));
        }

        // Create the StyleCop console.
        StyleCopConsole console = new StyleCopConsole(
            overrideSettingsFileName,
            this.inputCacheResults,
            this.outputFile == null ? null : this.outputFile.ItemSpec,
            addinPaths,
            true);

        // Create the configuration.
        Configuration configuration = new Configuration(this.inputDefineConstants);

        if (this.inputProjectFullPath != null && this.inputProjectFullPath.ItemSpec != null)
        {
            // Create a CodeProject object for these files.
            CodeProject project = new CodeProject(
                this.inputProjectFullPath.ItemSpec.GetHashCode(),
                this.inputProjectFullPath.ItemSpec,
                configuration);

            // Add each source file to this project.
            foreach (ITaskItem inputSourceFile in this.inputSourceFiles)
            {
                console.Core.Environment.AddSourceCode(project, inputSourceFile.ItemSpec, null);
            }

            try
            {
                // Subscribe to events
                console.OutputGenerated += this.OnOutputGenerated;
                console.ViolationEncountered += this.OnViolationEncountered;

                // Analyze the source files
                CodeProject[] projects = new CodeProject[] { project };
                console.Start(projects, this.inputForceFullAnalysis);
            }
            finally
            {
                // Unsubscribe from events
                console.OutputGenerated -= this.OnOutputGenerated;
                console.ViolationEncountered -= this.OnViolationEncountered;
            }
        }

        return this.succeeded;
    }

    private void OnViolationEncountered(object sender, ViolationEventArgs e)
    {
        if (this.violationLimit < 0 || this.violationCount < this.violationLimit)
        {
            this.violationCount++;

            // Does the violation qualify for breaking the build?
            if (!(e.Warning || this.inputTreatErrorsAsWarnings))
            {
                this.succeeded = false;
            }

            string path = string.Empty;
            if (e.SourceCode != null && e.SourceCode.Path != null && e.SourceCode.Path.Length > 0)
            {
                path = e.SourceCode.Path;
            }
            else if (e.Element != null &&
                e.Element.Document != null &&
                e.Element.Document.SourceCode != null &&
                e.Element.Document.SourceCode.Path != null)
            {
                path = e.Element.Document.SourceCode.Path;
            }

            // Prepend the rule check-id to the message.
            string message = string.Concat(e.Violation.Rule.CheckId, ": ", e.Message);

            lock (this)
            {
                if (e.Warning || this.inputTreatErrorsAsWarnings)
                {
                    Log.LogWarning(MSBuildSubCategory, MSBuildErrorCode, null, path, e.LineNumber, 1, 0, 0, message);
                }
                else
                {
                    Log.LogError(MSBuildSubCategory, MSBuildErrorCode, null, path, e.LineNumber, 1, 0, 0, message);
                }
            }
        }
    }

    private void OnOutputGenerated(object sender, OutputEventArgs e)
    {
        lock (this)
        {
            Log.LogMessage(e.Output.Trim());
        }
    }
}

}

Next, create a new file called MyCustomStyleCopTask.targets, and copy and paste the following code into this targets file. You will need to modify the contents of this targets file to match any modifications you make to your custom MSBuild task.

Code
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Specify where tasks are implemented. -->
<UsingTask AssemblyFile="MyCustomStyleCopTask.dll" TaskName="MyCustomStyleCopTask"/>

<PropertyGroup> <BuildDependsOn>$(BuildDependsOn);StyleCop</BuildDependsOn> <RebuildDependsOn>StyleCopForceFullAnalysis;$(RebuildDependsOn)</RebuildDependsOn> </PropertyGroup>

<!-- Define StyleCopForceFullAnalysis property. --> <PropertyGroup Condition="'$(StyleCopForceFullAnalysis)' == ''"> <StyleCopForceFullAnalysis>false</StyleCopForceFullAnalysis> </PropertyGroup>

<!-- Define StyleCopCacheResults property. --> <PropertyGroup Condition="'$(StyleCopCacheResults)' == ''"> <StyleCopCacheResults>true</StyleCopCacheResults> </PropertyGroup>

<!-- Define StyleCopTreatErrorsAsWarnings property. --> <PropertyGroup Condition="'$(StyleCopTreatErrorsAsWarnings)' == ''"> <StyleCopTreatErrorsAsWarnings>true</StyleCopTreatErrorsAsWarnings> </PropertyGroup>

<!-- Define StyleCopEnabled property. --> <PropertyGroup Condition="'$(StyleCopEnabled)' == ''"> <StyleCopEnabled>true</StyleCopEnabled> </PropertyGroup>

<!-- Define StyleCopOverrideSettingsFile property. --> <PropertyGroup Condition="'$(StyleCopOverrideSettingsFile)' == ''"> <StyleCopOverrideSettingsFile> </StyleCopOverrideSettingsFile> </PropertyGroup>

<!-- Define StyleCopOutputFile property. --> <PropertyGroup Condition="'$(StyleCopOutputFile)' == ''"> <StyleCopOutputFile>$(IntermediateOutputPath)StyleCopViolations.xml</StyleCopOutputFile> </PropertyGroup>

<!-- Define StyleCopMaxViolationCount property . --> <PropertyGroup Condition="'$(StyleCopMaxViolationCount)' == ''"> <!-- Specifying 0 will cause StyleCop to use the default violation count limit. Specifying any positive number will cause StyleCop to use that number as the violation count limit. Specifying any negative number will cause StyleCop to allow any number of violations without limit. --> <StyleCopMaxViolationCount>0</StyleCopMaxViolationCount> </PropertyGroup>

<!-- Define target: StyleCopForceFullAnalysis --> <Target Name="StyleCopForceFullAnalysis"> <CreateProperty Value="true"> <Output TaskParameter="Value" PropertyName="StyleCopForceFullAnalysis" /> </CreateProperty> </Target>

<!-- Define target: StyleCop --> <Target Name="StyleCop" Condition="'$(StyleCopEnabled)' != 'false'"> <!-- Determine what files should be checked. Take all Compile items, but exclude those that have set ExcludeFromStyleCop=true. --> <CreateItem Include="@(Compile)" Condition="'%(Compile.ExcludeFromStyleCop)' != 'true'"> <Output TaskParameter="Include" ItemName="StyleCopFiles"/> </CreateItem>

&lt;Message Text="Forcing full StyleCop reanalysis." Condition="'$(StyleCopForceFullAnalysis)' == 'true'" Importance="Low" /&gt;
&lt;Message Text="Analyzing @(StyleCopFiles)" Importance="Low" /&gt;

&lt;!-- Run the StyleCop MSBuild task. --&gt;
&lt;MyCustomStyleCopTask
  ProjectFullPath="$(MSBuildProjectFile)"
  SourceFiles="@(StyleCopFiles)"
  AdditionalAddinPaths="@(StyleCopAdditionalAddinPaths)"
  ForceFullAnalysis="$(StyleCopForceFullAnalysis)"
  DefineConstants="$(DefineConstants)"
  TreatErrorsAsWarnings="$(StyleCopTreatErrorsAsWarnings)"
  CacheResults="$(StyleCopCacheResults)"
  OverrideSettingsFile="$(StyleCopOverrideSettingsFile)"
  OutputFile="$(StyleCopOutputFile)"
  MaxViolationCount="$(StyleCopMaxViolationCount)"
/&gt;

&lt;!-- Make output files cleanable --&gt;
&lt;CreateItem Include="$(StyleCopOutputFile)"&gt;
  &lt;Output TaskParameter="Include" ItemName="FileWrites"/&gt;
&lt;/CreateItem&gt;

&lt;!-- Add the StyleCop.cache file to the list of files we've written - so they can be cleaned up on a Build Clean. --&gt;
&lt;CreateItem Include="StyleCop.Cache" Condition="'$(StyleCopCacheResults)' == 'true'"&gt;
  &lt;Output TaskParameter="Include" ItemName="FileWrites"/&gt;
&lt;/CreateItem&gt;

</Target> </Project>

Clone this wiki locally