GUI: Generate compliance report

This commit is contained in:
Daniel Marjamäki 2023-01-30 14:59:45 +01:00
parent 7c7ccdf7c0
commit e1b313ba76
10 changed files with 363 additions and 0 deletions

View File

@ -0,0 +1,169 @@
#include "compliancereportdialog.h"
#include "ui_compliancereportdialog.h"
#include "filelist.h"
#include "projectfile.h"
#include <QCryptographicHash>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include <QProcess>
#include <QRegularExpression>
#include <QSet>
#include <QTemporaryFile>
#include <QTextStream>
static void addHeaders(const QString& file1, QSet<QString> &allFiles) {
if (allFiles.contains(file1))
return;
QFile file(file1);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
allFiles << file1;
const QRegularExpression re("^#include[ ]*\"([^\">]+)\".*");
QTextStream in(&file);
QString line = in.readLine();
while (!in.atEnd()) {
if (line.startsWith("#include")) {
const QRegularExpressionMatch match = re.match(line);
if (match.hasMatch()) {
QString hfile = match.captured(1);
if (file1.contains("/"))
hfile = file1.mid(0,file1.lastIndexOf("/") + 1) + hfile;
addHeaders(hfile, allFiles);
}
}
line = in.readLine();
}
}
static std::vector<std::string> toStdStringList(const QStringList& from) {
std::vector<std::string> ret;
std::transform(from.cbegin(), from.cend(), std::back_inserter(ret), [](const QString& e) {
return e.toStdString();
});
return ret;
}
ComplianceReportDialog::ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile) :
QDialog(nullptr),
mUI(new Ui::ComplianceReportDialog),
mProjectFile(projectFile),
mResultsFile(std::move(resultsFile))
{
mUI->setupUi(this);
mUI->mEditProjectName->setText(projectFile->getProjectName());
connect(mUI->buttonBox, &QDialogButtonBox::clicked, this, &ComplianceReportDialog::buttonClicked);
}
ComplianceReportDialog::~ComplianceReportDialog()
{
delete mUI;
}
void ComplianceReportDialog::buttonClicked(QAbstractButton* button)
{
switch (mUI->buttonBox->standardButton(button)) {
case QDialogButtonBox::StandardButton::Save:
save();
break;
case QDialogButtonBox::StandardButton::Close:
close();
break;
default:
break;
};
}
void ComplianceReportDialog::save()
{
const QString outFile = QFileDialog::getSaveFileName(this,
tr("Compliance report"),
QDir::homePath() + "/misra-c-2012-compliance-report.html",
tr("HTML files (*.html)"));
if (outFile.isEmpty())
return;
close();
const QString& projectName = mUI->mEditProjectName->text();
const QString& projectVersion = mUI->mEditProjectVersion->text();
const bool files = mUI->mCheckFiles->isChecked();
if (projectName != mProjectFile->getProjectName()) {
mProjectFile->setProjectName(projectName);
mProjectFile->write();
}
QTemporaryFile tempFiles;
if (files && tempFiles.open()) {
QTextStream out(&tempFiles);
FileList fileList;
fileList.addPathList(mProjectFile->getCheckPaths());
if (!mProjectFile->getImportProject().isEmpty()) {
QFileInfo inf(mProjectFile->getFilename());
QString prjfile;
if (QFileInfo(mProjectFile->getImportProject()).isAbsolute())
prjfile = mProjectFile->getImportProject();
else
prjfile = inf.canonicalPath() + '/' + mProjectFile->getImportProject();
ImportProject p;
try {
p.import(prjfile.toStdString());
} catch (InternalError &e) {
QMessageBox msg(QMessageBox::Critical,
tr("Save compliance report"),
tr("Failed to import '%1', can not show files in compliance report").arg(prjfile),
QMessageBox::Ok,
this);
msg.exec();
return;
}
p.ignorePaths(toStdStringList(mProjectFile->getExcludedPaths()));
QDir dir(inf.absoluteDir());
for (const ImportProject::FileSettings& fs: p.fileSettings)
fileList.addFile(dir.relativeFilePath(QString::fromStdString(fs.filename)));
}
QSet<QString> allFiles;
for (const QString &sourcefile: fileList.getFileList())
addHeaders(sourcefile, allFiles);
for (const QString& fileName: allFiles) {
QFile f(fileName);
if (f.open(QFile::ReadOnly)) {
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
if (hash.addData(&f)) {
for (auto b: hash.result())
out << QString::number((unsigned char)b,16);
out << " " << fileName << "\n";
}
}
}
tempFiles.close();
}
QStringList args{"--compliant=misra-c2012-1.1",
"--compliant=misra-c2012-1.2",
"--project-name=" + projectName,
"--project-version=" + projectVersion,
"--output-file=" + outFile};
if (files)
args << "--files=" + tempFiles.fileName();
args << mResultsFile;
const QString appPath = QFileInfo(QCoreApplication::applicationFilePath()).canonicalPath();
QProcess process;
#ifdef Q_OS_WIN
process.start(appPath + "/compliance-report.exe", args);
#else
process.start(appPath + "/compliance-report", args);
#endif
process.waitForFinished();
}

View File

@ -0,0 +1,32 @@
#ifndef COMPLIANCEREPORTDIALOG_H
#define COMPLIANCEREPORTDIALOG_H
#include <QDialog>
namespace Ui {
class ComplianceReportDialog;
}
class ProjectFile;
class QAbstractButton;
class ComplianceReportDialog final : public QDialog
{
Q_OBJECT
public:
explicit ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile);
~ComplianceReportDialog() final;
private slots:
void buttonClicked(QAbstractButton* button);
private:
void save();
Ui::ComplianceReportDialog *mUI;
ProjectFile* mProjectFile;
QString mResultsFile;
};
#endif // COMPLIANCEREPORTDIALOG_H

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ComplianceReportDialog</class>
<widget class="QDialog" name="ComplianceReportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>403</width>
<height>199</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Project name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="mEditProjectName"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Project version</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="mEditProjectVersion"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="mCheckFiles">
<property name="text">
<string>List of files with md5 checksums</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -63,6 +63,7 @@ win32 {
RESOURCES = gui.qrc RESOURCES = gui.qrc
FORMS = about.ui \ FORMS = about.ui \
applicationdialog.ui \ applicationdialog.ui \
compliancereportdialog.ui \
fileview.ui \ fileview.ui \
helpdialog.ui \ helpdialog.ui \
mainwindow.ui \ mainwindow.ui \
@ -122,6 +123,7 @@ HEADERS += aboutdialog.h \
codeeditstyledialog.h \ codeeditstyledialog.h \
codeeditor.h \ codeeditor.h \
common.h \ common.h \
compliancereportdialog.h \
csvreport.h \ csvreport.h \
erroritem.h \ erroritem.h \
filelist.h \ filelist.h \
@ -162,6 +164,7 @@ SOURCES += aboutdialog.cpp \
codeeditstyledialog.cpp \ codeeditstyledialog.cpp \
codeeditor.cpp \ codeeditor.cpp \
common.cpp \ common.cpp \
compliancereportdialog.cpp \
csvreport.cpp \ csvreport.cpp \
erroritem.cpp \ erroritem.cpp \
filelist.cpp \ filelist.cpp \

View File

@ -25,6 +25,7 @@
#include "cppcheck.h" #include "cppcheck.h"
#include "errortypes.h" #include "errortypes.h"
#include "filelist.h" #include "filelist.h"
#include "compliancereportdialog.h"
#include "fileviewdialog.h" #include "fileviewdialog.h"
#include "helpdialog.h" #include "helpdialog.h"
#include "importproject.h" #include "importproject.h"
@ -68,6 +69,7 @@
#include <QTimer> #include <QTimer>
#include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QTemporaryFile>
static const QString OnlineHelpURL("https://cppcheck.sourceforge.io/manual.html"); static const QString OnlineHelpURL("https://cppcheck.sourceforge.io/manual.html");
static const QString compile_commands_json("compile_commands.json"); static const QString compile_commands_json("compile_commands.json");
@ -154,6 +156,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
connect(mUI->mActionStop, &QAction::triggered, this, &MainWindow::stopAnalysis); connect(mUI->mActionStop, &QAction::triggered, this, &MainWindow::stopAnalysis);
connect(mUI->mActionSave, &QAction::triggered, this, &MainWindow::save); connect(mUI->mActionSave, &QAction::triggered, this, &MainWindow::save);
connect(mUI->mActionComplianceReport, &QAction::triggered, this, &MainWindow::complianceReport);
// About menu // About menu
connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about); connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about);
@ -191,6 +194,8 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
else else
formatAndSetTitle(); formatAndSetTitle();
mUI->mActionComplianceReport->setVisible(isCppcheckPremium());
enableCheckButtons(true); enableCheckButtons(true);
mUI->mActionPrint->setShortcut(QKeySequence::Print); mUI->mActionPrint->setShortcut(QKeySequence::Print);
@ -1290,6 +1295,10 @@ void MainWindow::enableCheckButtons(bool enable)
} }
mUI->mActionAnalyzeDirectory->setEnabled(enable); mUI->mActionAnalyzeDirectory->setEnabled(enable);
if (isCppcheckPremium()) {
mUI->mActionComplianceReport->setEnabled(enable && mProjectFile && mProjectFile->getAddons().contains("misra"));
}
} }
void MainWindow::enableResultsButtons() void MainWindow::enableResultsButtons()
@ -1459,6 +1468,20 @@ void MainWindow::save()
} }
} }
void MainWindow::complianceReport()
{
if (isCppcheckPremium() && mProjectFile && mProjectFile->getAddons().contains("misra")) {
QTemporaryFile tempResults;
tempResults.open();
tempResults.close();
mUI->mResults->save(tempResults.fileName(), Report::XMLV2);
ComplianceReportDialog dlg(mProjectFile, tempResults.fileName());
dlg.exec();
}
}
void MainWindow::resultsAdded() void MainWindow::resultsAdded()
{} {}

View File

@ -168,6 +168,9 @@ public slots:
/** @brief Slot to save results */ /** @brief Slot to save results */
void save(); void save();
/** @brief Slot to generate compliance report */
void complianceReport();
/** @brief Slot to create new project file */ /** @brief Slot to create new project file */
void newProjectFile(); void newProjectFile();

View File

@ -140,6 +140,7 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="mActionProjectMRU"/> <addaction name="mActionProjectMRU"/>
<addaction name="mActionSave"/> <addaction name="mActionSave"/>
<addaction name="mActionComplianceReport"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="mActionPrintPreview"/> <addaction name="mActionPrintPreview"/>
<addaction name="mActionPrint"/> <addaction name="mActionPrint"/>
@ -914,6 +915,11 @@
<string>C++23</string> <string>C++23</string>
</property> </property>
</action--> </action-->
<action name="mActionComplianceReport">
<property name="text">
<string>Compliance report...</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -61,6 +61,7 @@ void ProjectFile::clear()
mExcludedPaths.clear(); mExcludedPaths.clear();
mLibraries.clear(); mLibraries.clear();
mPlatform.clear(); mPlatform.clear();
mProjectName.clear();
mSuppressions.clear(); mSuppressions.clear();
mAddons.clear(); mAddons.clear();
mClangAnalyzer = mClangTidy = false; mClangAnalyzer = mClangTidy = false;
@ -208,6 +209,8 @@ bool ProjectFile::read(const QString &filename)
readStringList(mCodingStandards, xmlReader, CppcheckXml::CodingStandardElementName); readStringList(mCodingStandards, xmlReader, CppcheckXml::CodingStandardElementName);
if (xmlReader.name() == QString(CppcheckXml::CertIntPrecisionElementName)) if (xmlReader.name() == QString(CppcheckXml::CertIntPrecisionElementName))
mCertIntPrecision = readInt(xmlReader, 0); mCertIntPrecision = readInt(xmlReader, 0);
if (xmlReader.name() == QString(CppcheckXml::ProjectNameElementName))
mProjectName = readString(xmlReader);
break; break;
@ -346,6 +349,32 @@ int ProjectFile::readInt(QXmlStreamReader &reader, int defaultValue)
} while (true); } while (true);
} }
QString ProjectFile::readString(QXmlStreamReader &reader)
{
QString ret;
do {
const QXmlStreamReader::TokenType type = reader.readNext();
switch (type) {
case QXmlStreamReader::Characters:
ret = reader.text().toString();
FALLTHROUGH;
case QXmlStreamReader::EndElement:
return ret;
// Not handled
case QXmlStreamReader::StartElement:
case QXmlStreamReader::NoToken:
case QXmlStreamReader::Invalid:
case QXmlStreamReader::StartDocument:
case QXmlStreamReader::EndDocument:
case QXmlStreamReader::Comment:
case QXmlStreamReader::DTD:
case QXmlStreamReader::EntityReference:
case QXmlStreamReader::ProcessingInstruction:
break;
}
} while (true);
}
void ProjectFile::readIncludeDirs(QXmlStreamReader &reader) void ProjectFile::readIncludeDirs(QXmlStreamReader &reader)
{ {
QXmlStreamReader::TokenType type; QXmlStreamReader::TokenType type;
@ -959,6 +988,12 @@ bool ProjectFile::write(const QString &filename)
xmlWriter.writeEndElement(); xmlWriter.writeEndElement();
} }
if (!mProjectName.isEmpty()) {
xmlWriter.writeStartElement(CppcheckXml::ProjectNameElementName);
xmlWriter.writeCharacters(mProjectName);
xmlWriter.writeEndElement();
}
xmlWriter.writeEndDocument(); xmlWriter.writeEndDocument();
file.close(); file.close();
return true; return true;

View File

@ -165,6 +165,14 @@ public:
return mPlatform; return mPlatform;
} }
QString getProjectName() const {
return mProjectName;
}
void setProjectName(QString projectName) {
mProjectName = std::move(projectName);
}
/** /**
* @brief Get "raw" suppressions. * @brief Get "raw" suppressions.
* @return list of suppressions. * @return list of suppressions.
@ -420,6 +428,8 @@ protected:
static int readInt(QXmlStreamReader &reader, int defaultValue); static int readInt(QXmlStreamReader &reader, int defaultValue);
static QString readString(QXmlStreamReader &reader);
/** /**
* @brief Read list of include directories from XML. * @brief Read list of include directories from XML.
* @param reader XML stream reader. * @param reader XML stream reader.
@ -581,6 +591,9 @@ private:
*/ */
QStringList mCodingStandards; QStringList mCodingStandards;
/** @brief Project name, used when generating compliance report */
QString mProjectName;
int mCertIntPrecision; int mCertIntPrecision;
/** @brief Execute clang analyzer? */ /** @brief Execute clang analyzer? */

View File

@ -184,6 +184,7 @@ namespace CppcheckXml {
const char CodingStandardsElementName[] = "coding-standards"; const char CodingStandardsElementName[] = "coding-standards";
const char CodingStandardElementName[] = "coding-standard"; const char CodingStandardElementName[] = "coding-standard";
const char CertIntPrecisionElementName[] = "cert-c-int-precision"; const char CertIntPrecisionElementName[] = "cert-c-int-precision";
const char ProjectNameElementName[] = "project-name";
} }
/// @} /// @}