diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..bd21071 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,31 @@ +#---------------------------------# +# Build Image # +#---------------------------------# +image: Visual Studio 2017 + +#---------------------------------# +# Build Script # +#---------------------------------# +build_script: + - ps: .\build.ps1 -Target AppVeyor + +# Tests +test: off + +#---------------------------------# +# Branches to build # +#---------------------------------# +branches: + # Whitelist + only: + - develop + - master + - /release/.*/ + - /hotfix/.*/ + +#---------------------------------# +# Build Cache # +#---------------------------------# +cache: +- src\packages -> src\**\packages.config +- tools -> setup.cake \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 940794e..ba4e60c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ +# Project specific + +BuildArtifacts/ + +# Created by https://www.gitignore.io/api/cake,visualstudio + +### Cake ### +tools/* +!tools/packages.config + +### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -148,9 +159,11 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml +# TODO: Uncomment the next line to ignore your web deploy settings. +# By default, sensitive information, such as encrypted password +# should be stored in the .pubxml.user file. +#*.pubxml +*.pubxml.user *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to @@ -286,3 +299,9 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs + +### VisualStudio Patch ### +# By default, sensitive information, such as encrypted password +# should be stored in the .pubxml.user file. + +# End of https://www.gitignore.io/api/cake,visualstudio \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..eb79eee --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: csharp +script: + - ./build.sh -v diagnostic +os: + - linux + - osx +cache: + directories: + - packages + - tools diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..078295e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contribution Guidelines + +This repository uses [GitFlow] with default configuration. +Development is happening on `develop` branch. + +To contribute: + +* Fork this repository. +* Create a feature branch from `develop`. +* Implement your changes. +* Push your feature branch. +* Create a pull request. + +## Build + +To build this package we are using Cake. + +On Windows PowerShell run: + +```powershell +./build +``` + +On OSX/Linux run: + +```bash +./build.sh +``` + +## Release + +See [Cake.Recipe documentation] how to create a new release of this addin. + +[GitFlow]: (http://nvie.com/posts/a-successful-git-branching-model/) +[Cake.Recipe documentation]: https://cake-contrib.github.io/Cake.Recipe/docs/usage/creating-release \ No newline at end of file diff --git a/GitReleaseManager.yaml b/GitReleaseManager.yaml new file mode 100644 index 0000000..721c1ba --- /dev/null +++ b/GitReleaseManager.yaml @@ -0,0 +1,12 @@ +issue-labels-include: +- Breaking change +- Feature +- Bug +- Improvement +- Documentation +issue-labels-exclude: +- Build +issue-labels-alias: + - name: Documentation + header: Documentation + plural: Documentation \ No newline at end of file diff --git a/LICENSE b/LICENSE index f90383f..e052ef6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Pascal Berger +Copyright (c) 2017 BBT Software AG and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 78f0579..7de82d6 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# Cake.Issues.MsBuild \ No newline at end of file +# MsBuild Issue Provider for Cake.Issues Addin + +This addin for the Cake Build Automation System allows you to read issues logged as warnings by MsBuild +using the [Cake Issues addin](https://github.com/cake-contrib/Cake.Issues). + +For more information about this add-in see the [Cake.Issues website](https://cake-contrib.github.io/Cake.Issues.Website) +and for general information about the Cake build automation system see the [Cake website](http://cakebuild.net) + +[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/cake-contrib/Cake.Issues.MsBuild/blob/feature/build/LICENSE) + +## Information + +| | Stable | Pre-release | +|:--:|:--:|:--:| +|GitHub Release|-|[![GitHub release](https://img.shields.io/github/release/cake-contrib/Cake.Issues.MsBuild.svg)](https://github.com/cake-contrib/Cake.Issues.MsBuild/releases/latest)| +|NuGet|[![NuGet](https://img.shields.io/nuget/v/Cake.Issues.MsBuild.svg)](https://www.nuget.org/packages/Cake.Issues.MsBuild)|[![NuGet](https://img.shields.io/nuget/vpre/Cake.Issues.MsBuild.svg)](https://www.nuget.org/packages/Cake.Issues.MsBuild)| + +## Build Status + +|Develop|Master| +|:--:|:--:| +|[![Build status](https://ci.appveyor.com/api/projects/status/7e9bedtgp9m30p4m/branch/develop?svg=true)](https://ci.appveyor.com/project/cakecontrib/cake-issues-msbuild/branch/develop)|[![Build status](https://ci.appveyor.com/api/projects/status/7e9bedtgp9m30p4m/branch/develop?svg=true)](https://ci.appveyor.com/project/cakecontrib/cake-issues-msbuild/branch/master)| + +## Code Coverage + +[![Coverage Status](https://coveralls.io/repos/github/cake-contrib/Cake.Issues.MsBuild/badge.svg?branch=develop)](https://coveralls.io/github/cake-contrib/Cake.Issues.MsBuild?branch=develop) + +## Quick Links + +- [Documentation](https://cake-contrib.github.io/Cake.Issues.Website) + +## Chat Room + +Come join in the conversation about this addin in our Gitter Chat Room + +[![Join the chat at https://gitter.im/cake-contrib/Lobby](https://badges.gitter.im/cake-contrib/Lobby.svg)](https://gitter.im/cake-contrib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Contributing + +Contributions are welcome. See [Contribution Guidelines](CONTRIBUTING.md). \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..bdfb32b --- /dev/null +++ b/build.ps1 @@ -0,0 +1,184 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. +.LINK +http://cakebuild.net +#> + +[CmdletBinding()] +Param( + [string]$Script = "setup.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" + +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} + +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} + +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -PreRelease -OutputDirectory `"$TOOLS_DIR`" -Source https://www.myget.org/F/cake/api/v3/index.json" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a541ec1 --- /dev/null +++ b/build.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +############################################################### +# This is the Cake bootstrapper script that is responsible for +# downloading Cake and all specified tools from NuGet. +############################################################### + +# Define directories. +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +NUGET_EXE=$TOOLS_DIR/nuget.exe +CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe + +# Define default arguments. +SCRIPT="setup.cake" +TARGET="Default" +CONFIGURATION="Release" +VERBOSITY="verbose" +DRYRUN= +SHOW_VERSION=false +SCRIPT_ARGUMENTS=() + +# Parse arguments. +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + -t|--target) TARGET="$2"; shift ;; + -c|--configuration) CONFIGURATION="$2"; shift ;; + -v|--verbosity) VERBOSITY="$2"; shift ;; + -d|--dryrun) DRYRUN="-dryrun" ;; + --version) SHOW_VERSION=true ;; + --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; + *) SCRIPT_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Make sure the tools folder exist. +if [ ! -d $TOOLS_DIR ]; then + mkdir $TOOLS_DIR +fi + +# Make sure that packages.config exist. +if [ ! -f $TOOLS_DIR/packages.config ]; then + echo "Downloading packages.config..." + curl -Lsfo $TOOLS_DIR/packages.config http://cakebuild.net/bootstrapper/packages + if [ $? -ne 0 ]; then + echo "An error occured while downloading packages.config." + exit 1 + fi +fi + +# Download NuGet if it does not exist. +if [ ! -f $NUGET_EXE ]; then + echo "Downloading NuGet..." + curl -Lsfo $NUGET_EXE https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + if [ $? -ne 0 ]; then + echo "An error occured while downloading nuget.exe." + exit 1 + fi +fi + +# Restore tools from NuGet. +pushd $TOOLS_DIR >/dev/null +mono $NUGET_EXE install -ExcludeVersion -PreRelease -Source https://www.myget.org/F/cake/api/v3/index.json +if [ $? -ne 0 ]; then + echo "Could not restore NuGet packages." + exit 1 +fi +popd >/dev/null + +# Make sure that Cake has been installed. +if [ ! -f $CAKE_EXE ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 +fi + +# Start Cake +if $SHOW_VERSION; then + exec mono $CAKE_EXE -version +else + exec mono $CAKE_EXE $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" +fi diff --git a/nuspec/nuget/Cake.Issues.MsBuild.nuspec b/nuspec/nuget/Cake.Issues.MsBuild.nuspec new file mode 100644 index 0000000..634466c --- /dev/null +++ b/nuspec/nuget/Cake.Issues.MsBuild.nuspec @@ -0,0 +1,33 @@ + + + + Cake.Issues.MsBuild + Cake.Issues.MsBuild + 0.0.0 + BBT Software AG and contributors + bbtsoftware, pascalberger, cake-contrib + MsBuild support for the Cake.Issues addin for Cake Build Automation System + +The MsBuild support for the Cake.Issues addin for Cake allows you to read issues logged as warnings in a MsBuild log. + +This addin provides the aliases for reading MsBuild warnings and providing them to the Cake.Issues addin. +It also requires the core Cake.Issues addin. + +There are also additional addins for generating reports or posting issues to pull requests. + +See the Project Site for an overview of the whole ecosystem of addins for working with issues in Cake scripts. + + https://github.com/cake-contrib/Cake.Issues.MsBuild/blob/develop/LICENSE + http://cake-contrib.github.io/Cake.Issues.Website + https://cdn.rawgit.com/cake-contrib/graphics/a5cf0f881c390650144b2243ae551d5b9f836196/png/cake-contrib-medium.png + false + Copyright © 2017 BBT Software AG and contributors + Cake Script Cake-Issues Cake-IssueProvider CodeAnalysis Linting MsBuild + https://github.com/cake-contrib/Cake.Issues.MsBuild/releases/tag/0.1.0 + + + + + + + \ No newline at end of file diff --git a/setup.cake b/setup.cake new file mode 100644 index 0000000..657a438 --- /dev/null +++ b/setup.cake @@ -0,0 +1,23 @@ +#load nuget:https://www.myget.org/F/cake-contrib/api/v2?package=Cake.Recipe&prerelease + +Environment.SetVariableNames(); + +BuildParameters.SetParameters( + context: Context, + buildSystem: BuildSystem, + sourceDirectoryPath: "./src", + title: "Cake.Issues.MsBuild", + repositoryOwner: "cake-contrib", + repositoryName: "Cake.Issues.MsBuild", + appVeyorAccountName: "cakecontrib"); + +BuildParameters.PrintParameters(Context); + +ToolSettings.SetToolSettings( + context: Context, + dupFinderExcludePattern: new string[] { BuildParameters.RootDirectoryPath + "/src/Cake.Issues.MsBuild.Tests/*.cs" }, + testCoverageFilter: "+[*]* -[xunit.*]* -[Cake.Core]* -[Cake.Testing]* -[*.Tests]* -[Cake.Issues]* -[Cake.Issues.Testing]*", + testCoverageExcludeByAttribute: "*.ExcludeFromCodeCoverage*", + testCoverageExcludeByFile: "*/*Designer.cs;*/*.g.cs;*/*.g.i.cs"); + +Build.Run(); diff --git a/src/Cake.Issues.MsBuild.Tests.ruleset b/src/Cake.Issues.MsBuild.Tests.ruleset new file mode 100644 index 0000000..73e07dc --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests.ruleset @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.Tests/Cake.Issues.MsBuild.Tests.csproj b/src/Cake.Issues.MsBuild.Tests/Cake.Issues.MsBuild.Tests.csproj new file mode 100644 index 0000000..5f7d7f5 --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/Cake.Issues.MsBuild.Tests.csproj @@ -0,0 +1,110 @@ + + + + + + Debug + AnyCPU + {C69329F6-A4D1-4E33-BD9D-5E59973BE781} + Library + Properties + Cake.Issues.MsBuild.Tests + Cake.Issues.MsBuild.Tests + v4.5.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + ..\Cake.Issues.MsBuild.Tests.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + ..\Cake.Issues.MsBuild.Tests.ruleset + + + + ..\packages\Cake.Core.0.16.2\lib\net45\Cake.Core.dll + + + ..\packages\Cake.Issues.0.1.0-beta0001\lib\net45\Cake.Issues.dll + + + ..\packages\Cake.Issues.Testing.0.1.0-beta0001\lib\net45\Cake.Issues.Testing.dll + + + ..\packages\Cake.Testing.0.16.2\lib\net45\Cake.Testing.dll + + + ..\packages\Shouldly.2.8.3\lib\net451\Shouldly.dll + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll + + + + + + + + + + + + + {1a6c74ce-4775-458a-b013-bde1986c5b88} + Cake.Issues.MsBuild + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderFixture.cs b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderFixture.cs new file mode 100644 index 0000000..830f03e --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderFixture.cs @@ -0,0 +1,49 @@ +namespace Cake.Issues.MsBuild.Tests +{ + using System.Collections.Generic; + using System.IO; + using Cake.Testing; + using Core.Diagnostics; + using IssueProvider; + + internal class MsBuildIssuesProviderFixture + { + public MsBuildIssuesProviderFixture(string fileResourceName) + { + this.Log = new FakeLog { Verbosity = Verbosity.Normal }; + + using (var stream = this.GetType().Assembly.GetManifestResourceStream("Cake.Issues.MsBuild.Tests.Testfiles." + fileResourceName)) + { + using (var sr = new StreamReader(stream)) + { + this.MsBuildIssuesSettings = + MsBuildIssuesSettings.FromContent( + sr.ReadToEnd(), + new XmlFileLoggerFormat(this.Log)); + } + } + + this.RepositorySettings = + new RepositorySettings(@"c:\Source\Cake.Issues.MsBuild"); + } + + public FakeLog Log { get; set; } + + public MsBuildIssuesSettings MsBuildIssuesSettings { get; set; } + + public RepositorySettings RepositorySettings { get; set; } + + public MsBuildIssuesProvider Create() + { + var provider = new MsBuildIssuesProvider(this.Log, this.MsBuildIssuesSettings); + provider.Initialize(this.RepositorySettings); + return provider; + } + + public IEnumerable ReadIssues() + { + var issueProvider = this.Create(); + return issueProvider.ReadIssues(IssueCommentFormat.PlainText); + } + } +} diff --git a/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderTests.cs b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderTests.cs new file mode 100644 index 0000000..0e08b9a --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesProviderTests.cs @@ -0,0 +1,39 @@ +namespace Cake.Issues.MsBuild.Tests +{ + using Cake.Testing; + using Testing; + using Xunit; + + public class MsBuildIssuesProviderTests + { + public sealed class TheMsBuildCodeAnalysisProviderCtor + { + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new MsBuildIssuesProvider( + null, + MsBuildIssuesSettings.FromContent( + "Foo", + new XmlFileLoggerFormat(new FakeLog())))); + + // Then + result.IsArgumentNullException("log"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + var result = Record.Exception(() => + new MsBuildIssuesProvider( + new FakeLog(), + null)); + + // Then + result.IsArgumentNullException("settings"); + } + } + } +} diff --git a/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesSettingsTests.cs b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesSettingsTests.cs new file mode 100644 index 0000000..1d70647 --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/MsBuildIssuesSettingsTests.cs @@ -0,0 +1,163 @@ +namespace Cake.Issues.MsBuild.Tests +{ + using System; + using System.IO; + using System.Linq; + using System.Text; + using Cake.Testing; + using Shouldly; + using Testing; + using Xunit; + + public class MsBuildIssuesSettingsTests + { + public sealed class TheMsBuildCodeAnalysisSettingsCtor + { + [Fact] + public void Should_Throw_If_LogFilePath_Is_Null() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromFilePath( + null, + new XmlFileLoggerFormat(new FakeLog()))); + + // Then + result.IsArgumentNullException("logFilePath"); + } + + [Fact] + public void Should_Throw_If_Format_For_LogFilePath_Is_Null() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromFilePath( + @"C:\foo.log", + null)); + + // Then + result.IsArgumentNullException("format"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_Null() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromContent( + null, + new XmlFileLoggerFormat(new FakeLog()))); + + // Then + result.IsArgumentNullException("logFileContent"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_Empty() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromContent( + string.Empty, + new XmlFileLoggerFormat(new FakeLog()))); + + // Then + result.IsArgumentOutOfRangeException("logFileContent"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_WhiteSpace() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromContent( + " ", + new XmlFileLoggerFormat(new FakeLog()))); + + // Then + result.IsArgumentOutOfRangeException("logFileContent"); + } + + [Fact] + public void Should_Throw_If_Format_For_LogFileContent_Is_Null() + { + // Given / When + var result = Record.Exception(() => + MsBuildIssuesSettings.FromContent( + "foo", + null)); + + // Then + result.IsArgumentNullException("format"); + } + + [Fact] + public void Should_Set_Property_Values_Passed_To_Constructor() + { + // Given + const string logFileContent = "foo"; + var format = new XmlFileLoggerFormat(new FakeLog()); + + // When + var settings = MsBuildIssuesSettings.FromContent(logFileContent, format); + + // Then + settings.LogFileContent.ShouldBe(logFileContent); + settings.Format.ShouldBe(format); + } + + [Fact] + public void Should_Read_File_From_Disk() + { + var fileName = Path.GetTempFileName(); + try + { + // Given + string expected; + using (var ms = new MemoryStream()) + using (var stream = this.GetType().Assembly.GetManifestResourceStream("Cake.Issues.MsBuild.Tests.Testfiles.IssueWithFile.xml")) + { + stream.CopyTo(ms); + var data = ms.ToArray(); + + using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + { + file.Write(data, 0, data.Length); + } + + expected = ConvertFromUtf8(data); + } + + // When + var settings = + MsBuildIssuesSettings.FromFilePath( + fileName, + new XmlFileLoggerFormat(new FakeLog())); + + // Then + settings.LogFileContent.ShouldBe(expected); + } + finally + { + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + } + } + + private static string ConvertFromUtf8(byte[] bytes) + { + var enc = new UTF8Encoding(true); + var preamble = enc.GetPreamble(); + + if (preamble.Where((p, i) => p != bytes[i]).Any()) + { + throw new ArgumentException("Not utf8-BOM"); + } + + return enc.GetString(bytes.Skip(preamble.Length).ToArray()); + } + } + } +} diff --git a/src/Cake.Issues.MsBuild.Tests/MsBuildRuleUrlResolverTests.cs b/src/Cake.Issues.MsBuild.Tests/MsBuildRuleUrlResolverTests.cs new file mode 100644 index 0000000..c0ed31d --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/MsBuildRuleUrlResolverTests.cs @@ -0,0 +1,96 @@ +namespace Cake.Issues.MsBuild.Tests +{ + using System; + using Shouldly; + using Testing; + using Xunit; + + public class MsBuildRuleUrlResolverTests + { + public sealed class TheResolveRuleUrlMethod + { + [Fact] + public void Should_Throw_If_Rule_Is_Null() + { + // Given / When + var result = Record.Exception(() => MsBuildRuleUrlResolver.Instance.ResolveRuleUrl(null)); + + // Then + result.IsArgumentNullException("rule"); + } + + [Fact] + public void Should_Throw_If_Rule_Is_Empty() + { + // Given / When + var result = Record.Exception(() => MsBuildRuleUrlResolver.Instance.ResolveRuleUrl(string.Empty)); + + // Then + result.IsArgumentOutOfRangeException("rule"); + } + + [Fact] + public void Should_Throw_If_Rule_Is_WhiteSpace() + { + // Given / When + var result = Record.Exception(() => MsBuildRuleUrlResolver.Instance.ResolveRuleUrl(" ")); + + // Then + result.IsArgumentOutOfRangeException("rule"); + } + + [Theory] + [InlineData("CA2201", "https://www.google.im/search?q=\"CA2201:\"+site:msdn.microsoft.com")] + [InlineData("SA1652", "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1652.md")] + public void Should_Resolve_Url(string rule, string expectedUrl) + { + // Given + var urlResolver = MsBuildRuleUrlResolver.Instance; + + // When + var ruleUrl = urlResolver.ResolveRuleUrl(rule); + + // Then + ruleUrl.ToString().ShouldBe(expectedUrl); + } + + [Theory] + [InlineData("CA")] + [InlineData("2201")] + [InlineData("CA2201Foo")] + [InlineData("CS0219")] + public void Should_Return_Null_For_Unknown_Rules(string rule) + { + // Given + var urlResolver = MsBuildRuleUrlResolver.Instance; + + // When + var ruleUrl = urlResolver.ResolveRuleUrl(rule); + + // Then + ruleUrl.ShouldBeNull(); + } + + [Fact] + public void Should_Resolve_Url_From_Custom_Resolvers() + { + // Given + const string foo = "FOO123"; + const string fooUrl = "http://foo.com/"; + const string bar = "BAR123"; + const string barUrl = "http://bar.com/"; + var urlResolver = MsBuildRuleUrlResolver.Instance; + urlResolver.AddUrlResolver(x => x.Rule == foo ? new Uri(fooUrl) : null); + urlResolver.AddUrlResolver(x => x.Rule == bar ? new Uri(barUrl) : null); + + // When + var fooRuleUrl = urlResolver.ResolveRuleUrl(foo); + var barRuleUrl = urlResolver.ResolveRuleUrl(bar); + + // Then + fooRuleUrl.ToString().ShouldBe(fooUrl); + barRuleUrl.ToString().ShouldBe(barUrl); + } + } + } +} diff --git a/src/Cake.Issues.MsBuild.Tests/Properties/AssemblyInfo.cs b/src/Cake.Issues.MsBuild.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c2f0bc3 --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Cake.Issues.MsBuild")] +[assembly: AssemblyDescription("MsBuild support for the Cake.Issues Addin for Cake Build Automation System")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("BBT Software AG")] +[assembly: AssemblyProduct("Cake.Issues")] +[assembly: AssemblyCopyright("Copyright © 2017 BBT Software AG, Root/Zermatt, Switzerland")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c69329f6-a4d1-4e33-bd9d-5e59973be781")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithFile.xml b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithFile.xml new file mode 100644 index 0000000..f4cc173 --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithFile.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithOnlyFileName.xml b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithOnlyFileName.xml new file mode 100644 index 0000000..965aea2 --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithOnlyFileName.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithoutFile.xml b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithoutFile.xml new file mode 100644 index 0000000..fd20b4d --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/Testfiles/IssueWithoutFile.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.Tests/XmlFileLoggerFormatTests.cs b/src/Cake.Issues.MsBuild.Tests/XmlFileLoggerFormatTests.cs new file mode 100644 index 0000000..d07e4dd --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/XmlFileLoggerFormatTests.cs @@ -0,0 +1,115 @@ +namespace Cake.Issues.MsBuild.Tests +{ + using System.Linq; + using Core.IO; + using IssueProvider; + using Shouldly; + using Testing; + using Xunit; + + public class XmlFileLoggerFormatTests + { + public sealed class TheXmlFileLoggerFormatCtor + { + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given / When + var result = Record.Exception(() => new XmlFileLoggerFormat(null)); + + // Then + result.IsArgumentNullException("log"); + } + } + + public sealed class TheReadIssuesMethod + { + [Fact] + public void Should_Read_Issue_With_File_Correct() + { + // Given + var fixture = new MsBuildIssuesProviderFixture("IssueWithFile.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + var issue = issues.Single(); + CheckIssue( + issue, + @"src\Cake.Issues.MsBuild.Tests\MsBuildIssuesProviderTests.cs", + 1311, + "CA2201", + 0, + @"Microsoft.Usage : 'ConfigurationManager.GetSortedConfigFiles(String)' creates an exception of type 'ApplicationException', an exception type that is not sufficiently specific and should never be raised by user code. If this exception instance might be thrown, use a different exception type."); + } + + [Fact] + public void Should_Read_Issue_With_File_Without_Path_Correct() + { + // Given + var fixture = new MsBuildIssuesProviderFixture("IssueWithOnlyFileName.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + var issue = issues.Single(); + CheckIssue( + issue, + @"src\Cake.Issues.MsBuild.Tests\MsBuildIssuesProviderTests.cs", + 13, + "CS0219", + 0, + "The variable 'foo' is assigned but its value is never used"); + } + + [Fact] + public void Should_Read_Issue_Without_File_Correct() + { + // Given + var fixture = new MsBuildIssuesProviderFixture("IssueWithoutFile.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + var issue = issues.Single(); + CheckIssue( + issue, + null, + null, + "CA1711", + 0, + "Microsoft.Naming : Rename type name 'UniqueQueue(Of T)' so that it does not end in 'Queue'."); + } + + private static void CheckIssue( + IIssue issue, + string affectedFileRelativePath, + int? line, + string rule, + int priority, + string message) + { + if (issue.AffectedFileRelativePath == null) + { + affectedFileRelativePath.ShouldBeNull(); + } + else + { + issue.AffectedFileRelativePath.ToString().ShouldBe(new FilePath(affectedFileRelativePath).ToString()); + issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "Issue path is not relative"); + } + + issue.Line.ShouldBe(line); + issue.Rule.ShouldBe(rule); + issue.Priority.ShouldBe(priority); + issue.Message.ShouldBe(message); + } + } + } +} diff --git a/src/Cake.Issues.MsBuild.Tests/packages.config b/src/Cake.Issues.MsBuild.Tests/packages.config new file mode 100644 index 0000000..d8c638c --- /dev/null +++ b/src/Cake.Issues.MsBuild.Tests/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.ruleset b/src/Cake.Issues.MsBuild.ruleset new file mode 100644 index 0000000..1d18efa --- /dev/null +++ b/src/Cake.Issues.MsBuild.ruleset @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild.sln b/src/Cake.Issues.MsBuild.sln new file mode 100644 index 0000000..d8f23a9 --- /dev/null +++ b/src/Cake.Issues.MsBuild.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.8 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.MsBuild", "Cake.Issues.MsBuild\Cake.Issues.MsBuild.csproj", "{1A6C74CE-4775-458A-B013-BDE1986C5B88}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.MsBuild.Tests", "Cake.Issues.MsBuild.Tests\Cake.Issues.MsBuild.Tests.csproj", "{C69329F6-A4D1-4E33-BD9D-5E59973BE781}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2EBEB9BA-3A3B-4EDC-95C0-44DD001AFFDC}" + ProjectSection(SolutionItems) = preProject + ..\setup.cake = ..\setup.cake + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspec", "nuspec", "{3E340FDE-17DA-429E-BC41-AA5F5F5271DB}" + ProjectSection(SolutionItems) = preProject + ..\nuspec\nuget\Cake.Issues.MsBuild.nuspec = ..\nuspec\nuget\Cake.Issues.MsBuild.nuspec + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A6C74CE-4775-458A-B013-BDE1986C5B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A6C74CE-4775-458A-B013-BDE1986C5B88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A6C74CE-4775-458A-B013-BDE1986C5B88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A6C74CE-4775-458A-B013-BDE1986C5B88}.Release|Any CPU.Build.0 = Release|Any CPU + {C69329F6-A4D1-4E33-BD9D-5E59973BE781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C69329F6-A4D1-4E33-BD9D-5E59973BE781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C69329F6-A4D1-4E33-BD9D-5E59973BE781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C69329F6-A4D1-4E33-BD9D-5E59973BE781}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3E340FDE-17DA-429E-BC41-AA5F5F5271DB} = {2EBEB9BA-3A3B-4EDC-95C0-44DD001AFFDC} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {896FC38E-24E4-4A35-8CF6-01E8EADDB346} + EndGlobalSection +EndGlobal diff --git a/src/Cake.Issues.MsBuild.sln.DotSettings b/src/Cake.Issues.MsBuild.sln.DotSettings new file mode 100644 index 0000000..7cc51a4 --- /dev/null +++ b/src/Cake.Issues.MsBuild.sln.DotSettings @@ -0,0 +1,3 @@ + + DO_NOT_SHOW + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild/Cake.Issues.MsBuild.csproj b/src/Cake.Issues.MsBuild/Cake.Issues.MsBuild.csproj new file mode 100644 index 0000000..e64a533 --- /dev/null +++ b/src/Cake.Issues.MsBuild/Cake.Issues.MsBuild.csproj @@ -0,0 +1,77 @@ + + + + + Debug + AnyCPU + {1A6C74CE-4775-458A-B013-BDE1986C5B88} + Library + Properties + Cake.Issues.MsBuild + Cake.Issues.MsBuild + v4.5.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Cake.Issues.MsBuild.xml + ..\Cake.Issues.MsBuild.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Cake.Issues.MsBuild.xml + ..\Cake.Issues.MsBuild.ruleset + + + + ..\packages\Cake.Core.0.16.2\lib\net45\Cake.Core.dll + + + ..\packages\Cake.Issues.0.1.0-beta0001\lib\net45\Cake.Issues.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild/ILogFileFormat.cs b/src/Cake.Issues.MsBuild/ILogFileFormat.cs new file mode 100644 index 0000000..9cf4ab3 --- /dev/null +++ b/src/Cake.Issues.MsBuild/ILogFileFormat.cs @@ -0,0 +1,21 @@ +namespace Cake.Issues.MsBuild +{ + using System.Collections.Generic; + using IssueProvider; + + /// + /// Definition of a MsBuild log file format. + /// + public interface ILogFileFormat + { + /// + /// Gets all code analysis issues. + /// + /// Repository settings to use. + /// Settings for code analysis provider to use. + /// List of code analysis issues + IEnumerable ReadIssues( + RepositorySettings repositorySettings, + MsBuildIssuesSettings msBuildIssuesSettings); + } +} diff --git a/src/Cake.Issues.MsBuild/LogFileFormat.cs b/src/Cake.Issues.MsBuild/LogFileFormat.cs new file mode 100644 index 0000000..d431edf --- /dev/null +++ b/src/Cake.Issues.MsBuild/LogFileFormat.cs @@ -0,0 +1,33 @@ +namespace Cake.Issues.MsBuild +{ + using System.Collections.Generic; + using Core.Diagnostics; + using IssueProvider; + + /// + /// Base class for all MsBuild log file format implementations. + /// + public abstract class LogFileFormat : ILogFileFormat + { + /// + /// Initializes a new instance of the class. + /// + /// The Cake log instance. + protected LogFileFormat(ICakeLog log) + { + log.NotNull(nameof(log)); + + this.Log = log; + } + + /// + /// Gets the Cake log instance. + /// + protected ICakeLog Log { get; private set; } + + /// + public abstract IEnumerable ReadIssues( + RepositorySettings repositorySettings, + MsBuildIssuesSettings msBuildIssuesSettings); + } +} diff --git a/src/Cake.Issues.MsBuild/MsBuildIssuesAliases.cs b/src/Cake.Issues.MsBuild/MsBuildIssuesAliases.cs new file mode 100644 index 0000000..a3870ef --- /dev/null +++ b/src/Cake.Issues.MsBuild/MsBuildIssuesAliases.cs @@ -0,0 +1,234 @@ +namespace Cake.Issues.MsBuild +{ + using System; + using Core; + using Core.Annotations; + using Core.IO; + using IssueProvider; + + /// + /// Contains functionality related to importing code analysis issues from MSBuild logs to write them to + /// pull requests. + /// + [CakeAliasCategory(IssuesAliasConstants.MainCakeAliasCategory)] + public static class MsBuildIssuesAliases + { + /// + /// Registers a new URL resolver with default priority of 0. + /// + /// The context. + /// Resolver which returns an linking to a site + /// containing help for a specific . + /// + /// Adds a provider with default priority of 0 returning a link for all rules of the category CA to + /// search msdn.microsoft.com with Google for the rule: + /// + /// + /// x.Category.ToUpperInvariant() == "CA" ? + /// new Uri("https://www.google.com/search?q=%22" + x.Rule + ":%22+site:msdn.microsoft.com") : + /// null) + /// ]]> + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static void MsBuildAddRuleUrlResolver( + this ICakeContext context, + Func resolver) + { + context.NotNull(nameof(context)); + resolver.NotNull(nameof(resolver)); + + MsBuildRuleUrlResolver.Instance.AddUrlResolver(resolver); + } + + /// + /// Registers a new URL resolver with a specific priority. + /// + /// The context. + /// Resolver which returns an linking to a site + /// containing help for a specific . + /// Priority of the resolver. Resolver with a higher priority are considered + /// first during resolving of the URL. + /// + /// Adds a provider of priority 5 returning a link for all rules of the category CA to + /// search msdn.microsoft.com with Google for the rule: + /// + /// + /// x.Category.ToUpperInvariant() == "CA" ? + /// new Uri("https://www.google.com/search?q=%22" + x.Rule + ":%22+site:msdn.microsoft.com") : + /// null, + /// 5) + /// ]]> + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static void MsBuildAddRuleUrlResolver( + this ICakeContext context, + Func resolver, + int priority) + { + context.NotNull(nameof(context)); + resolver.NotNull(nameof(resolver)); + + MsBuildRuleUrlResolver.Instance.AddUrlResolver(resolver, priority); + } + + /// + /// + /// Gets an instance for the MsBuild log format as written by the XmlFileLogger class + /// from MSBuild Extension Pack. + /// + /// + /// You can add the logger to the MSBuildSettings like this: + /// + /// var settings = new MsBuildSettings() + /// .WithLogger( + /// Context.Tools.Resolve("MSBuild.ExtensionPack.Loggers.dll").FullPath, + /// "XmlFileLogger", + /// string.Format( + /// "logfile=\"{0}\";verbosity=Detailed;encoding=UTF-8", + /// @"C:\build\msbuild.log") + /// ) + /// + /// + /// + /// In order to use the above logger, include the following in your build.cake file to download and + /// install from NuGet.org: + /// + /// #tool "nuget:?package=MSBuild.Extension.Pack" + /// + /// + /// + /// The context. + /// Instance for the MsBuild log format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static ILogFileFormat MsBuildXmlFileLoggerFormat( + this ICakeContext context) + { + context.NotNull(nameof(context)); + + return new XmlFileLoggerFormat(context.Log); + } + + /// + /// Gets an instance of a provider for code analysis issues reported as MsBuild warnings using a log file from disk. + /// + /// The context. + /// Path to the the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + /// Instance of a provider for code analysis issues reported as MsBuild warnings. + /// + /// Report code analysis issues reported as MsBuild warnings to a TFS pull request: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider MsBuildIssuesFromFilePath( + this ICakeContext context, + FilePath logFilePath, + ILogFileFormat format) + { + context.NotNull(nameof(context)); + logFilePath.NotNull(nameof(logFilePath)); + format.NotNull(nameof(format)); + + return context.MsBuildIssues(MsBuildIssuesSettings.FromFilePath(logFilePath, format)); + } + + /// + /// Gets an instance of a provider for code analysis issues reported as MsBuild warnings using log content. + /// + /// The context. + /// Content of the the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + /// Instance of a provider for code analysis issues reported as MsBuild warnings. + /// + /// Report code analysis issues reported as MsBuild warnings to a TFS pull request: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider MsBuildIssuesFromContent( + this ICakeContext context, + string logFileContent, + ILogFileFormat format) + { + context.NotNull(nameof(context)); + logFileContent.NotNullOrWhiteSpace(nameof(logFileContent)); + format.NotNull(nameof(format)); + + return context.MsBuildIssues(MsBuildIssuesSettings.FromContent(logFileContent, format)); + } + + /// + /// Gets an instance of a provider for code analysis issues reported as MsBuild warnings using specified settings. + /// + /// The context. + /// Settings for reading the MSBuild log. + /// Instance of a provider for code analysis issues reported as MsBuild warnings. + /// + /// Report code analysis issues reported as MsBuild warnings to a TFS pull request: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider MsBuildIssues( + this ICakeContext context, + MsBuildIssuesSettings settings) + { + context.NotNull(nameof(context)); + settings.NotNull(nameof(settings)); + + return new MsBuildIssuesProvider(context.Log, settings); + } + } +} diff --git a/src/Cake.Issues.MsBuild/MsBuildIssuesProvider.cs b/src/Cake.Issues.MsBuild/MsBuildIssuesProvider.cs new file mode 100644 index 0000000..f50ee4c --- /dev/null +++ b/src/Cake.Issues.MsBuild/MsBuildIssuesProvider.cs @@ -0,0 +1,33 @@ +namespace Cake.Issues.MsBuild +{ + using System.Collections.Generic; + using Core.Diagnostics; + using IssueProvider; + + /// + /// Provider for code analysis issues reported as MsBuild warnings. + /// + internal class MsBuildIssuesProvider : IssueProvider + { + private readonly MsBuildIssuesSettings msBuildIssuesSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The Cake log context. + /// Settings for reading the log file. + public MsBuildIssuesProvider(ICakeLog log, MsBuildIssuesSettings settings) + : base(log) + { + settings.NotNull(nameof(settings)); + + this.msBuildIssuesSettings = settings; + } + + /// + protected override IEnumerable InternalReadIssues(IssueCommentFormat format) + { + return this.msBuildIssuesSettings.Format.ReadIssues(this.Settings, this.msBuildIssuesSettings); + } + } +} diff --git a/src/Cake.Issues.MsBuild/MsBuildIssuesSettings.cs b/src/Cake.Issues.MsBuild/MsBuildIssuesSettings.cs new file mode 100644 index 0000000..892eb91 --- /dev/null +++ b/src/Cake.Issues.MsBuild/MsBuildIssuesSettings.cs @@ -0,0 +1,83 @@ +namespace Cake.Issues.MsBuild +{ + using System.IO; + using Core.IO; + + /// + /// Settings for . + /// + public class MsBuildIssuesSettings + { + /// + /// Initializes a new instance of the class. + /// + /// Path to the the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + protected MsBuildIssuesSettings(FilePath logFilePath, ILogFileFormat format) + { + logFilePath.NotNull(nameof(logFilePath)); + format.NotNull(nameof(format)); + + this.Format = format; + + using (var stream = new FileStream(logFilePath.FullPath, FileMode.Open, FileAccess.Read)) + { + using (var sr = new StreamReader(stream)) + { + this.LogFileContent = sr.ReadToEnd(); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Content of the the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + protected MsBuildIssuesSettings(string logFileContent, ILogFileFormat format) + { + logFileContent.NotNullOrWhiteSpace(nameof(logFileContent)); + format.NotNull(nameof(format)); + + this.LogFileContent = logFileContent; + this.Format = format; + } + + /// + /// Gets the format of the MsBuild log file. + /// + public ILogFileFormat Format { get; private set; } + + /// + /// Gets the content of the log file. + /// + public string LogFileContent { get; private set; } + + /// + /// Returns a new instance of the class from a log file on disk. + /// + /// Path to the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + /// Instance of the class. + public static MsBuildIssuesSettings FromFilePath(FilePath logFilePath, ILogFileFormat format) + { + return new MsBuildIssuesSettings(logFilePath, format); + } + + /// + /// Returns a new instance of the class from the content + /// of a MsBuild log file. + /// + /// Content of the MsBuild log file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided MsBuild log file. + /// Instance of the class. + public static MsBuildIssuesSettings FromContent(string logFileContent, ILogFileFormat format) + { + return new MsBuildIssuesSettings(logFileContent, format); + } + } +} diff --git a/src/Cake.Issues.MsBuild/MsBuildRuleDescription.cs b/src/Cake.Issues.MsBuild/MsBuildRuleDescription.cs new file mode 100644 index 0000000..e25c270 --- /dev/null +++ b/src/Cake.Issues.MsBuild/MsBuildRuleDescription.cs @@ -0,0 +1,20 @@ +namespace Cake.Issues.MsBuild +{ + using IssueProvider; + + /// + /// Class describing rules appearing in MsBuild logs. + /// + public class MsBuildRuleDescription : BaseRuleDescription + { + /// + /// Gets or sets the category of the rule. + /// + public string Category { get; set; } + + /// + /// Gets or sets the identifier of the rule. + /// + public int RuleId { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.MsBuild/MsBuildRuleUrlResolver.cs b/src/Cake.Issues.MsBuild/MsBuildRuleUrlResolver.cs new file mode 100644 index 0000000..b59fd66 --- /dev/null +++ b/src/Cake.Issues.MsBuild/MsBuildRuleUrlResolver.cs @@ -0,0 +1,74 @@ +namespace Cake.Issues.MsBuild +{ + using System; + using System.Text; + using IssueProvider; + + /// + /// Class for retrieving an URL linking to a site describing a rule. + /// + internal class MsBuildRuleUrlResolver : BaseRuleUrlResolver + { + private static readonly Lazy InstanceValue = + new Lazy(() => new MsBuildRuleUrlResolver()); + + /// + /// Initializes a new instance of the class. + /// + private MsBuildRuleUrlResolver() + { + // Add resolver for common known issue categories. + this.AddUrlResolver(x => + x.Category.ToUpperInvariant() == "CA" ? + new Uri("https://www.google.im/search?q=%22" + x.Rule + ":%22+site:msdn.microsoft.com") : + null); + this.AddUrlResolver(x => + x.Category.ToUpperInvariant() == "SA" ? + new Uri("https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/" + x.Rule + ".md") : + null); + } + + /// + /// Gets the instance of the rule resolver. + /// + public static MsBuildRuleUrlResolver Instance => InstanceValue.Value; + + /// + protected override bool TryGetRuleDescription(string rule, MsBuildRuleDescription ruleDescription) + { + // Parse the rule. Expect it in the form starting with a identifier containing characters + // followed by the rule id as a number. + var digitIndex = -1; + var categoryBuilder = new StringBuilder(); + for (var index = 0; index < rule.Length; index++) + { + var currentChar = rule[index]; + if (char.IsDigit(currentChar)) + { + digitIndex = index; + break; + } + + categoryBuilder.Append(currentChar); + } + + // If rule doesn't contain numbers return false. + if (digitIndex < 0) + { + return false; + } + + // Try to parse the part after the first number to an integer. + int ruleId; + if (!int.TryParse(rule.Substring(digitIndex), out ruleId)) + { + return false; + } + + ruleDescription.RuleId = ruleId; + ruleDescription.Category = categoryBuilder.ToString(); + + return true; + } + } +} diff --git a/src/Cake.Issues.MsBuild/Properties/AssemblyInfo.cs b/src/Cake.Issues.MsBuild/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cf1a87a --- /dev/null +++ b/src/Cake.Issues.MsBuild/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Cake.Issues.MsBuild")] +[assembly: AssemblyDescription("MsBuild support for the Cake.Issues Addin for Cake Build Automation System")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("BBT Software AG")] +[assembly: AssemblyProduct("Cake.Issues")] +[assembly: AssemblyCopyright("Copyright © 2017 BBT Software AG, Root/Zermatt, Switzerland")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1a6c74ce-4775-458a-b013-bde1986c5b88")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("Cake.Issues.MsBuild.Tests")] diff --git a/src/Cake.Issues.MsBuild/XmlFileLoggerFormat.cs b/src/Cake.Issues.MsBuild/XmlFileLoggerFormat.cs new file mode 100644 index 0000000..09238d7 --- /dev/null +++ b/src/Cake.Issues.MsBuild/XmlFileLoggerFormat.cs @@ -0,0 +1,178 @@ +namespace Cake.Issues.MsBuild +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using Core.Diagnostics; + using IssueProvider; + + /// + /// MsBuild log format as written by the XmlFileLogger class from MSBuild Extension Pack. + /// + internal class XmlFileLoggerFormat : LogFileFormat + { + /// + /// Initializes a new instance of the class. + /// + /// The Cake log instance. + public XmlFileLoggerFormat(ICakeLog log) + : base(log) + { + } + + /// + public override IEnumerable ReadIssues( + RepositorySettings repositorySettings, + MsBuildIssuesSettings msBuildIssuesSettings) + { + repositorySettings.NotNull(nameof(repositorySettings)); + msBuildIssuesSettings.NotNull(nameof(msBuildIssuesSettings)); + + var result = new List(); + + var logDocument = XDocument.Parse(msBuildIssuesSettings.LogFileContent); + + // Loop through all warning tags. + foreach (var warning in logDocument.Descendants("warning")) + { + // Read affected file from the warning. + if (!this.TryGetFile(warning, repositorySettings, out string fileName)) + { + continue; + } + + // Read affected line from the warning. + if (!TryGetLine(warning, out var line)) + { + continue; + } + + // Read rule code from the warning. + if (!TryGetRule(warning, out string rule)) + { + continue; + } + + result.Add(new Issue( + fileName, + line, + warning.Value, + 0, + rule, + MsBuildRuleUrlResolver.Instance.ResolveRuleUrl(rule))); + } + + return result; + } + + /// + /// Reads the affected line from a warning logged in a MsBuild log. + /// + /// Warning element from MsBuild log. + /// Returns line. + /// True if the line could be parsed. + private static bool TryGetLine(XElement warning, out int? line) + { + line = null; + + var lineAttr = warning.Attribute("line"); + + var lineValue = lineAttr?.Value; + if (string.IsNullOrWhiteSpace(lineValue)) + { + return false; + } + + line = int.Parse(lineValue, CultureInfo.InvariantCulture); + + // Convert negative lines numbers to null + if (line < 0) + { + line = null; + } + + return true; + } + + /// + /// Reads the rule code from a warning logged in a MsBuild log. + /// + /// Warning element from MsBuild log. + /// Returns the code of the rule. + /// True if the rule code could be parsed. + private static bool TryGetRule(XElement warning, out string rule) + { + rule = string.Empty; + + var codeAttr = warning.Attribute("code"); + if (codeAttr == null) + { + return false; + } + + rule = codeAttr.Value; + return !string.IsNullOrWhiteSpace(rule); + } + + /// + /// Reads the affected file path from a warning logged in a MsBuild log. + /// + /// Warning element from MsBuild log. + /// Repository settings to use. + /// Returns the full path to the affected file. + /// True if the file path could be parsed. + private bool TryGetFile( + XElement warning, + RepositorySettings repositorySettings, + out string fileName) + { + fileName = string.Empty; + + var fileAttr = warning.Attribute("file"); + if (fileAttr == null) + { + return true; + } + + fileName = fileAttr.Value; + if (string.IsNullOrWhiteSpace(fileName)) + { + return true; + } + + // If not absolute path, combine with file path from compile task. + if (!fileName.IsFullPath()) + { + var parentFileAttr = warning.Parent?.Attribute("file"); + if (parentFileAttr != null) + { + var compileTaskDirectory = Path.GetDirectoryName(parentFileAttr.Value); + fileName = Path.Combine(compileTaskDirectory, fileName); + } + } + + // Ignore files from outside the repository. + if (!fileName.IsSubpathOf(repositorySettings.RepositoryRoot.FullPath)) + { + this.Log.Warning( + "Ignored issue for file '{0}' since it is outside the repository folder at {1}.", + fileName, + repositorySettings.RepositoryRoot); + + return false; + } + + // Make path relative to repository root. + fileName = fileName.Substring(repositorySettings.RepositoryRoot.FullPath.Length); + + // Remove leading directory separator. + if (fileName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + fileName = fileName.Substring(1); + } + + return true; + } + } +} diff --git a/src/Cake.Issues.MsBuild/packages.config b/src/Cake.Issues.MsBuild/packages.config new file mode 100644 index 0000000..d7a63d1 --- /dev/null +++ b/src/Cake.Issues.MsBuild/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config new file mode 100644 index 0000000..3b95cb1 --- /dev/null +++ b/tools/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file