GUI: Generate compliance report
This commit is contained in:
parent
7c7ccdf7c0
commit
e1b313ba76
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -63,6 +63,7 @@ win32 {
|
|||
RESOURCES = gui.qrc
|
||||
FORMS = about.ui \
|
||||
applicationdialog.ui \
|
||||
compliancereportdialog.ui \
|
||||
fileview.ui \
|
||||
helpdialog.ui \
|
||||
mainwindow.ui \
|
||||
|
@ -122,6 +123,7 @@ HEADERS += aboutdialog.h \
|
|||
codeeditstyledialog.h \
|
||||
codeeditor.h \
|
||||
common.h \
|
||||
compliancereportdialog.h \
|
||||
csvreport.h \
|
||||
erroritem.h \
|
||||
filelist.h \
|
||||
|
@ -162,6 +164,7 @@ SOURCES += aboutdialog.cpp \
|
|||
codeeditstyledialog.cpp \
|
||||
codeeditor.cpp \
|
||||
common.cpp \
|
||||
compliancereportdialog.cpp \
|
||||
csvreport.cpp \
|
||||
erroritem.cpp \
|
||||
filelist.cpp \
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "cppcheck.h"
|
||||
#include "errortypes.h"
|
||||
#include "filelist.h"
|
||||
#include "compliancereportdialog.h"
|
||||
#include "fileviewdialog.h"
|
||||
#include "helpdialog.h"
|
||||
#include "importproject.h"
|
||||
|
@ -68,6 +69,7 @@
|
|||
#include <QTimer>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
static const QString OnlineHelpURL("https://cppcheck.sourceforge.io/manual.html");
|
||||
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->mActionSave, &QAction::triggered, this, &MainWindow::save);
|
||||
connect(mUI->mActionComplianceReport, &QAction::triggered, this, &MainWindow::complianceReport);
|
||||
|
||||
// About menu
|
||||
connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about);
|
||||
|
@ -191,6 +194,8 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
|
|||
else
|
||||
formatAndSetTitle();
|
||||
|
||||
mUI->mActionComplianceReport->setVisible(isCppcheckPremium());
|
||||
|
||||
enableCheckButtons(true);
|
||||
|
||||
mUI->mActionPrint->setShortcut(QKeySequence::Print);
|
||||
|
@ -1290,6 +1295,10 @@ void MainWindow::enableCheckButtons(bool enable)
|
|||
}
|
||||
|
||||
mUI->mActionAnalyzeDirectory->setEnabled(enable);
|
||||
|
||||
if (isCppcheckPremium()) {
|
||||
mUI->mActionComplianceReport->setEnabled(enable && mProjectFile && mProjectFile->getAddons().contains("misra"));
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{}
|
||||
|
||||
|
|
|
@ -168,6 +168,9 @@ public slots:
|
|||
/** @brief Slot to save results */
|
||||
void save();
|
||||
|
||||
/** @brief Slot to generate compliance report */
|
||||
void complianceReport();
|
||||
|
||||
/** @brief Slot to create new project file */
|
||||
void newProjectFile();
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="mActionProjectMRU"/>
|
||||
<addaction name="mActionSave"/>
|
||||
<addaction name="mActionComplianceReport"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="mActionPrintPreview"/>
|
||||
<addaction name="mActionPrint"/>
|
||||
|
@ -914,6 +915,11 @@
|
|||
<string>C++23</string>
|
||||
</property>
|
||||
</action-->
|
||||
<action name="mActionComplianceReport">
|
||||
<property name="text">
|
||||
<string>Compliance report...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -61,6 +61,7 @@ void ProjectFile::clear()
|
|||
mExcludedPaths.clear();
|
||||
mLibraries.clear();
|
||||
mPlatform.clear();
|
||||
mProjectName.clear();
|
||||
mSuppressions.clear();
|
||||
mAddons.clear();
|
||||
mClangAnalyzer = mClangTidy = false;
|
||||
|
@ -208,6 +209,8 @@ bool ProjectFile::read(const QString &filename)
|
|||
readStringList(mCodingStandards, xmlReader, CppcheckXml::CodingStandardElementName);
|
||||
if (xmlReader.name() == QString(CppcheckXml::CertIntPrecisionElementName))
|
||||
mCertIntPrecision = readInt(xmlReader, 0);
|
||||
if (xmlReader.name() == QString(CppcheckXml::ProjectNameElementName))
|
||||
mProjectName = readString(xmlReader);
|
||||
|
||||
break;
|
||||
|
||||
|
@ -346,6 +349,32 @@ int ProjectFile::readInt(QXmlStreamReader &reader, int defaultValue)
|
|||
} 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)
|
||||
{
|
||||
QXmlStreamReader::TokenType type;
|
||||
|
@ -959,6 +988,12 @@ bool ProjectFile::write(const QString &filename)
|
|||
xmlWriter.writeEndElement();
|
||||
}
|
||||
|
||||
if (!mProjectName.isEmpty()) {
|
||||
xmlWriter.writeStartElement(CppcheckXml::ProjectNameElementName);
|
||||
xmlWriter.writeCharacters(mProjectName);
|
||||
xmlWriter.writeEndElement();
|
||||
}
|
||||
|
||||
xmlWriter.writeEndDocument();
|
||||
file.close();
|
||||
return true;
|
||||
|
|
|
@ -165,6 +165,14 @@ public:
|
|||
return mPlatform;
|
||||
}
|
||||
|
||||
QString getProjectName() const {
|
||||
return mProjectName;
|
||||
}
|
||||
|
||||
void setProjectName(QString projectName) {
|
||||
mProjectName = std::move(projectName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get "raw" suppressions.
|
||||
* @return list of suppressions.
|
||||
|
@ -420,6 +428,8 @@ protected:
|
|||
|
||||
static int readInt(QXmlStreamReader &reader, int defaultValue);
|
||||
|
||||
static QString readString(QXmlStreamReader &reader);
|
||||
|
||||
/**
|
||||
* @brief Read list of include directories from XML.
|
||||
* @param reader XML stream reader.
|
||||
|
@ -581,6 +591,9 @@ private:
|
|||
*/
|
||||
QStringList mCodingStandards;
|
||||
|
||||
/** @brief Project name, used when generating compliance report */
|
||||
QString mProjectName;
|
||||
|
||||
int mCertIntPrecision;
|
||||
|
||||
/** @brief Execute clang analyzer? */
|
||||
|
|
|
@ -184,6 +184,7 @@ namespace CppcheckXml {
|
|||
const char CodingStandardsElementName[] = "coding-standards";
|
||||
const char CodingStandardElementName[] = "coding-standard";
|
||||
const char CertIntPrecisionElementName[] = "cert-c-int-precision";
|
||||
const char ProjectNameElementName[] = "project-name";
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
|
Loading…
Reference in New Issue