Ich denke, es macht keinen Sinn, wieder ein wunderbares Tool für die statische Analyse zu bewerben - PVS Studio. Es gibt bereits viele Artikel über die Habr, die ihr gewidmet sind, aber ich möchte einen anderen Aspekt ansprechen - die Verwendung dieses Tools in einem kontinuierlichen Integrationssystem.
Es gibt also eine Organisation, ein CI, das einfach funktioniert: Jenkins erhält nach einem Push in Git einen Hook, wonach eine Pipeline gestartet wird. Aufgrund der verwendeten Tools wird die Assembly für Projekte ausgeführt, die in C # (msbuild) und C ++ (msbuild, CMake) erstellt wurden. In einer der Endphasen wird die Berichterstellung gestartet, einschließlich der Verwendung von PVS Studio (unter anderem cppcheck, dies ist jedoch für die weitere Erzählung nicht wichtig).
PVS Studio verfügt über ein Konsolenanalysetool, das über die Befehlszeile gestartet wird: PVS-Studio_Cmd.exe --target "${projectFile}" --output report.plog --progress
Bei der Eingabe - dem Namen des Projekts (.sln), bei der Ausgabe - dem Bericht.
Bericht - Eine Datei mit der Erweiterung .plog ist eine reguläre XML-Datei. Das Dokumentschema ist integriert, sodass das Ausgabeformat keine Überraschungen bietet. Zumindest bis die Entwickler das Schema ändern, aber wir werden diese Option nicht in Betracht ziehen.
Der Bericht besteht aus einer Reihe von Datensätzen, von denen jeder auf eine Datei und eine Zeile darin verweist, einer Fehlerklasse, einer Fehlerstufe, einer Beschreibung und anderen nicht sehr interessanten Dingen.
Das Lesen von XML mit Ihren Augen ist jedoch eine Freude für Sie. Sie benötigen daher eine Möglichkeit zum Anzeigen und Navigieren.
Am einfachsten und am einfachsten ist das PVS Studio-Plugin für Visual Studio, mit dem Sie durch den Code navigieren können. Es ist jedoch eine schlechte Idee, einen technischen Manager oder eine andere interessierte Person zu zwingen, jedes Mal ein Projekt in VS zu laden, und die Geschichte der Projektentwicklung ist nicht sichtbar.
Gehen wir also in die andere Richtung und sehen, was getan werden kann. Und es gibt eine ziemlich standardmäßige Methode, mit der Sie XML in etwas anderes konvertieren können: XSLT . Jetzt hat wahrscheinlich einer der Leser verzerrt, aber ich schlage trotzdem vor, weiterzulesen.
XSLT ist eine Sprache zum Konvertieren von XML-Dokumenten in etwas anderes. Es vergleicht einfach die Konvertierungsregel mit der Eingabevorlage, aber wir haben die Konvertierung in einen HTML-Bericht für uns selbst durchgeführt.
Ich hoffe, niemand wird mich für das Erstellen von Tabellen beurteilen, da die Daten tabellarischer Natur sind. Jeder Datensatz im Bericht entspricht einer Tabellenzeile mit den folgenden Spalten:
- Die Zeilennummer in der Tabelle.
- Dateiname.
- Zeilennummer.
- Der Fehlercode.
- Die Fehlermeldung.
Die Zeilennummer dient nur zur mündlichen Bezugnahme in der Diskussion.
Mit dem Dateinamen und dem Zeilennamen können Sie einen Link zum Repository erstellen. Aber dazu später mehr.
Der Fehlercode wird durch einen Link zur PVS-Studio-Entwickler-Website eingerahmt: http://viva64.com/en/{ErrorCode } (oder / ru /, wie Sie möchten).
Die Fehlermeldung ist kein Kommentar.
Es gibt einige Punkte zu behandeln.
Erstens möchte ich, dass Nachrichten nach Wichtigkeitsstufen sortiert werden und eine Gesamtzahl von Nachrichten jedes Typs haben. Die erste Aufgabe wird mit dem Ausdruck xsl:sort
gelöst, die zweite ist count([])
.
Zweitens: Der Dateiname wird vollständig angegeben, und ein relativer Name wird benötigt, um eine Verknüpfung zum Versionskontrollsystem herzustellen. Sie müssen nur das Präfix abschneiden, das dem Namen des Verzeichnisses mit dem Projekt entspricht, in das das Repository geklont wurde (wir haben Git, aber es passt sich leicht an). Damit dieser Pfad xsl:param
, müssen wir die XSL-Transformation mithilfe des Konstrukts xsl:param
. Der Rest ist relativ einfach: Entfernen Sie aus der Zeile mit dem Dateinamen ein gemeinsames Präfix mit dem Namen des Verzeichnisses, in das das Repository geklont wird. Ich muss sagen, dass dieses Problem in XSLT ziemlich raffiniert gelöst ist.
Drittens: Die Validierung bezieht sich auf eine bestimmte Revision im Repository, und dies muss ebenfalls berücksichtigt werden. Es wird mithilfe des Parameters mit der Festschreibungskennung gelöst. Ähnliches gilt für Filialen.
Viertens: Wenn Sie Bibliotheken von Drittanbietern mit Quellcodes verwenden, mischen Sie darin keine Warnungen mit Warnungen in unserem Projekt. Das Problem wird wie folgt gelöst: Wir legen alle externen Projekte in einem bestimmten Verzeichnis ab, dessen Name nicht in unserem Projekt enthalten ist. Wenn der Dateiname dieses Unterverzeichnis enthält (eigentlich nur eine Teilzeichenfolge), wird der Eintrag im Plog nicht in den Bericht aufgenommen, sondern im Berichtsheader als "versteckt" betrachtet. Für mehr Flexibilität können Sie die Transformation parametrisieren und diesem Verzeichnis einen Standardnamen zuweisen: <xsl:param name="external" select="'External'" />
Nun und noch eine kleine Aufgabe: den Link zu einem Repository zu sammeln. Wir verwenden Redmine + Gitolite. Wieder anpassungsfähig.
Da viele Parameter für die Konvertierung konstant sind, können Sie ein konstantes URL-Präfix vorbereiten:
<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>
Ein paar Schönheiten mit Stilisierung, und Sie können es verwenden. Wir binden CSS in die Seite ein, nur um einen einzigen Dateibericht zu haben. Wir brauchen auch keine Bilder.
Vollständiger Transformationscode unter dem 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> <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 < $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>
Wir starten die Transformation mit Hilfe eines kleinen Konsolendienstprogramms, das in C # geschrieben ist, aber Sie können es auch anders machen (falls erforderlich, werde ich es auch teilen, da gibt es nichts Kompliziertes und Geheimnisvolles).
Und dann können Sie daraus ein Dashboard erstellen, aber das ist, wie sie sagen, eine ganz andere Geschichte.
Und jetzt ein bisschen weinen zu den Entwicklern. Es gibt einen Fehler, keine Funktion, die es unmöglich macht, das oben Beschriebene vollständig auszuführen. Darüber hinaus gilt dies nur für C ++ - Projekte. In C # gibt es keine derartigen Probleme. Wenn eine Plog-Datei erstellt wird, wird <File>
Name im <File>
immer in Kleinbuchstaben umgewandelt. Und wenn redmine (und anderes Web) auf UNIX-ähnlichen Systemen mit Dateinamen mit Groß- und Kleinschreibung gehostet wird, wird der Fall beim Bilden von Links zu Dateien unterbrochen, wodurch Links funktionsunfähig werden. So eine Traurigkeit.
Ich habe eine Antwort auf den technischen Support erhalten, dass dieses Verhalten auf eine externe API zurückzuführen ist, aber es ist nicht klar, warum es so selektiv ist und nur für C ++ gilt und nicht für C #.
Deshalb appelliere ich vorerst, wie versprochen, an die Fortsetzung von Paull und hoffe auf eine fruchtbare Zusammenarbeit.
Vielen Dank für Ihre Aufmerksamkeit.
Update : Nach den Ergebnissen der Korrespondenz mit den von Paull und khandeliants vertretenen Entwicklern wurden tiefe Ausgrabungen durchgeführt, wodurch eine Beta veröffentlicht wurde, in der das oben beschriebene Problem gelöst wurde. Damit dies funktioniert, ist jedoch die Unterstützung für kurze Pfade erforderlich, zumindest auf dem Laufwerk, auf dem die Analyse durchgeführt wird. Dazu müssen Sie in der Registrierung entlang des Pfads HKLM \ SYSTEM \ CurrentControlSet \ Control \ FileSystem den Parameter NtfsDisable8dot3NameCreation (DWORD) auf einen Wert setzen, mit dem kurze Dateinamen gespeichert werden können. Siehe Details zu MSDN .
Das Standardverbot für Kurznamen ist erforderlich, um die Geschwindigkeit von NTFS zu erhöhen.
Sie können entweder den Wert 0 festlegen und sich nicht darum kümmern, oder 3, wenn die CI-Aufgaben im Benutzerprofil auf der Systempartition oder an einer anderen Stelle auf der Systempartition ausgeführt werden, oder in 2 den Befehl fsutil 8dot3name set Z: 0
(Ihre Festplatte anstelle von Z. :), wo der CI-Arbeitsbereich bereitgestellt wird (gilt übrigens auch für RAM-Festplatten).
Ich hoffe, diese Informationen erscheinen irgendwo auf der viva64-Website.
Jetzt scheint die Gestalt geschlossen zu sein, nochmals vielen Dank für Ihre Aufmerksamkeit.