Skip to content

Commit 02b0e87

Browse files
authored
fix #14207: GUI: write proper error messages when project import fails (danmar#7941)
Example: <img width="538" height="229" alt="screenshot" src="https://github.com/user-attachments/assets/4e78c684-173b-4577-a213-df3d8be61b33" />
1 parent 5fd0e6c commit 02b0e87

File tree

5 files changed

+40
-34
lines changed

5 files changed

+40
-34
lines changed

cli/cmdlineparser.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
11901190
if (projectType == ImportProject::Type::VS_SLN || projectType == ImportProject::Type::VS_VCXPROJ) {
11911191
mSettings.libraries.emplace_back("windows");
11921192
}
1193+
for (const auto &error : project.errors)
1194+
mLogger.printError(error);
11931195
if (projectType == ImportProject::Type::MISSING) {
11941196
mLogger.printError("failed to open project '" + projectFile + "'. The file does not exist.");
11951197
return Result::Fail;

gui/mainwindow.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,12 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
19901990
break;
19911991
}
19921992

1993+
if (!p.errors.empty())
1994+
errorMessage += ": \n";
1995+
1996+
for (const auto &error : p.errors)
1997+
errorMessage += "\n - " + QString::fromStdString(error);
1998+
19931999
if (!errorMessage.isEmpty()) {
19942000
QMessageBox msg(QMessageBox::Critical,
19952001
tr("Cppcheck"),

lib/importproject.cpp

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ bool ImportProject::importCompileCommands(std::istream &istr)
335335
picojson::value compileCommands;
336336
istr >> compileCommands;
337337
if (!compileCommands.is<picojson::array>()) {
338-
printError("compilation database is not a JSON array");
338+
errors.emplace_back("compilation database is not a JSON array");
339339
return false;
340340
}
341341

@@ -345,12 +345,12 @@ bool ImportProject::importCompileCommands(std::istream &istr)
345345
picojson::object obj = fileInfo.get<picojson::object>();
346346

347347
if (obj.count("directory") == 0) {
348-
printError("'directory' field in compilation database entry missing");
348+
errors.emplace_back("'directory' field in compilation database entry missing");
349349
return false;
350350
}
351351

352352
if (!obj["directory"].is<std::string>()) {
353-
printError("'directory' field in compilation database entry is not a string");
353+
errors.emplace_back("'directory' field in compilation database entry is not a string");
354354
return false;
355355
}
356356

@@ -377,24 +377,24 @@ bool ImportProject::importCompileCommands(std::istream &istr)
377377
}
378378
}
379379
} else {
380-
printError("'arguments' field in compilation database entry is not a JSON array");
380+
errors.emplace_back("'arguments' field in compilation database entry is not a JSON array");
381381
return false;
382382
}
383383
} else if (obj.count("command")) {
384384
doUnescape = true;
385385
if (obj["command"].is<std::string>()) {
386386
command = obj["command"].get<std::string>();
387387
} else {
388-
printError("'command' field in compilation database entry is not a string");
388+
errors.emplace_back("'command' field in compilation database entry is not a string");
389389
return false;
390390
}
391391
} else {
392-
printError("no 'arguments' or 'command' field found in compilation database entry");
392+
errors.emplace_back("no 'arguments' or 'command' field found in compilation database entry");
393393
return false;
394394
}
395395

396396
if (!obj.count("file") || !obj["file"].is<std::string>()) {
397-
printError("skip compilation database entry because it does not have a proper 'file' field");
397+
errors.emplace_back("skip compilation database entry because it does not have a proper 'file' field");
398398
continue;
399399
}
400400

@@ -434,14 +434,14 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
434434
std::string line;
435435

436436
if (!std::getline(istr,line)) {
437-
printError("Visual Studio solution file is empty");
437+
errors.emplace_back("Visual Studio solution file is empty");
438438
return false;
439439
}
440440

441441
if (!startsWith(line, "Microsoft Visual Studio Solution File")) {
442442
// Skip BOM
443443
if (!std::getline(istr, line) || !startsWith(line, "Microsoft Visual Studio Solution File")) {
444-
printError("Visual Studio solution file header not found");
444+
errors.emplace_back("Visual Studio solution file header not found");
445445
return false;
446446
}
447447
}
@@ -466,14 +466,14 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
466466
vcxproj = path + vcxproj;
467467
vcxproj = Path::fromNativeSeparators(std::move(vcxproj));
468468
if (!importVcxproj(vcxproj, variables, "", fileFilters, sharedItemsProjects)) {
469-
printError("failed to load '" + vcxproj + "' from Visual Studio solution");
469+
errors.emplace_back("failed to load '" + vcxproj + "' from Visual Studio solution");
470470
return false;
471471
}
472472
found = true;
473473
}
474474

475475
if (!found) {
476-
printError("no projects found in Visual Studio solution file");
476+
errors.emplace_back("no projects found in Visual Studio solution file");
477477
return false;
478478
}
479479

@@ -730,7 +730,7 @@ bool ImportProject::importVcxproj(const std::string &filename,
730730
tinyxml2::XMLDocument doc;
731731
const tinyxml2::XMLError error = doc.LoadFile(filename.c_str());
732732
if (error != tinyxml2::XML_SUCCESS) {
733-
printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
733+
errors.emplace_back(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
734734
return false;
735735
}
736736
return importVcxproj(filename, doc, variables, additionalIncludeDirectories, fileFilters, cache);
@@ -749,7 +749,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
749749

750750
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
751751
if (rootnode == nullptr) {
752-
printError("Visual Studio project file has no XML root node");
752+
errors.emplace_back("Visual Studio project file has no XML root node");
753753
return false;
754754
}
755755
for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) {
@@ -810,13 +810,13 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X
810810
pathToSharedItemsFile = variables["ProjectDir"] + projectAttribute;
811811
}
812812
if (!simplifyPathWithVariables(pathToSharedItemsFile, variables)) {
813-
printError("Could not simplify path to referenced shared items project");
813+
errors.emplace_back("Could not simplify path to referenced shared items project");
814814
return false;
815815
}
816816

817817
SharedItemsProject toAdd = importVcxitems(pathToSharedItemsFile, fileFilters, cache);
818818
if (!toAdd.successful) {
819-
printError("Could not load shared items project \"" + pathToSharedItemsFile + "\" from original path \"" + std::string(projectAttribute) + "\".");
819+
errors.emplace_back("Could not load shared items project \"" + pathToSharedItemsFile + "\" from original path \"" + std::string(projectAttribute) + "\".");
820820
return false;
821821
}
822822
sharedItemsProjects.emplace_back(toAdd);
@@ -928,12 +928,12 @@ ImportProject::SharedItemsProject ImportProject::importVcxitems(const std::strin
928928
tinyxml2::XMLDocument doc;
929929
const tinyxml2::XMLError error = doc.LoadFile(filename.c_str());
930930
if (error != tinyxml2::XML_SUCCESS) {
931-
printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
931+
errors.emplace_back(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
932932
return result;
933933
}
934934
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
935935
if (rootnode == nullptr) {
936-
printError("Visual Studio project file has no XML root node");
936+
errors.emplace_back("Visual Studio project file has no XML root node");
937937
return result;
938938
}
939939
for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) {
@@ -951,7 +951,7 @@ ImportProject::SharedItemsProject ImportProject::importVcxitems(const std::strin
951951

952952
result.sourceFiles.emplace_back(file);
953953
} else {
954-
printError("Could not find shared items source file");
954+
errors.emplace_back("Could not find shared items source file");
955955
return result;
956956
}
957957
}
@@ -979,12 +979,12 @@ bool ImportProject::importBcb6Prj(const std::string &projectFilename)
979979
tinyxml2::XMLDocument doc;
980980
const tinyxml2::XMLError error = doc.LoadFile(projectFilename.c_str());
981981
if (error != tinyxml2::XML_SUCCESS) {
982-
printError(std::string("Borland project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
982+
errors.emplace_back(std::string("Borland project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
983983
return false;
984984
}
985985
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
986986
if (rootnode == nullptr) {
987-
printError("Borland project file has no XML root node");
987+
errors.emplace_back("Borland project file has no XML root node");
988988
return false;
989989
}
990990

@@ -1296,12 +1296,12 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti
12961296
const std::string xmldata = istream_to_string(istr);
12971297
const tinyxml2::XMLError error = doc.Parse(xmldata.data(), xmldata.size());
12981298
if (error != tinyxml2::XML_SUCCESS) {
1299-
printError(std::string("Cppcheck GUI project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
1299+
errors.emplace_back(std::string("Cppcheck GUI project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
13001300
return false;
13011301
}
13021302
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
13031303
if (rootnode == nullptr || strcmp(rootnode->Name(), CppcheckXml::ProjectElementName) != 0) {
1304-
printError("Cppcheck GUI project file has no XML root node");
1304+
errors.emplace_back("Cppcheck GUI project file has no XML root node");
13051305
return false;
13061306
}
13071307

@@ -1420,7 +1420,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti
14201420
else if (strcmp(childname, Settings::SafeChecks::XmlExternalVariables) == 0)
14211421
temp.safeChecks.externalVariables = true;
14221422
else {
1423-
printError("Unknown '" + std::string(Settings::SafeChecks::XmlRootName) + "' element '" + childname + "' in Cppcheck project file");
1423+
errors.emplace_back("Unknown '" + std::string(Settings::SafeChecks::XmlRootName) + "' element '" + childname + "' in Cppcheck project file");
14241424
return false;
14251425
}
14261426
}
@@ -1443,7 +1443,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti
14431443
else if (strcmp(name, CppcheckXml::ProjectNameElementName) == 0)
14441444
; // no-op
14451445
else {
1446-
printError("Unknown element '" + std::string(name) + "' in Cppcheck project file");
1446+
errors.emplace_back("Unknown element '" + std::string(name) + "' in Cppcheck project file");
14471447
return false;
14481448
}
14491449
}
@@ -1548,7 +1548,3 @@ void ImportProject::setRelativePaths(const std::string &filename)
15481548
}
15491549
}
15501550

1551-
void ImportProject::printError(const std::string &message)
1552-
{
1553-
std::cout << "cppcheck: error: " << message << std::endl;
1554-
}

lib/importproject.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class CPPCHECKLIB WARN_UNUSED ImportProject {
7474
static void fsSetIncludePaths(FileSettings& fs, const std::string &basepath, const std::list<std::string> &in, std::map<std::string, std::string, cppcheck::stricmp> &variables);
7575

7676
std::list<FileSettings> fileSettings;
77+
std::vector<std::string> errors;
7778

7879
ImportProject() = default;
7980
virtual ~ImportProject() = default;
@@ -112,13 +113,11 @@ class CPPCHECKLIB WARN_UNUSED ImportProject {
112113
};
113114

114115
bool importSln(std::istream &istr, const std::string &path, const std::vector<std::string> &fileFilters);
115-
static SharedItemsProject importVcxitems(const std::string &filename, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
116+
SharedItemsProject importVcxitems(const std::string &filename, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
116117
bool importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
117118
bool importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
118119
bool importBcb6Prj(const std::string &projectFilename);
119120

120-
static void printError(const std::string &message);
121-
122121
void setRelativePaths(const std::string &filename);
123122

124123
std::string mPath;

test/testimportproject.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,8 @@ class TestImportProject : public TestFixture {
409409
TestImporter importer;
410410
ASSERT_EQUALS(false, importer.importCompileCommands(istr));
411411
ASSERT_EQUALS(0, importer.fileSettings.size());
412-
ASSERT_EQUALS("cppcheck: error: no 'arguments' or 'command' field found in compilation database entry\n", GET_REDIRECT_OUTPUT);
412+
ASSERT_EQUALS(1, importer.errors.size());
413+
ASSERT_EQUALS("no 'arguments' or 'command' field found in compilation database entry", importer.errors[0]);
413414
}
414415

415416
void importCompileCommandsDirectoryMissing() const {
@@ -419,7 +420,8 @@ class TestImportProject : public TestFixture {
419420
TestImporter importer;
420421
ASSERT_EQUALS(false, importer.importCompileCommands(istr));
421422
ASSERT_EQUALS(0, importer.fileSettings.size());
422-
ASSERT_EQUALS("cppcheck: error: 'directory' field in compilation database entry missing\n", GET_REDIRECT_OUTPUT);
423+
ASSERT_EQUALS(1, importer.errors.size());
424+
ASSERT_EQUALS("'directory' field in compilation database entry missing", importer.errors[0]);
423425
}
424426

425427
void importCompileCommandsDirectoryInvalid() const {
@@ -430,7 +432,8 @@ class TestImportProject : public TestFixture {
430432
TestImporter importer;
431433
ASSERT_EQUALS(false, importer.importCompileCommands(istr));
432434
ASSERT_EQUALS(0, importer.fileSettings.size());
433-
ASSERT_EQUALS("cppcheck: error: 'directory' field in compilation database entry is not a string\n", GET_REDIRECT_OUTPUT);
435+
ASSERT_EQUALS(1, importer.errors.size());
436+
ASSERT_EQUALS("'directory' field in compilation database entry is not a string", importer.errors[0]);
434437
}
435438

436439
void importCppcheckGuiProject() const {

0 commit comments

Comments
 (0)