From 961f66baffab76b688d24cd6feff67627b661659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 31 Jan 2019 20:40:15 +0100 Subject: [PATCH] Fixed #8820 (import GUI project) This has basic handling of GUI projects. But further work will be needed to handle addons etc, the plan is that we will be able to run addons from the command line soon. --- cli/cmdlineparser.cpp | 42 +++++++++-- lib/importproject.cpp | 144 +++++++++++++++++++++++++++++++++++-- lib/importproject.h | 18 ++++- test/testimportproject.cpp | 34 ++++++++- 4 files changed, 221 insertions(+), 17 deletions(-) diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 60be19dec..6ba1ace54 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -520,19 +520,53 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[]) // --project else if (std::strncmp(argv[i], "--project=", 10) == 0) { const std::string projectFile = argv[i]+10; - const ImportProject::Type projType = mSettings->project.import(projectFile); - if (projType == ImportProject::VS_SLN || projType == ImportProject::VS_VCXPROJ) { + ImportProject::Type projType = mSettings->project.import(projectFile, mSettings); + if (projType == ImportProject::Type::CPPCHECK_GUI) { + mPathNames = mSettings->project.guiProject.pathNames; + for (const std::string &lib : mSettings->project.guiProject.libraries) { + if (!CppCheckExecutor::tryLoadLibrary(mSettings->library, argv[0], lib.c_str())) + return false; + } + + const std::string platform(mSettings->project.guiProject.platform); + + if (platform == "win32A") + mSettings->platform(Settings::Win32A); + else if (platform == "win32W") + mSettings->platform(Settings::Win32W); + else if (platform == "win64") + mSettings->platform(Settings::Win64); + else if (platform == "unix32") + mSettings->platform(Settings::Unix32); + else if (platform == "unix64") + mSettings->platform(Settings::Unix64); + else if (platform == "native") + mSettings->platform(Settings::Native); + else if (platform == "unspecified") + mSettings->platform(Settings::Unspecified); + else if (!mSettings->loadPlatformFile(argv[0], platform)) { + std::string message("cppcheck: error: unrecognized platform: \""); + message += platform; + message += "\"."; + printMessage(message); + return false; + } + + if (!mSettings->project.guiProject.projectFile.empty()) + projType = mSettings->project.import(mSettings->project.guiProject.projectFile, mSettings); + } + if (projType == ImportProject::Type::VS_SLN || projType == ImportProject::Type::VS_VCXPROJ) { if (!CppCheckExecutor::tryLoadLibrary(mSettings->library, argv[0], "windows.cfg")) { // This shouldn't happen normally. printMessage("cppcheck: Failed to load 'windows.cfg'. Your Cppcheck installation is broken. Please re-install."); return false; } } - if (projType == ImportProject::MISSING) { + if (projType == ImportProject::Type::MISSING) { printMessage("cppcheck: Failed to open project '" + projectFile + "'."); return false; } - if (projType == ImportProject::UNKNOWN) { + if (projType == ImportProject::Type::UNKNOWN) { printMessage("cppcheck: Failed to load project '" + projectFile + "'. The format is unknown."); return false; } diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 93c300bf7..2c684ca33 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -168,29 +168,31 @@ void ImportProject::FileSettings::setIncludePaths(const std::string &basepath, c includePaths.swap(I); } -ImportProject::Type ImportProject::import(const std::string &filename) +ImportProject::Type ImportProject::import(const std::string &filename, Settings *settings) { std::ifstream fin(filename); if (!fin.is_open()) - return MISSING; + return ImportProject::Type::MISSING; if (endsWith(filename, ".json", 5)) { importCompileCommands(fin); - return COMPILE_DB; + return ImportProject::Type::COMPILE_DB; } else if (endsWith(filename, ".sln", 4)) { std::string path(Path::getPathFromFilename(Path::fromNativeSeparators(filename))); if (!path.empty() && !endsWith(path,'/')) path += '/'; importSln(fin,path); - return VS_SLN; + return ImportProject::Type::VS_SLN; } else if (endsWith(filename, ".vcxproj", 8)) { std::map variables; importVcxproj(filename, variables, emptyString); - return VS_VCXPROJ; + return ImportProject::Type::VS_VCXPROJ; } else if (endsWith(filename, ".bpr", 4)) { importBcb6Prj(filename); - return BORLAND; + return ImportProject::Type::BORLAND; + } else if (endsWith(filename, ".cppcheck", 9)) { + return importCppcheckGuiProject(fin, settings) ? ImportProject::Type::CPPCHECK_GUI : ImportProject::Type::MISSING; } - return UNKNOWN; + return ImportProject::Type::UNKNOWN; } static std::string readUntil(const std::string &command, std::string::size_type *pos, const char until[]) @@ -836,3 +838,131 @@ void ImportProject::importBcb6Prj(const std::string &projectFilename) fileSettings.push_back(fs); } } + +static std::list readXmlStringList(const tinyxml2::XMLElement *node, const char name[], const char attribute[]) +{ + std::list ret; + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { + if (strcmp(child->Name(), name) != 0) + continue; + const char *attr = attribute ? child->Attribute(attribute) : child->GetText(); + if (attr) + ret.push_back(attr); + } + return ret; +} + +static std::string join(const std::list &strlist, const char *sep) +{ + std::string ret; + for (const std::string &s : strlist) { + ret += (ret.empty() ? "" : sep) + s; + } + return ret; +} + +// These constants are copy/pasted from gui/projectfile.cpp +static const char ProjectElementName[] = "project"; +static const char ProjectVersionAttrib[] = "version"; +static const char ProjectFileVersion[] = "1"; +static const char BuildDirElementName[] = "builddir"; +static const char ImportProjectElementName[] = "importproject"; +static const char AnalyzeAllVsConfigsElementName[] = "analyze-all-vs-configs"; +static const char IncludeDirElementName[] = "includedir"; +static const char DirElementName[] = "dir"; +static const char DirNameAttrib[] = "name"; +static const char DefinesElementName[] = "defines"; +static const char DefineName[] = "define"; +static const char DefineNameAttrib[] = "name"; +static const char UndefinesElementName[] = "undefines"; +static const char UndefineName[] = "undefine"; +static const char PathsElementName[] = "paths"; +static const char PathName[] = "dir"; +static const char PathNameAttrib[] = "name"; +static const char RootPathName[] = "root"; +static const char RootPathNameAttrib[] = "name"; +static const char IgnoreElementName[] = "ignore"; +static const char IgnorePathName[] = "path"; +static const char IgnorePathNameAttrib[] = "name"; +static const char ExcludeElementName[] = "exclude"; +static const char ExcludePathName[] = "path"; +static const char ExcludePathNameAttrib[] = "name"; +static const char LibrariesElementName[] = "libraries"; +static const char LibraryElementName[] = "library"; +static const char PlatformElementName[] = "platform"; +static const char SuppressionsElementName[] = "suppressions"; +static const char SuppressionElementName[] = "suppression"; +static const char AddonElementName[] = "addon"; +static const char AddonsElementName[] = "addons"; +static const char ToolElementName[] = "tool"; +static const char ToolsElementName[] = "tools"; +static const char TagsElementName[] = "tags"; +static const char TagElementName[] = "tag"; + + +bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *settings) +{ + tinyxml2::XMLDocument doc; + const std::string xmldata(std::istreambuf_iterator(istr), {}); + if (doc.Parse(xmldata.data(), xmldata.size()) != tinyxml2::XML_SUCCESS) + return false; + const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); + if (rootnode == nullptr || strcmp(rootnode->Name(), ProjectElementName) != 0) + return false; + + (void)ProjectFileVersion; + (void)ProjectVersionAttrib; + + std::list paths; + std::list excludePaths; + Settings temp; + + for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { + if (strcmp(node->Name(), RootPathName) == 0 && node->Attribute(RootPathNameAttrib)) + temp.basePaths.push_back(node->Attribute(RootPathNameAttrib)); + else if (strcmp(node->Name(), BuildDirElementName) == 0) + temp.buildDir = node->GetText() ? node->GetText() : ""; + else if (strcmp(node->Name(), IncludeDirElementName) == 0) + temp.includePaths = readXmlStringList(node, DirElementName, DirNameAttrib); + else if (strcmp(node->Name(), DefinesElementName) == 0) + temp.userDefines = join(readXmlStringList(node, DefineName, DefineNameAttrib), ";"); + else if (strcmp(node->Name(), UndefinesElementName) == 0) { + for (const std::string &u : readXmlStringList(node, UndefineName, nullptr)) + temp.userUndefs.insert(u); + } else if (strcmp(node->Name(), ImportProjectElementName) == 0) + guiProject.projectFile = node->GetText() ? node->GetText() : ""; + else if (strcmp(node->Name(), PathsElementName) == 0) + paths = readXmlStringList(node, PathName, PathNameAttrib); + else if (strcmp(node->Name(), ExcludeElementName) == 0) + excludePaths = readXmlStringList(node, ExcludePathName, ExcludePathNameAttrib); + else if (strcmp(node->Name(), IgnoreElementName) == 0) + excludePaths = readXmlStringList(node, IgnorePathName, IgnorePathNameAttrib); + else if (strcmp(node->Name(), LibrariesElementName) == 0) + guiProject.libraries = readXmlStringList(node, LibraryElementName, nullptr); + else if (strcmp(node->Name(), SuppressionsElementName) == 0) { + for (const std::string &s : readXmlStringList(node, SuppressionElementName, nullptr)) { + temp.nomsg.addSuppressionLine(s); + } + } else if (strcmp(node->Name(), PlatformElementName) == 0) + guiProject.platform = node->GetText(); + else if (strcmp(node->Name(), AnalyzeAllVsConfigsElementName) == 0) + ; // FIXME: Write some warning + else if (strcmp(node->Name(), AddonsElementName) == 0) + node->Attribute(AddonElementName); // FIXME: Handle addons + else if (strcmp(node->Name(), TagsElementName) == 0) + node->Attribute(TagElementName); // FIXME: Write some warning + else if (strcmp(node->Name(), ToolsElementName) == 0) + node->Attribute(ToolElementName); // FIXME: Write some warning + else + return false; + } + settings->basePaths = temp.basePaths; + settings->buildDir = temp.buildDir; + settings->includePaths = temp.includePaths; + settings->userDefines = temp.userDefines; + settings->userUndefs = temp.userUndefs; + for (const std::string &path : paths) + guiProject.pathNames.push_back(path); + settings->project.ignorePaths(std::vector(excludePaths.begin(), excludePaths.end())); + return true; +} diff --git a/lib/importproject.h b/lib/importproject.h index 8562e7861..de07b9102 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -42,18 +42,21 @@ namespace cppcheck { }; } +class Settings; + /** * @brief Importing project settings. */ class CPPCHECKLIB ImportProject { public: - enum Type { + enum class Type { UNKNOWN, MISSING, COMPILE_DB, VS_SLN, VS_VCXPROJ, - BORLAND + BORLAND, + CPPCHECK_GUI }; /** File settings. Multiple configurations for a file is allowed. */ @@ -79,13 +82,22 @@ public: }; std::list fileSettings; + // Cppcheck GUI output + struct { + std::vector pathNames; + std::list libraries; + std::string projectFile; + std::string platform; + } guiProject; + void ignorePaths(const std::vector &ipaths); void ignoreOtherConfigs(const std::string &cfg); void ignoreOtherPlatforms(cppcheck::Platform::PlatformType platformType); - Type import(const std::string &filename); + Type import(const std::string &filename, Settings *settings); protected: void importCompileCommands(std::istream &istr); + bool importCppcheckGuiProject(std::istream &istr, Settings *settings); private: void importSln(std::istream &istr, const std::string &path); void importVcxproj(const std::string &filename, std::map &variables, const std::string &additionalIncludeDirectories); diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index 60a026c6b..3ade4dedd 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -17,6 +17,7 @@ */ #include "importproject.h" +#include "settings.h" #include "testsuite.h" #include @@ -25,9 +26,8 @@ class TestImporter : public ImportProject { public: - void importCompileCommands(std::istream &istr) { - ImportProject::importCompileCommands(istr); - } + using ImportProject::importCompileCommands; + using ImportProject::importCppcheckGuiProject; }; @@ -47,6 +47,7 @@ private: TEST_CASE(importCompileCommands2); // #8563 TEST_CASE(importCompileCommands3); // check with existing trailing / in directory TEST_CASE(importCompileCommands4); // only accept certain file types + TEST_CASE(importCppcheckGuiProject); } void setDefines() const { @@ -136,6 +137,33 @@ private: importer.importCompileCommands(istr); ASSERT_EQUALS(0, importer.fileSettings.size()); } + + void importCppcheckGuiProject() const { + const char xml[] = "\n" + "\n" + " \n" + " out1\n" + " true\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + std::istringstream istr(xml); + Settings s; + TestImporter project; + std::vector pathNames; + ASSERT_EQUALS(true, project.importCppcheckGuiProject(istr, &s)); + ASSERT_EQUALS(1, project.guiProject.pathNames.size()); + ASSERT_EQUALS("cli/", project.guiProject.pathNames[0]); + ASSERT_EQUALS(1, s.includePaths.size()); + ASSERT_EQUALS("lib/", s.includePaths.front()); + } }; REGISTER_TEST(TestImportProject)