Creo que no tiene sentido volver a anunciar una herramienta maravillosa para el análisis estático: PVS Studio. Ya hay muchos artículos sobre Habr dedicados a ella, pero quiero tocar otro aspecto: el uso de esta herramienta en un sistema de integración continua.
Entonces, hay alguna organización, hay un CI que funciona simplemente: Jenkins recibe un gancho después de un empujón en Git, después de lo cual lanza una tubería. Debido a las herramientas utilizadas, el ensamblaje se lleva a cabo para proyectos creados en C # (msbuild) y C ++ (msbuild, CMake). En una de las etapas finales, se inicia la generación de informes, incluido el uso de PVS Studio (entre otras cosas, cppcheck, pero esto no es importante para la narración posterior).
PVS Studio tiene una herramienta de análisis de consola que se inicia desde la línea de comandos: PVS-Studio_Cmd.exe --target "${projectFile}" --output report.plog --progress
En la entrada, el nombre del proyecto (.sln), en la salida, el informe.
Informe: un archivo con la extensión .plog es un archivo XML normal. El esquema de documentos está integrado, por lo que no puede haber sorpresas en el formato de salida. Al menos hasta que los desarrolladores cambien el esquema, pero no consideraremos esta opción.
El informe consta de un conjunto de registros, cada uno de los cuales apunta a un archivo y una línea en él, una clase de error, nivel de error, descripción y otras cosas no muy interesantes.
Pero leer XML con los ojos es un placer para usted, por lo que necesita alguna forma de ver y navegar.
El complemento más simple y que funciona es el complemento PVS Studio para Visual Studio, con la capacidad de navegar a través del código. Pero obligar a un gerente técnico u otra persona interesada a cargar un proyecto en VS cada vez es una mala idea, y la historia del desarrollo del proyecto no es visible.
Así que vamos al otro lado y veamos qué se puede hacer. Y hay una forma bastante estándar que le permite convertir XML a otra cosa: XSLT . Ahora, probablemente, uno de los lectores se ha distorsionado, pero, sin embargo, propongo continuar leyendo.
XSLT es un lenguaje para convertir documentos XML a otra cosa. Simplemente compara la regla de conversión con la plantilla de entrada, pero nosotros mismos hemos realizado la conversión a un informe HTML.
Espero que nadie me juzgue por hacer tablas, porque los datos son de naturaleza tabular. Cada registro en el informe corresponderá a una fila de la tabla con las siguientes columnas:
- El número de fila en la tabla.
- Nombre de archivo
- Numero de linea.
- El código de error
- El mensaje de error
El número de línea es conveniente para referencia verbal en la discusión.
El nombre del archivo junto con el nombre de la línea le permite crear un enlace al repositorio. Pero más sobre eso más tarde.
El código de error está enmarcado por un enlace al sitio web de desarrolladores de PVS-Studio: http://viva64.com/en/{ErrorCode } (o / ru /, como lo desee).
El mensaje de error no es comentario.
Hay algunos puntos a tratar.
En primer lugar, me gustaría que los mensajes se clasifiquen por nivel de importancia, así como que tengan un número total de mensajes de cada tipo. La primera tarea se resuelve usando la expresión xsl:sort
, la segunda es count([])
.
Segundo: el nombre del archivo se indica en su totalidad y se necesita un nombre relativo para crear un enlace al sistema de control de versiones. Solo necesita cortar el prefijo correspondiente al nombre del directorio con el proyecto en el que se ha clonado el repositorio (tenemos Git, pero se adapta fácilmente). Pero para que aparezca esta ruta, necesitamos parametrizar la transformación XSL usando la construcción xsl:param
. El resto es relativamente simple: elimine de la línea con el nombre del archivo un prefijo común con el nombre del directorio donde se clona el repositorio. Debo decir que en XSLT este problema se resuelve de manera bastante sofisticada.
Tercero: la validación se refiere a una revisión específica en el repositorio, y esto también debe tenerse en cuenta. Se resuelve utilizando el parámetro con el identificador de confirmación. Del mismo modo para las ramas.
Cuarto: si usa bibliotecas de terceros con códigos fuente, no mezcle advertencias en ellas con advertencias en nuestro proyecto. El problema se resuelve de la siguiente manera: colocamos todos los proyectos externos en un directorio determinado, cuyo nombre no figura en nuestro proyecto. Ahora, si el nombre del archivo contiene este subdirectorio (en realidad solo una subcadena), la entrada en el cuadro de diálogo no ingresa en el informe, pero se considera "oculta" en el encabezado del informe. Para una mayor flexibilidad, puede parametrizar la transformación y asignar un nombre predeterminado a este directorio: <xsl:param name="external" select="'External'" />
Bueno y una pequeña tarea más: recopilar el enlace a un repositorio. Usamos redmine + gitolite. De nuevo, adaptable.
Dado que muchos parámetros son constantes para la conversión, puede preparar un prefijo de URL constante:
<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>
Algunas bellezas con estilización, y puedes usarlo. Incorporamos CSS en la página, solo para tener un informe de archivo único. Tampoco necesitamos fotos.
Código de transformación completo bajo el 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>
Comenzamos la transformación con la ayuda de una pequeña utilidad de consola escrita en C #, pero puede hacerlo de manera diferente (si es necesario, también lo compartiré, no hay nada complicado y secreto allí).
Y luego puedes hacer un tablero de mandos a partir de esto, pero esto, como dicen, es una historia completamente diferente.
Y ahora un poco de llanto hacia los desarrolladores. Hay un error, no una característica, que hace que sea imposible hacer completamente lo que se describe anteriormente, además, esto solo se aplica a proyectos de C ++, en C # no existe tal problema. Cuando se forma un archivo plog, en la <File>
nombre siempre se convierte en minúsculas. Y cuando redmine (y otros sitios web) está alojado en sistemas similares a UNIX con nombres de archivo sensibles a mayúsculas y minúsculas, el caso se rompe al formar enlaces a archivos, lo que hace que los enlaces no funcionen. Qué tristeza.
Recibí una respuesta a la carta de soporte técnico de que este comportamiento se debe a una API externa, pero no está claro por qué es tan selectivo y se aplica solo a C ++, y no se aplica a C #.
Por lo tanto, por ahora, como lo prometí, hago un llamamiento para que Paull continúe y espero una fructífera cooperación.
Gracias por su atencion
Actualización : de acuerdo con los resultados de la correspondencia con los desarrolladores representados por Paull y khandeliants , se llevaron a cabo excavaciones profundas, como resultado de lo cual se lanzó una versión beta en la que se resolvió el problema descrito anteriormente. Pero para que esto funcione, se requiere soporte para rutas cortas, al menos en la unidad donde se realiza el análisis. Para hacer esto, en el registro a lo largo de la ruta HKLM \ SYSTEM \ CurrentControlSet \ Control \ FileSystem, debe establecer el parámetro NtfsDisable8dot3NameCreation (DWORD) en un valor que permita guardar nombres de archivos cortos. Ver detalles en MSDN .
La prohibición predeterminada de los nombres cortos es necesaria para aumentar la velocidad de NTFS.
Puede establecer el valor 0 y no molestar, o 3 si las tareas de CI se realizan en el perfil de usuario en la partición del sistema o en otro lugar de la partición del sistema, o en 2 y ejecutar el comando fsutil 8dot3name set Z: 0
(su disco en lugar de Z :), donde se desplegará el espacio de trabajo de CI (por cierto, también se aplica a los discos RAM).
Espero que esta información aparezca en algún lugar del sitio web de viva64.
Ahora parece que la gestalt está cerrada, gracias de nuevo por su atención.