Eu acho que não faz sentido mais uma vez anunciar uma ferramenta maravilhosa para análise estática - PVS Studio. Já existem muitos artigos sobre o Habr, mas quero abordar outro aspecto - o uso dessa ferramenta em um sistema de integração contínua.
Portanto, existe alguma organização, existe um IC que funciona simplesmente: Jenkins recebe um gancho após um empurrão no Git, após o qual lança algum pipeline. Devido às ferramentas utilizadas, a montagem é realizada para projetos criados em C # (msbuild) e C ++ (msbuild, CMake). Em um dos estágios de acabamento, a geração de relatórios é iniciada, incluindo o uso do PVS Studio (entre outras coisas, cppcheck, mas isso não é importante para outras narrativas).
O PVS Studio possui uma ferramenta de análise de console que é iniciada na linha de comando: PVS-Studio_Cmd.exe --target "${projectFile}" --output report.plog --progress
Na entrada - o nome do projeto (.sln), na saída - o relatório.
Relatório - um arquivo com a extensão .plog, é um arquivo XML comum. O esquema de documentos é incorporado, para que não haja surpresas no formato de saída. Pelo menos até que os desenvolvedores alterem o esquema, mas não consideraremos essa opção.
O relatório consiste em um conjunto de registros, cada um dos quais aponta para um arquivo e uma linha, uma classe de erro, nível de erro, descrição e outras coisas não muito interessantes.
Mas ler XML com seus olhos é um prazer para si mesmo, então você precisa de uma maneira de visualizar e navegar.
O mais simples e mais funcional é o plug-in PVS Studio para Visual Studio, com a capacidade de navegar pelo código. Mas forçar um gerente técnico ou outra pessoa interessada a carregar um projeto no VS toda vez é uma má ideia, e o histórico do desenvolvimento do projeto não é visível.
Então, vamos para o outro lado e ver o que pode ser feito. E existe uma maneira bastante padrão que permite converter XML para outra coisa: XSLT . Agora, provavelmente, um dos leitores distorceu, mas, no entanto, proponho continuar lendo.
XSLT é uma linguagem para converter documentos XML em outra coisa. Ele simplesmente compara a regra de conversão ao modelo de entrada, mas fizemos a conversão em um relatório HTML para nós mesmos.
Espero que ninguém me julgue por criar tabelas, porque os dados são de natureza tabular. Cada registro no relatório corresponderá a uma linha da tabela com as seguintes colunas:
- O número da linha na tabela.
- Nome do arquivo
- Número da linha
- O código de erro.
- A mensagem de erro.
O número da linha é apenas conveniente para referência verbal na discussão.
O nome do arquivo, juntamente com o nome da linha, permite criar um link para o repositório. Mas mais sobre isso mais tarde.
O código de erro é enquadrado por um link para o site de desenvolvedores do PVS-Studio: http://viva64.com/en/{ErrorCode } (ou / ru /, como desejar).
A mensagem de erro é nenhum comentário.
Existem alguns pontos a serem tratados.
Em primeiro lugar, gostaria que as mensagens fossem classificadas por nível de importância, além de ter um número total de mensagens de cada tipo. A primeira tarefa é resolvida usando a expressão xsl:sort
, a segunda é count([])
.
Segundo: o nome do arquivo é indicado na íntegra e é necessário um nome relativo para criar um link para o sistema de controle de versão. Você só precisa cortar o prefixo correspondente ao nome do diretório com o projeto no qual o repositório foi clonado (temos o Git, mas ele se adapta facilmente). Mas, para que esse caminho apareça, precisamos parametrizar a transformação XSL usando a construção xsl:param
. O resto é relativamente simples: remova da linha com o nome do arquivo um prefixo comum com o nome do diretório em que o repositório está clonado. Devo dizer que no XSLT esse problema é resolvido de maneira bastante sofisticada.
Terceiro: a validação se refere a uma revisão específica no repositório, e isso também precisa ser mantido em mente. É resolvido usando o parâmetro com o identificador de confirmação. Da mesma forma para os ramos.
Quarto: se você usar bibliotecas de terceiros com códigos-fonte, não misture avisos neles com avisos em nosso projeto. O problema foi resolvido da seguinte maneira: colocamos todos os projetos externos em um determinado diretório, cujo nome não está contido em nosso projeto. Agora, se o nome do arquivo contiver esse subdiretório (na verdade, apenas uma subcadeia de caracteres), a entrada no plog não entrará no relatório, mas será considerada como "oculta" no cabeçalho do relatório. Para maior flexibilidade, você pode parametrizar a transformação e atribuir um nome padrão a este diretório: <xsl:param name="external" select="'External'" />
Bem, e mais uma pequena tarefa: coletar o link para um repositório. Usamos redmine + gitolite. Mais uma vez, adaptável.
Como muitos parâmetros são constantes para a conversão, você pode preparar um prefixo 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>
Algumas belezas com estilização, e você pode usá-lo. Incorporamos CSS na página, apenas para ter um relatório de arquivo único. Nós também não precisamos de fotos.
Código de transformação completo sob o 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>
Iniciamos a transformação com a ajuda de um pequeno utilitário de console escrito em C #, mas você pode fazer de maneira diferente (se necessário, também compartilharei, não há nada complicado e secreto).
E então você pode criar um painel com isso, mas isso, como se costuma dizer, é uma história completamente diferente.
E agora um pouco chorando para os desenvolvedores. Há um bug, não um recurso, que impossibilita a execução completa do descrito acima; além disso, isso só se aplica a projetos em C ++, pois em C # não existe esse problema. Quando um arquivo plog é formado, na <File>
nome é sempre convertido para minúsculas. E quando o redmine (e outra web) é hospedado em sistemas semelhantes ao UNIX com nomes de arquivo que diferenciam maiúsculas de minúsculas, o caso é interrompido ao formar links para arquivos, o que torna os links inoperantes. Que tristeza.
Recebi uma resposta à carta de suporte técnico de que esse comportamento se deve a uma API externa, mas não está claro por que é tão seletivo e se aplica apenas ao C ++ e não ao C #.
Portanto, por enquanto, como prometido, apelo à continuação de Paull e espero uma cooperação frutuosa.
Obrigado pela atenção.
Atualização : de acordo com os resultados da correspondência com os desenvolvedores representados por Paull e khandeliants , foram realizadas escavações profundas, como resultado do lançamento de uma versão beta na qual o problema descrito acima foi resolvido. Mas, para que isso funcione, você precisa de suporte para caminhos curtos, pelo menos na unidade em que a análise está sendo conduzida. Para fazer isso, no registro no caminho HKLM \ SYSTEM \ CurrentControlSet \ Control \ FileSystem, é necessário definir o parâmetro NtfsDisable8dot3NameCreation (DWORD) com um valor que permita salvar nomes curtos de arquivos. Veja detalhes no MSDN .
A proibição padrão de nomes abreviados é necessária para aumentar a velocidade do NTFS.
Você pode definir o valor 0 e não incomodá-lo, ou 3 se as tarefas de IC forem executadas no perfil do usuário na partição do sistema ou em outro local da partição do sistema ou em 2 e executar o comando fsutil 8dot3name set Z: 0
(seu disco em vez de Z :), onde o espaço de trabalho do IC será implantado (a propósito, também se aplica aos discos RAM).
Espero que essas informações apareçam em algum lugar do site viva64.
Agora parece que a gestalt está fechada, obrigado novamente por sua atenção.