Une brève note sur PVS Studio dans CI (et ce qui manque)

Je pense que cela n'a pas de sens une fois de plus d'annoncer un merveilleux outil d'analyse statique - PVS Studio. Il y a déjà beaucoup d'articles sur le Habr qui lui sont dédiés, mais je veux aborder un autre aspect - l'utilisation de cet outil dans un système d'intégration continue.


Donc, il y a une organisation, il y a un CI qui fonctionne simplement: Jenkins reçoit un hook après une poussée dans Git, après quoi il lance un pipeline. En raison des outils utilisés, l'assemblage est effectué pour les projets créés en C # (msbuild) et C ++ (msbuild, CMake). À l'une des étapes finales, la génération du rapport est lancée, y compris à l'aide de PVS Studio (entre autres, cppcheck, mais ce n'est pas important pour la suite de la narration).


PVS Studio dispose d'un outil d'analyse de console qui est lancé à partir de la ligne de commande: PVS-Studio_Cmd.exe --target "${projectFile}" --output report.plog --progress


À l'entrée - le nom du projet (.sln), à la sortie - le rapport.


Rapport - un fichier avec l'extension .plog, est un fichier XML standard. Le schéma de document est intégré, donc il ne peut y avoir aucune surprise dans le format de sortie. Au moins jusqu'à ce que les développeurs modifient le schéma, mais nous n'envisagerons pas cette option.


Le rapport se compose d'un ensemble d'enregistrements, chacun pointant vers un fichier et une ligne, une classe d'erreur, un niveau d'erreur, une description et d'autres choses pas très intéressantes.


Mais lire XML avec vos yeux est un plaisir pour vous, vous avez donc besoin d'un moyen de visualiser et de naviguer.


Le plus simple et le plus fonctionnel est le plugin PVS Studio pour Visual Studio, avec la possibilité de naviguer dans le code. Mais forcer un responsable technique ou une autre personne intéressée à charger un projet dans VS à chaque fois est une mauvaise idée, et l'historique du développement du projet n'est pas visible.


Alors, allons dans l'autre sens et voyons ce qui peut être fait. Et il existe un moyen assez standard qui vous permet de convertir XML en autre chose: XSLT . Maintenant, probablement, l'un des lecteurs a déformé, mais, néanmoins, je propose de continuer la lecture.


XSLT est un langage pour convertir des documents XML en autre chose. Il compare simplement la règle de conversion au modèle d'entrée, mais nous avons fait la conversion en un rapport HTML pour nous-mêmes.


J'espère que personne ne me jugera pour avoir fait des tableaux, car les données sont de nature tabulaire. Chaque enregistrement du rapport correspondra à une ligne de tableau avec les colonnes suivantes:


  1. Numéro de ligne dans le tableau.
  2. Nom de fichier.
  3. Numéro de ligne.
  4. Le code d'erreur.
  5. Le message d'erreur.

Le numéro de ligne est juste pratique pour référence verbale dans la discussion.


Le nom de fichier et le nom de ligne vous permettent de créer un lien vers le référentiel. Mais plus à ce sujet plus tard.


Le code d'erreur est encadré par un lien vers le site Web des développeurs de PVS-Studio: http://viva64.com/en/{ErrorCode } (ou / ru /, comme vous le souhaitez).


Le message d'erreur n'est pas un commentaire.


Il y a quelques points à traiter.


Tout d'abord, je voudrais que les messages soient triés par niveau d'importance, ainsi que d'avoir un nombre total de messages de chaque type. La première tâche est résolue à l'aide de l'expression xsl:sort , la seconde est count([]) .


Deuxièmement: le nom du fichier est indiqué en entier et un nom relatif est nécessaire pour créer un lien vers le système de contrôle de version. Il suffit de couper le préfixe correspondant au nom du répertoire avec le projet dans lequel le référentiel a été cloné (nous avons Git, mais il est facilement adaptable). Mais pour que ce chemin apparaisse, nous devons paramétrer la transformation XSL à l'aide de la construction xsl:param . Le reste est relativement simple: supprimez de la ligne avec le nom de fichier un préfixe commun avec le nom du répertoire où le référentiel est cloné. Je dois dire qu'en XSLT, ce problème est résolu de manière assez sophistiquée.


Troisièmement: la validation fait référence à une révision spécifique dans le référentiel, et cela doit également être gardé à l'esprit. Il est résolu en utilisant le paramètre avec l'identifiant de validation. De même pour les branches.
Quatrièmement: si vous utilisez des bibliothèques tierces avec des codes sources, ne mélangez pas les avertissements avec les avertissements de notre projet. Le problème est résolu comme suit: nous plaçons tous les projets externes dans un certain répertoire, dont le nom n'est pas contenu dans notre projet. Maintenant, si le nom de fichier contient ce sous-répertoire (en fait juste une sous-chaîne), l'entrée dans le plog n'entre pas dans le rapport, mais elle est considérée comme «cachée» dans l'en-tête du rapport. Pour une plus grande flexibilité, vous pouvez paramétrer la transformation et attribuer un nom par défaut à ce répertoire: <xsl:param name="external" select="'External'" />


Eh bien et encore une petite tâche: collecter le lien vers un référentiel. Nous utilisons de la redmine + de la gitolite. Encore une fois, adaptable.


Étant donné que de nombreux paramètres sont constants pour la conversion, vous pouvez préparer un préfixe d'URL constant:


 <xsl:variable name="repo"> <xsl:text>http://redmine.your-site.com/projects/</xsl:text> <xsl:value-of select="$project" /> <xsl:text>/revisions/</xsl:text> <xsl:value-of select="$revision" /> <xsl:text>/entry/</xsl:text> </xsl:variable> 

Quelques beautés avec stylisation, et vous pouvez l'utiliser. Nous incorporons CSS dans la page, juste pour avoir un rapport de fichier unique. Nous n'avons pas non plus besoin d'images.


Code de transformation complet sous le spoiler


XSLT
 <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns="http://www.w3.org/1999/xhtml" exclude-result-prefixes="msxsl" > <xsl:output method="html" indent="yes"/> <xsl:param name="project" /> <xsl:param name="base-path" /> <xsl:param name="branch" select="'master'" /> <xsl:param name="revision" select="'[required]'" /> <xsl:param name="external" select="'External'" /> <xsl:variable name="repo"> <xsl:text>http://redmine.your-company.com/projects/</xsl:text> <!-- # !!!attention!!! # --> <xsl:value-of select="$project" /> <xsl:text>/revisions/</xsl:text> <xsl:value-of select="$revision" /> <xsl:text>/entry/</xsl:text> </xsl:variable> <xsl:template name="min-len"> <xsl:param name="a" /> <xsl:param name="b" /> <xsl:variable name="la" select="string-length($a)" /> <xsl:variable name="lb" select="string-length($b)" /> <xsl:choose> <xsl:when test="$la &lt; $lb"> <xsl:value-of select="$la"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$lb" /> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="strdiff-impl"> <xsl:param name="mask" /> <xsl:param name="value" /> <xsl:param name="n" /> <xsl:param name="lim" /> <xsl:choose> <xsl:when test="$n = $lim"> <xsl:value-of select="substring($value, $lim + 1)" /> </xsl:when> <xsl:when test="substring($mask, 0, $n) = substring($value,0, $n)"> <xsl:call-template name="strdiff-impl"> <xsl:with-param name="lim" select="$lim" /> <xsl:with-param name="mask" select="$mask" /> <xsl:with-param name="value" select="$value" /> <xsl:with-param name="n" select="$n + 1" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="substring($value, $n - 1)"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="strdiff"> <xsl:param name="mask" /> <xsl:param name="value" /> <xsl:choose> <xsl:when test="not($value)" /> <xsl:when test="not($mask)"> <xsl:value-of select="$value" /> </xsl:when> <xsl:otherwise> <xsl:call-template name="strdiff-impl"> <xsl:with-param name="mask" select="$mask" /> <xsl:with-param name="value" select="$value" /> <xsl:with-param name="lim"> <xsl:call-template name="min-len"> <xsl:with-param name="a" select="$mask" /> <xsl:with-param name="b" select="$value" /> </xsl:call-template> </xsl:with-param> <xsl:with-param name="n" select="1" /> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="/*"> <xsl:variable select="Solution_Path/SolutionPath" name="solution" /> <xsl:variable select="PVS-Studio_Analysis_Log [not(contains(File, $external))] [ErrorCode!='Renew'] " name="input" /> <html lang="en"> <head> <style type="text/css"> <![CDATA[ #report * {font-family: consolas, monospace, sans-serif; } #report {border-collapse: collapse; border: solid silver 1px;} #report th, #report td {padding: 6px 8px; border: solid silver 1px;} .sev-1 {background-color: #9A2617;} .sev-2 {background-color: #C2571A;} .sev-3 {background-color: #BCA136;} .sev-hidden {background-color: #999; } #report tbody * {color: white;} .fa * { color: #AAA; } a {color: #006;} .stat {padding: 20px;} .stat * {color: white; } .stat span {padding: 8px 16px; } html {background-color: #EEE;} .success {color: #3A3; } ]]> </style> </head> <body> <h1>PVS-Studio report</h1> <h2> <xsl:call-template name="strdiff"> <xsl:with-param name="value"> <xsl:value-of select="$solution" /> </xsl:with-param> <xsl:with-param name="mask"> <xsl:value-of select="$base-path" /> </xsl:with-param> </xsl:call-template> </h2> <div class="stat"> <span class="sev-1"> High: <b> <xsl:value-of select="count($input[Level=1])" /> </b> </span> <span class="sev-2"> Meduim: <b> <xsl:value-of select="count($input[Level=2])" /> </b> </span> <span class="sev-3"> Low: <b> <xsl:value-of select="count($input[Level=3])" /> </b> </span> <span class="sev-hidden" title="Externals etc"> Hidden: <b> <xsl:value-of select="count(PVS-Studio_Analysis_Log) - count($input)"/> </b> </span> </div> <xsl:choose> <xsl:when test="count($input) = 0"> <h2 class="success">No error messages.</h2> </xsl:when> <xsl:otherwise> <table id="report"> <thead> <tr> <th> # </th> <th> File </th> <th> Line </th> <th> Code </th> <th> Message </th> </tr> </thead> <tbody> <xsl:for-each select="$input"> <xsl:sort select="Level" data-type="number"/> <xsl:sort select="DefaultOrder" /> <tr> <xsl:attribute name="class"> <xsl:text>sev-</xsl:text> <xsl:value-of select="Level" /> <xsl:if test="FalseAlarm = 'true'"> <xsl:text xml:space="preserve"> fa</xsl:text> </xsl:if> </xsl:attribute> <th> <xsl:value-of select="position()" /> </th> <td> <xsl:variable name="file"> <xsl:call-template name="strdiff"> <xsl:with-param name="value" select="File" /> <xsl:with-param name="mask" select="$base-path" /> </xsl:call-template> </xsl:variable> <a href="{$repo}{translate($file, '\', '/')}#L{Line}"> <xsl:value-of select="$file" /> </a> </td> <td> <xsl:value-of select="Line"/> </td> <td> <a href="http://viva64.com/en/{ErrorCode}" target="_blank"> <xsl:value-of select="ErrorCode" /> </a> </td> <td> <xsl:value-of select="Message" /> </td> </tr> </xsl:for-each> </tbody> </table> </xsl:otherwise> </xsl:choose> </body> </html> </xsl:template> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> 

Nous commençons la transformation à l'aide d'un petit utilitaire de console écrit en C #, mais vous pouvez le faire différemment (si nécessaire, je le partagerai également, il n'y a rien de compliqué et de secret là-bas).
Et puis vous pouvez faire un tableau de bord à partir de cela, mais cela, comme on dit, est une histoire complètement différente.


Et maintenant un peu pleurer vers les développeurs. Il y a un bogue, pas une fonctionnalité, qui rend impossible de faire complètement ce qui est décrit ci-dessus, d'ailleurs, cela ne s'applique qu'aux projets C ++, en C #, il n'y a pas de problème. Lorsqu'un fichier plog est formé, dans la <File> nom est toujours converti en minuscules. Et lorsque redmine (et autres sites Web) est hébergé sur des systèmes de type UNIX avec des noms de fichiers sensibles à la casse, la casse se rompt lors de la création de liens vers des fichiers, ce qui rend les liens inopérants. Quelle tristesse.


J'ai reçu une réponse à la lettre de support technique que ce comportement est dû à une API externe, mais il n'est pas clair pourquoi il est si sélectif et s'applique uniquement à C ++, et ne s'applique pas à C #.
Par conséquent, pour l'instant, comme promis, j'appelle à la poursuite de Paull et j'espère une coopération fructueuse.


Merci de votre attention.


Mise à jour : selon les résultats de la correspondance avec les développeurs représentés par Paull et khandeliants , des fouilles profondes ont été effectuées, à la suite desquelles une version bêta a été publiée dans laquelle le problème décrit ci-dessus a été résolu. Mais pour que cela fonctionne, la prise en charge des chemins courts est requise, au moins sur le lecteur sur lequel l'analyse est effectuée. Pour ce faire, dans le Registre le long du chemin HKLM \ SYSTEM \ CurrentControlSet \ Control \ FileSystem, vous devez définir le paramètre NtfsDisable8dot3NameCreation (DWORD) sur une valeur qui permet d'enregistrer des noms de fichiers courts. Voir les détails sur MSDN .
L'interdiction par défaut des noms courts est nécessaire pour augmenter la vitesse de NTFS.
Vous pouvez soit définir la valeur 0 et ne pas déranger, ou 3 si les tâches CI sont effectuées dans le profil utilisateur sur la partition système ou ailleurs sur la partition système, ou en 2 et exécuter la commande fsutil 8dot3name set Z: 0 (votre disque au lieu de Z :), où l'espace de travail CI sera déployé (s'applique également aux disques RAM, soit dit en passant).


J'espère que cette information apparaît quelque part sur le site Web de viva64.


Maintenant, il semble que la gestalt soit fermée, merci encore pour votre attention.

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


All Articles