📜 ⬆️ ⬇️

Implement StyleCop in MSBuild

Increasingly, the task of automating different processes within CI. Having picked up with MSBuild, I am becoming more and more convinced that this is a fairly powerful tool. If desired, they can do a lot of things. However, neither in RuNet as a whole, nor specifically on Habré, I did not find articles on it and decided to fill this gap as much as I had and I had free time.
So today we will cook

Stylecop



The task: to implement a total forced verification of the code (C #) for compliance with the rules of registration.
')
Condition: total, forced. Those. all code that falls on the assembly must be checked without fail. In case of detection of violations - build error and forward, refactor.

Tools: StyleCop , MSBuild (TFS or TeamCity - no matter).

So. We want the code to be run through StyleCop. We are aware of the laziness and irresponsibility of the developers, so we do not trust client verification. Those. VSIX, which is installed from the StyleCop distribution kit, is available at the link above, and allows the studio to simply right-click on the project / solution and do the Run Stylecop, we are not satisfied.
Any StyleCop Checkin Policy is not our way either. Because they are tied to TFS, moreover as a repository. Thus, by running Git for TFS, we will already lose these policies. No, we do not consider (although, purely ideologically, this is the most correct way - the “dirty” code simply should not fall into the repository).

So, just do not let the code we can not 1 . It remains only to not allow him to gather. To do this, we have well studied mechanisms for implementation in the MSBuild pipeline. Fortunately, most of the work has already been done for us - there are all the necessary files in the StyleCop.MSBuild nuget package. These are the files of the StyleCop itself (the StyleCopTask class that implements the Task for MSBuild we already have is in the StyleCop.dll itself), and the default settings ( StyleCop.Settings ) and, most importantly, the StyleCop.MSBuild.targets. This last one is what we need most.

How are we going to be introduced?



For MSBuild, there is a rather convenient extension mechanism via ImportBefore / After . In short:
If you want to find out what you want to do. For this purpose, Microsoft.Common.Targets contains the following line at the top of the file.
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\$(MSBuildThisFile)\ImportBefore\*" Condition="'$(ImportByWildcardBeforeMicrosoftCommonTargets)' == 'true' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\$(MSBuildThisFile)\ImportBefore')"/> 


And also import files from ImportAfter automatically . Those. for each build, MSBuild imports files that are in $ (MSBuildExtensionsPath) \ $ (MSBuildToolsVersion) \ Microsoft.Common.Targets \ ImportBefore \ and ImportAfter. The difference between them is before or after downloading the file to be collected (sln / csproj / another proj), this is true for ... I'll tell you another time.

You can follow the path of the monkey job: download .msi from the stylecop site, install it on the machine where MSBuild is running, put the .targets file in the same place ...
But we have several machines with build agents (previously TFS, now also under TeamCity) and somehow I don’t want to copy / install with my hands. God forbid more updates and then spill ... no, thank you.

We will automate.


Since we have TFS / TeamCity, we can go to the forehead. We create a repository with the following file structure:
 /
 ├ [lib]
 │ ├mssp7en.dll
 │ ├mssp7en.lex
 │ ├Settings.StyleCop
 │ ├StyleCop.CSharp.dll
 │ ├StyleCop.CSharp.Rules.dll
 │ tyleStyleCop.dll
 ├ [targets]
 │ ├StyleCop.MSBuild.Targets
 â””build.proj


build.proj


This is just a project file in msbuild format, which will be executed on the agent and perform operations to put the files in the right places.
Content, say, such
 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="StyleCopUpdate" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <StyleCopTargetsFolder Condition="'$(StyleCopTargetsFolder)' == ''">$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets\ImportAfter</StyleCopTargetsFolder> <StyleCopFolder Condition="'$(StyleCopFolder)' == ''">$(MSBuildExtensionsPath)\StyleCop</StyleCopFolder> </PropertyGroup> <Target Name="StyleCopUpdate" DependsOnTargets="StyleCopClear"> <ItemGroup> <StyleCopTargets Include="$(MSBuildThisFileDirectory)targets\ImportAfter\StyleCop*.targets" /> </ItemGroup> <ItemGroup> <StyleCopLibs Include="$(MSBuildThisFileDirectory)lib\*.*" /> </ItemGroup> <MakeDir Directories="$(StyleCopTargetsFolder)" Condition="!Exists('$(StyleCopTargetsFolder)')" /> <Copy DestinationFolder="$(StyleCopTargetsFolder)" SourceFiles="@(StyleCopTargets)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" /> <MakeDir Directories="$(StyleCopFolder)" Condition="!Exists('$(StyleCopFolder)')" /> <Copy DestinationFolder="$(StyleCopFolder)" SourceFiles="@(StyleCopLibs)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" /> </Target> <Target Name="StyleCopClear"> <ItemGroup> <StyleCopToDelete Include="$(StyleCopTargetsFolder)\StyleCop*.targets" /> <StyleCopToDelete Include="$(StyleCopFolder)\*.*" /> </ItemGroup> <Delete Files="@(StyleCopToDelete)" TreatErrorsAsWarnings="true" Condition="@(StyleCopToDelete) != ''" /> </Target> </Project> 



Running msbuild.exe build.proj we get:
0. If there are old files - delete
1. copying files from targets to $ (MSBuildExtensionsPath) \ $ (MSBuildToolsVersion) \ Microsoft.Common.targets \ ImportAfter (depending on the version of MSBuild being launched, it may be something like C: \ Program Files (x86) \ MSBuild \ 14.0 \ Microsoft.Common.Targets \ ImportAfter
2. copy files from lib to $ (MSBuildExtensionsPath) \ StyleCop (for example C: \ Program Files (x86) \ MSBuild \ StyleCop )

We configure a build in TFS / TeamCity, which in the Continuous Integration mode reacts to commits to this repository and makes it so that it is forced to wake up on each agent. Thus, after making changes, all our agents will be automatically updated to the latest StyleCop / dll / configs. Milota.

Customize StyleCop



In StyleCop.MSBuild.targets it is important for us to fix two things.
1. Enable StyleCop. We find we change false to true:
  <PropertyGroup Condition="'$(StyleCopEnabled)' == ''"> <StyleCopEnabled>true</StyleCopEnabled> </PropertyGroup> <PropertyGroup Condition="'$(StyleCopTreatErrorsAsWarnings)' == ''"> <StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings> </PropertyGroup> 

(the second is that the test results fall out in Error, not Warn).
2. Fix the path to StyleCop.dll
  <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\StyleCop\StyleCop.dll" TaskName="StyleCopTask"/> 

and to config
  <PropertyGroup Condition="'$(StyleCopOverrideSettingsFile)' == '' and Exists('$(MSBuildExtensionsPath)\StyleCop\Settings.StyleCop')"> <StyleCopOverrideSettingsFile>$(MSBuildExtensionsPath)\StyleCop\Settings.StyleCop</StyleCopOverrideSettingsFile> </PropertyGroup> 

There is a nuance here.

File Settings.StyleCop


The default StyleCop rules are pretty controversial. There are a number of both doubtful and simply uncomfortable to us. To edit this file by hand is inconvenient. To do this, it's easier to put the same .msi from the site and it will install StyleCopSettingsEditor.exe at the same time - a convenient GUI for editing the list of rules.
The trick is that by default in StyleCop the inheritance of settings is enabled and default ones are taken from the one that you installed from msi (although the entire directory tree is scanned upward in search of a .stylecop file to inherit it automatically too).
So opening a file in your own folder, correcting the rules and saving, you actually save only the differences from the default . And if you put only this modified file in the repository, then only what is in it will be checked. The default rules will not be received by StyleCop under MSBuil, because it does not have that default file.
So as not to edit the source file and be able to replace it in case of anything, while saving my modifications, I did this:

The original Settings.StyleCop file has been renamed to default.StyleCop and placed side by side.

What is the result?


We have a forced verification of the code through StyleCop. Violations are immediately visible in the form of errors in the build log.
We have a mechanism that automatically updates the stylecop on build machines.

PS: You can run this “msbuild.exe build.proj” locally. Then all the same mistakes will be visible immediately in the studio. Those. msbuild imports these ImportBefore / After even when it is run out of the studio. Without any installation of plug-ins and other things, but right away in the Error List - know yourself click on the list and go where the studio puts the cursor. Without reading logs from the build server.

1 . I honestly tried to write an extension for TFS via ISubscriber, as described here , but I ended up drowning in meager documentation with no tools to publish ( C: \ Program Files \ Microsoft Team Server Server 12.0). \ Application Tier \ Web Services \ bin \ Plugins. Then it’s ridiculous, it’s ridiculous), a very obscure and confusing structure of classes / dependencies in this Microsoft.TeamFoundation.Git.Server library and even inconsistencies methods with their actions. And as I imagined that I would have to rebuild this later under TFS2015 - I made a Close solution and went to look for other ways of implementation.

Source: https://habr.com/ru/post/265081/


All Articles