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)