8
votes

I have added an Inspections (.NET) build step to a TeamCity (v8) project consisting of a Visual Studio 2010 solution build step for a single dummy C# class.

The build step has failure conditions that are set to fail if I get any inspection Errors or Warnings.

My dummy class only generates 2 Suggestions but the build fails claiming

"Build failure on metric change: Number of inspection warnings is too large: 3".

Is there a way to make the TeamCity step ignore the Suggestions?

I enabled debug output and the step definitely has no Warnings in it:

<!-- Generated by InspectCode 2.0.0.0 -->
<Report ToolsVersion="2.0">
<Information>
 <Solution>Demo.sln</Solution>
 <InspectionScope><Element>Solution</Element></InspectionScope>
</Information>
<IssueTypes><IssueType Id="InconsistentNaming" Category="Constraints Violations" Description="Inconsistent Naming" Severity="SUGGESTION"/>
 <IssueType Id="UnusedMember.Global" Category="Redundancies in Symbol Declarations" Description="Type or type member is never used: Non-private accessibility" Severity="SUGGESTION"/>
</IssueTypes>
<Issues>
 <Project Name="Demo">
  <Issue TypeId="UnusedMember.Global" File="Demo\Class1.cs" Offset="36-42" Line="3" Message="Class 'Class1' is never used"/>
  <Issue TypeId="UnusedMember.Global" File="Demo\Class1.cs" Offset="71-76" Line="5" Message="Field 'maybe' is never used"/>
  <Issue TypeId="InconsistentNaming" File="Demo\Class1.cs" Offset="71-76" Line="5" Message="Name 'maybe' does not match rule 'Fields (not private)'. Suggested name is 'Maybe'."/>
 </Project>
</Issues>
</Report>

The dummy class is as follows:

namespace Demo
{
    public class Class1
    {
        public bool maybe = true;
    }
}

Note that I still want Suggestions and Hints reported in Visual Studio so changing all non-Warnings and Errors to Do Not Report in the ReSharper settings is not an option (or creating and maintaining such a settings file solely for TeamCity to use).

3
Did you ever find a solution to this? I have the same problem. ThanksAppetere
Nope, but it is on the jetbrains bug tracker: youtrack.jetbrains.com/issue/TW-30714stephen

3 Answers

6
votes

Following the previous suggested technique I wrote a PowerShell script that runs InspectCode, then performs an XSLT transform of the output file and imports the result into TeamCity. You can run this as a PowerShell build step, passing the solution filename as the first script argument and the variable %system.teamcity.build.tempDir% as the second.

TransformResults.xslt:

(This should be located in the same folder as the PowerShell script)

<xsl:stylesheet
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0">
  <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
  <xsl:strip-space elements="*"/>

  <xsl:key name="issueType" match="IssueTypes/IssueType" use="@Id" />

  <!--
  Template to strip out issues where the severity is HINT or SUGGESTION
  -->
  <xsl:template match="Issues//Issue">
    <xsl:choose>
      <xsl:when test="not(contains('HINT SUGGESTION', key('issueType', @TypeId)/@Severity))" >
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

RunInspectCode.ps1:

param( 
    [Parameter(Mandatory = $true)][string]$solutionFile, 
    [Parameter(Mandatory = $true)][string]$buildTempDir)

# Runs the Jetbrains Resharper code inspection utility (InspectCode)
# - see https://confluence.jetbrains.com/display/NETCOM/Introducing+InspectCode
# We don't want to use the built-in TeamCity inspection plugin because it reports
# HINT and SUGGESTION issues, which we don't want to make mandatory.
# Basically we run InspectCode to generate an intermediate XML report, then we 
# transform that report via XSLT to strip out the issues we don't want to see.

$ErrorActionPreference = "Stop"

# Required if Powershell < 3.0
#$PSScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path

# General-purpose function to transform XML
function Transform-Xml {
    param([string]$stylesheetPath=$(throw '$stylesheetPath is required'),
        [string[]]$xmlPath)

    begin {
        # Compile the stylesheet
        $compiledXslt = New-Object System.Xml.Xsl.XslCompiledTransform
        $compiledXslt.Load($stylesheetPath)

        function transformXmlDoc {
            param([xml]$xml, [System.Xml.Xsl.XslCompiledTransform]$xslt = $(throw '$xslt param is required'))

            $output = New-Object System.IO.MemoryStream
            $arglist = new-object System.Xml.Xsl.XsltArgumentList
            $outputReader = new-object System.IO.StreamReader($output)

            $xmlReader = New-Object System.Xml.XmlNodeReader($xml)
            $xslt.Transform($xmlReader, $arglist, $output)
            $output.position = 0

            $transformed = [string]$outputReader.ReadToEnd()
            $outputReader.Close()
            return $transformed
        }
        function applyStylesheetToXml([xml]$xml) {
            $result = transformXmlDoc $xml $compiledXslt
            [string]::join([environment]::newline, $result)
        }
        function applyStylesheetToXmlFile($sourcePath) {
            $rpath = resolve-path $sourcePath
            [xml]$xml = Get-Content $rpath
            $result = transformXmlDoc $xml $compiledXslt
            [string]::join([environment]::newline, $result)
        }
    }

    process {
        if ($_) {
            if ($_ -is [xml]) {
                applyStylesheetToXml $_
            }
            elseif ($_ -is [IO.FileInfo]) {
                applyStylesheetToXmlFile $_.FullName
            }
            elseif ($_ -is [string]) {
                if (test-path -type Leaf $_) {
                    applyStylesheetToXmlFile $_
                }
                else {
                    applyStylesheetToXml $_
                }
            }
            else {
                throw "Pipeline input type must be one of: [xml], [string] or [IO.FileInfo]"
            }
        }
    }

    end {
        if ($xmlPath) {
            foreach ($path in $xmlPath) {
                applyStylesheetToXmlFile $path
            }
        }
    }
}

$solutionPath = Resolve-Path $solutionFile
$tempDir = Resolve-Path $buildTempDir

# Locate inspectcode
if (-Not (Test-Path Env:\RESHARPER_TOOLS_PATH)) {
    throw 'RESHARPER_TOOLS_PATH environment variable not set'
}

$inspectcode = Join-Path $env:RESHARPER_TOOLS_PATH 'inspectcode.exe'
if (-Not (Test-Path -type Leaf $inspectcode)) {
    throw 'InpectCode executable not found'
}

# Path to XSLT transformation file
$fullXsltPath = Resolve-Path (Join-Path $PSScriptRoot 'TransformResults.xslt')

# Names of intermediate and final output files
$intermediateOutput = Join-Path $tempDir 'inspectcode-intermediate.xml'
$outputPath = Join-Path $tempDir 'inspectcode-final.xml'

# Run InspectCode 
& $inspectcode "/o=$intermediateOutput" $solutionPath

# Transform the inspection output to remove HINT and SUGGESTION
$transformed = Transform-Xml $fullXsltPath @($intermediateOutput)

# The PowerShell Out-File cmdlet always adds a UTF8 Byte Order Marker so we need to take steps to
# ensure this is not emitted, as the TeamCity XML importer will choke on it
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllLines($outputPath, $transformed, $encoding)

# When PowerShell is started through TeamCity's Command Runner, the standard
# output will be wrapped at column 80 (a default). This has a negative impact
# on service messages, as TeamCity quite naturally fails parsing a wrapped
# message. The solution is to set a new, much wider output width. It will
# only be set if TEAMCITY_VERSION exists, i.e., if started by TeamCity.
$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(8192,50)

Write-Output "##teamcity[importData type='ReSharperInspectCode' path='$outputPath']"
3
votes

This is still not possible with TeamCity 9 (it starts to get embarrassing for JetBrains, as this feature is really a joke without being able to limit only to warnings and errors).

But there is a do-it-yourself workaround

Using the free Resharper Command Line Tool "inspectcode" it is possible to generate an XML report for all Resharper issues:

inspectcode.exe /o=codeInspectionReport.xml solution.sln

This report still contains all issues, but it is then possible to throw an XPath query at the report to count the number of errors and warnings.

Here is how I do it in a Psake build file:

Task ReportInspectCode {
  [xml]$xml = Get-Content 'codeInspectionReport.xml'
  $nodes = $xml.selectNodes("/Report/IssueTypes/IssueType[@Severity='ERROR' or (@Severity='WARNING' and @Id!='InconsistentNaming')]")

  if ($nodes.Count -gt 0) {
    Echo $nodes
    throw "There are code inspection errors or warnings, see Resharper -> Inspect -> Code Issues in Solution"
  }
  else {
    Echo "Code inspection completed without warnings or errors"
  }

}

As you can see, I also had to filter out spurious InconsistentNaming warnings, but you could remove that part if you don't need it.

And you could of course use a similar xpath query in an XSLT transformation to get rid of all non-warnings and non-errors. This way, you could use TeamCity to process the resulting file and display it as code inspection result.

0
votes

As workaround you can add --severity=WARNING argument to inspectCode.exe in InspectCode Options