diff --git a/Makefile b/Makefile index 2a72e243844..afbd65fd951 100644 --- a/Makefile +++ b/Makefile @@ -767,7 +767,7 @@ test/testfunctions.o: test/testfunctions.cpp lib/addoninfo.h lib/check.h lib/che test/testgarbage.o: test/testgarbage.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testgarbage.cpp -test/testimportproject.o: test/testimportproject.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h test/redirect.h +test/testimportproject.o: test/testimportproject.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h test/fixture.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testimportproject.cpp test/testincompletestatement.o: test/testincompletestatement.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h diff --git a/lib/importproject.cpp b/lib/importproject.cpp index e23503885d0..1af563ed439 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "xml.h" @@ -527,51 +528,11 @@ namespace { std::string platformStr; }; - struct ItemDefinitionGroup { - explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : additionalIncludePaths(std::move(includePaths)) { + struct ConditionalGroup { + explicit ConditionalGroup(const tinyxml2::XMLElement *idg){ const char *condAttr = idg->Attribute("Condition"); if (condAttr) - condition = condAttr; - for (const tinyxml2::XMLElement *e1 = idg->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) { - const char* name = e1->Name(); - if (std::strcmp(name, "ClCompile") == 0) { - enhancedInstructionSet = "StreamingSIMDExtensions2"; - for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { - const char * const text = e->GetText(); - if (!text) - continue; - const char * const ename = e->Name(); - if (std::strcmp(ename, "PreprocessorDefinitions") == 0) - preprocessorDefinitions = text; - else if (std::strcmp(ename, "AdditionalIncludeDirectories") == 0) { - if (!additionalIncludePaths.empty()) - additionalIncludePaths += ';'; - additionalIncludePaths += text; - } else if (std::strcmp(ename, "LanguageStandard") == 0) { - if (std::strcmp(text, "stdcpp14") == 0) - cppstd = Standards::CPP14; - else if (std::strcmp(text, "stdcpp17") == 0) - cppstd = Standards::CPP17; - else if (std::strcmp(text, "stdcpp20") == 0) - cppstd = Standards::CPP20; - else if (std::strcmp(text, "stdcpplatest") == 0) - cppstd = Standards::CPPLatest; - } else if (std::strcmp(ename, "EnableEnhancedInstructionSet") == 0) { - enhancedInstructionSet = text; - } - } - } - else if (std::strcmp(name, "Link") == 0) { - for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { - const char * const text = e->GetText(); - if (!text) - continue; - if (std::strcmp(e->Name(), "EntryPointSymbol") == 0) { - entryPointSymbol = text; - } - } - } - } + mCondition = condAttr; } static void replaceAll(std::string &c, const std::string &from, const std::string &to) { @@ -585,9 +546,9 @@ namespace { // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions // properties are .NET String objects and you can call any of its members on them bool conditionIsTrue(const ProjectConfiguration &p) const { - if (condition.empty()) + if (mCondition.empty()) return true; - std::string c = '(' + condition + ");"; + std::string c = '(' + mCondition + ");"; replaceAll(c, "$(Configuration)", p.configuration); replaceAll(c, "$(Platform)", p.platformStr); @@ -623,13 +584,75 @@ namespace { } return false; } - std::string condition; + private: + std::string mCondition; + }; + + struct ItemDefinitionGroup : ConditionalGroup { + explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : ConditionalGroup(idg), additionalIncludePaths(std::move(includePaths)) { + for (const tinyxml2::XMLElement *e1 = idg->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) { + const char* name = e1->Name(); + if (std::strcmp(name, "ClCompile") == 0) { + enhancedInstructionSet = "StreamingSIMDExtensions2"; + for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { + const char * const text = e->GetText(); + if (!text) + continue; + const char * const ename = e->Name(); + if (std::strcmp(ename, "PreprocessorDefinitions") == 0) + preprocessorDefinitions = text; + else if (std::strcmp(ename, "AdditionalIncludeDirectories") == 0) { + if (!additionalIncludePaths.empty()) + additionalIncludePaths += ';'; + additionalIncludePaths += text; + } else if (std::strcmp(ename, "LanguageStandard") == 0) { + if (std::strcmp(text, "stdcpp14") == 0) + cppstd = Standards::CPP14; + else if (std::strcmp(text, "stdcpp17") == 0) + cppstd = Standards::CPP17; + else if (std::strcmp(text, "stdcpp20") == 0) + cppstd = Standards::CPP20; + else if (std::strcmp(text, "stdcpplatest") == 0) + cppstd = Standards::CPPLatest; + } else if (std::strcmp(ename, "EnableEnhancedInstructionSet") == 0) { + enhancedInstructionSet = text; + } + } + } + else if (std::strcmp(name, "Link") == 0) { + for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { + const char * const text = e->GetText(); + if (!text) + continue; + if (std::strcmp(e->Name(), "EntryPointSymbol") == 0) { + entryPointSymbol = text; + } + } + } + } + } + std::string enhancedInstructionSet; std::string preprocessorDefinitions; std::string additionalIncludePaths; std::string entryPointSymbol; // TODO: use this Standards::cppstd_t cppstd = Standards::CPPLatest; }; + + struct ConfigurationPropertyGroup : ConditionalGroup { + explicit ConfigurationPropertyGroup(const tinyxml2::XMLElement *idg) : ConditionalGroup(idg) { + for (const tinyxml2::XMLElement *e = idg->FirstChildElement(); e; e = e->NextSiblingElement()) { + if (std::strcmp(e->Name(), "UseOfMfc") == 0) { + useOfMfc = true; + } else if (std::strcmp(e->Name(), "CharacterSet") == 0) { + useUnicode = std::strcmp(e->GetText(), "Unicode") == 0; + } + } + } + + bool useOfMfc = false; + bool useUnicode = false; + }; } static std::list toStringList(const std::string &s) @@ -648,17 +671,8 @@ static std::list toStringList(const std::string &s) return ret; } -static void importPropertyGroup(const tinyxml2::XMLElement *node, std::map &variables, std::string &includePath, bool *useOfMfc) +static void importPropertyGroup(const tinyxml2::XMLElement *node, std::map &variables, std::string &includePath) { - if (useOfMfc) { - for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "UseOfMfc") == 0) { - *useOfMfc = true; - break; - } - } - } - const char* labelAttribute = node->Attribute("Label"); if (labelAttribute && std::strcmp(labelAttribute, "UserMacros") == 0) { for (const tinyxml2::XMLElement *propertyGroup = node->FirstChildElement(); propertyGroup; propertyGroup = propertyGroup->NextSiblingElement()) { @@ -719,31 +733,39 @@ static void loadVisualStudioProperties(const std::string &props, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache) +bool ImportProject::importVcxproj(const std::string &filename, + std::map &variables, + const std::string &additionalIncludeDirectories, + const std::vector &fileFilters, + std::vector &cache) +{ + tinyxml2::XMLDocument doc; + const tinyxml2::XMLError error = doc.LoadFile(filename.c_str()); + if (error != tinyxml2::XML_SUCCESS) { + printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error)); + return false; + } + return importVcxproj(filename, doc, variables, additionalIncludeDirectories, fileFilters, cache); +} + +bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache) { variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename)); std::list projectConfigurationList; std::list compileList; std::list itemDefinitionGroupList; + std::vector configurationPropertyGroups; std::string includePath; std::vector sharedItemsProjects; - bool useOfMfc = false; - - tinyxml2::XMLDocument doc; - const tinyxml2::XMLError error = doc.LoadFile(filename.c_str()); - if (error != tinyxml2::XML_SUCCESS) { - printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error)); - return false; - } const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); if (rootnode == nullptr) { printError("Visual Studio project file has no XML root node"); @@ -777,7 +799,12 @@ bool ImportProject::importVcxproj(const std::string &filename, std::mapAttribute("Label"); + if (labelAttribute && std::strcmp(labelAttribute, "Configuration") == 0) { + configurationPropertyGroups.emplace_back(node); + } else { + importPropertyGroup(node, variables, includePath); + } } else if (std::strcmp(name, "ImportGroup") == 0) { const char *labelAttribute = node->Attribute("Label"); if (labelAttribute && std::strcmp(labelAttribute, "PropertySheets") == 0) { @@ -853,7 +880,6 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map &fileFilters); static SharedItemsProject importVcxitems(const std::string &filename, const std::vector &fileFilters, std::vector &cache); bool importVcxproj(const std::string &filename, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache); + bool importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache); bool importBcb6Prj(const std::string &projectFilename); static void printError(const std::string &message); diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index c134a17fcee..1cc14f2d2c7 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -22,6 +22,7 @@ #include "redirect.h" #include "settings.h" #include "suppressions.h" +#include "xml.h" #include #include @@ -34,6 +35,8 @@ class TestImporter : public ImportProject { public: using ImportProject::importCompileCommands; using ImportProject::importCppcheckGuiProject; + using ImportProject::importVcxproj; + using ImportProject::SharedItemsProject; bool sourceFileExists(const std::string & /*file*/) override { return true; @@ -71,6 +74,7 @@ class TestImportProject : public TestFixture { TEST_CASE(importCompileCommandsDirectoryInvalid); // 'directory' field not a string TEST_CASE(importCppcheckGuiProject); TEST_CASE(ignorePaths); + TEST_CASE(testVcxprojUnicode); } void setDefines() const { @@ -455,6 +459,59 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS(0, project.fileSettings.size()); } + void testVcxprojUnicode() const + { + const char vcxproj[] = R"-( + + + + + Debug + Win32 + + + Release + Win32 + + + + + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + NotSet + Static + + + + + +)-"; + tinyxml2::XMLDocument doc; + ASSERT_EQUALS(tinyxml2::XML_SUCCESS, doc.Parse(vcxproj, sizeof(vcxproj))); + TestImporter project; + std::map variables; + std::vector cache; + ASSERT_EQUALS(project.importVcxproj("test.vcxproj", doc, variables, {}, {}, cache), true); + ASSERT_EQUALS(project.fileSettings.size(), 2); + ASSERT(project.fileSettings.front().defines.find(";UNICODE=1;") != std::string::npos); + ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1") != std::string::npos); + ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1;") == std::string::npos); // No duplicates + ASSERT_EQUALS(project.fileSettings.front().useMfc, false); + ASSERT(project.fileSettings.back().defines.find(";UNICODE=1;") == std::string::npos); + ASSERT(project.fileSettings.back().defines.find(";_UNICODE=1") == std::string::npos); + ASSERT_EQUALS(project.fileSettings.back().useMfc, true); + } + // TODO: test fsParseCommand() // TODO: test vcxproj conditions