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.
This commit is contained in:
Daniel Marjamäki 2019-01-31 20:40:15 +01:00
parent 05a3e6807b
commit 961f66baff
4 changed files with 221 additions and 17 deletions

View File

@ -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;
}

View File

@ -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<std::string, std::string, cppcheck::stricmp> 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<std::string> readXmlStringList(const tinyxml2::XMLElement *node, const char name[], const char attribute[])
{
std::list<std::string> 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<std::string> &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<char>(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<std::string> paths;
std::list<std::string> 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<std::string>(excludePaths.begin(), excludePaths.end()));
return true;
}

View File

@ -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> fileSettings;
// Cppcheck GUI output
struct {
std::vector<std::string> pathNames;
std::list<std::string> libraries;
std::string projectFile;
std::string platform;
} guiProject;
void ignorePaths(const std::vector<std::string> &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<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories);

View File

@ -17,6 +17,7 @@
*/
#include "importproject.h"
#include "settings.h"
#include "testsuite.h"
#include <list>
@ -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[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<project version=\"1\">\n"
" <root name=\".\"/>\n"
" <builddir>out1</builddir>\n"
" <analyze-all-vs-configs>true</analyze-all-vs-configs>\n"
" <includedir>\n"
" <dir name=\"lib/\"/>\n"
" </includedir>\n"
" <paths>\n"
" <dir name=\"cli/\"/>\n"
" </paths>\n"
" <exclude>\n"
" <path name=\"gui/temp/\"/>\n"
" </exclude>\n"
"</project>\n";
std::istringstream istr(xml);
Settings s;
TestImporter project;
std::vector<std::string> 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)