diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 336e25744c8..fec2deb734d 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -203,7 +203,8 @@ ImportProject::Type ImportProject::import(const std::string &filename, Settings } } else if (endsWith(filename, ".vcxproj")) { std::map variables; - if (importVcxproj(filename, variables, emptyString, fileFilters)) { + std::vector sharedItemsProjects; + if (importVcxproj(filename, variables, emptyString, fileFilters, sharedItemsProjects)) { setRelativePaths(filename); return ImportProject::Type::VS_VCXPROJ; } @@ -446,7 +447,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const variables["SolutionDir"] = path; bool found = false; - + std::vector sharedItemsProjects; while (std::getline(istr,line)) { if (!startsWith(line,"Project(")) continue; @@ -461,7 +462,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const if (!Path::isAbsolute(vcxproj)) vcxproj = path + vcxproj; vcxproj = Path::fromNativeSeparators(std::move(vcxproj)); - if (!importVcxproj(vcxproj, variables, emptyString, fileFilters)) { + if (!importVcxproj(vcxproj, variables, emptyString, fileFilters, sharedItemsProjects)) { printError("failed to load '" + vcxproj + "' from Visual Studio solution"); return false; } @@ -698,7 +699,7 @@ static void loadVisualStudioProperties(const std::string &props, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters) +bool ImportProject::importVcxproj(const std::string &filename, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache) { variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename)); @@ -706,6 +707,7 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map compileList; std::list itemDefinitionGroupList; std::string includePath; + std::vector sharedItemsProjects; bool useOfMfc = false; @@ -737,8 +739,10 @@ bool ImportProject::importVcxproj(const std::string &filename, std::mapFirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "ClCompile") == 0) { const char *include = e->Attribute("Include"); - if (include && Path::acceptFile(include)) - compileList.emplace_back(include); + if (include && Path::acceptFile(include)) { + std::string toInclude = Path::simplifyPath(Path::isAbsolute(include) ? include : Path::getPathFromFilename(filename) + include); + compileList.emplace_back(toInclude); + } } } } @@ -756,14 +760,54 @@ bool ImportProject::importVcxproj(const std::string &filename, std::mapFirstChildElement(); e; e = e->NextSiblingElement()) { + if (std::strcmp(e->Name(), "Import") == 0) { + const char *projectAttribute = e->Attribute("Project"); + if (projectAttribute) { + // Path to shared items project is relative to current project directory, + // unless the string starts with $(SolutionDir) + std::string pathToSharedItemsFile; + if (std::string(projectAttribute).rfind("$(SolutionDir)", 0) == 0) { + pathToSharedItemsFile = projectAttribute; + } else { + pathToSharedItemsFile = variables["ProjectDir"] + projectAttribute; + } + if (!simplifyPathWithVariables(pathToSharedItemsFile, variables)) { + printError("Could not simplify path to referenced shared items project"); + return false; + } + + SharedItemsProject toAdd = importVcxitems(pathToSharedItemsFile, fileFilters, cache); + if (!toAdd.successful) { + printError("Could not load shared items project \"" + pathToSharedItemsFile + "\" from original path \"" + std::string(projectAttribute) + "\"."); + return false; + } + sharedItemsProjects.emplace_back(toAdd); + } + } + } } } } // # TODO: support signedness of char via /J (and potential XML option for it)? // we can only set it globally but in this context it needs to be treated per file - for (const std::string &c : compileList) { - const std::string cfilename = Path::simplifyPath(Path::isAbsolute(c) ? c : Path::getPathFromFilename(filename) + c); + // Include shared items project files + std::vector sharedItemsIncludePaths; + for (const auto& sharedProject : sharedItemsProjects) { + for (const auto &file : sharedProject.sourceFiles) { + std::string pathToFile = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + file); + compileList.emplace_back(std::move(pathToFile)); + } + for (const auto &p : sharedProject.includePaths) { + std::string path = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + p); + sharedItemsIncludePaths.emplace_back(std::move(path)); + } + } + + // Project files + for (const std::string &cfilename : compileList) { if (!fileFilters.empty() && !matchglobs(fileFilters, cfilename)) continue; @@ -809,6 +853,9 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map& fileFilters, std::vector &cache) +{ + auto isInCacheCheck = [filename](const ImportProject::SharedItemsProject& e) -> bool { + return filename == e.pathToProjectFile; + }; + const auto iterator = std::find_if(cache.begin(), cache.end(), isInCacheCheck); + if (iterator != std::end(cache)) { + return *iterator; + } + + SharedItemsProject result; + result.pathToProjectFile = filename; + + 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 result; + } + const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); + if (rootnode == nullptr) { + printError("Visual Studio project file has no XML root node"); + return result; + } + for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { + if (std::strcmp(node->Name(), "ItemGroup") == 0) { + for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { + if (std::strcmp(e->Name(), "ClCompile") == 0) { + const char* include = e->Attribute("Include"); + if (include && Path::acceptFile(include)) { + std::string file(include); + findAndReplace(file, "$(MSBuildThisFileDirectory)", "./"); + + // Don't include file if it matches the filter + if (!fileFilters.empty() && !matchglobs(fileFilters, file)) + continue; + + result.sourceFiles.emplace_back(file); + } else { + printError("Could not find shared items source file"); + return result; + } + } + } + } else if (std::strcmp(node->Name(), "ItemDefinitionGroup") == 0) { + ItemDefinitionGroup temp(node, ""); + for (const auto& includePath : toStringList(temp.additionalIncludePaths)) { + if (includePath == "%(AdditionalIncludeDirectories)") + continue; + + std::string toAdd(includePath); + findAndReplace(toAdd, "$(MSBuildThisFileDirectory)", "./"); + result.includePaths.emplace_back(toAdd); + } + } + } + + result.successful = true; + cache.emplace_back(result); + return result; +} + bool ImportProject::importBcb6Prj(const std::string &projectFilename) { tinyxml2::XMLDocument doc; diff --git a/lib/importproject.h b/lib/importproject.h index 7382b56eb8d..bf5d3dcf57c 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -100,9 +100,18 @@ class CPPCHECKLIB WARN_UNUSED ImportProject { bool importCompileCommands(std::istream &istr); bool importCppcheckGuiProject(std::istream &istr, Settings *settings); virtual bool sourceFileExists(const std::string &file); + private: + struct SharedItemsProject { + bool successful = false; + std::string pathToProjectFile; + std::vector includePaths; + std::vector sourceFiles; + }; + bool importSln(std::istream &istr, const std::string &path, const std::vector &fileFilters); - bool importVcxproj(const std::string &filename, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &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 importBcb6Prj(const std::string &projectFilename); static void printError(const std::string &message); diff --git a/test/cli/more-projects_test.py b/test/cli/more-projects_test.py index 2830d577abb..7824bb88f5c 100644 --- a/test/cli/more-projects_test.py +++ b/test/cli/more-projects_test.py @@ -843,3 +843,24 @@ def test_compdb_D(tmpdir): assert stdout.splitlines() == out_expected assert stderr.splitlines() == [] assert ret == 0, stdout + + +def test_shared_items_project(tmpdir = ""): + # tmpdir is unused + solutionDir = os.path.join(os.getcwd(), 'shared-items-project') + solutionFile = os.path.join(solutionDir, 'Solution.sln') + + args = [ + '--platform=win64', + '--project={}'.format(solutionFile), + '--project-configuration=Release|x64', + '-j1' + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + lines = stdout.splitlines() + + # Assume no errors, and that shared items code files have been checked as well + assert any('2/2 files checked 100% done' in x for x in lines) + assert stderr == '' diff --git a/test/cli/shared-items-project/Main/Main.vcxproj b/test/cli/shared-items-project/Main/Main.vcxproj new file mode 100644 index 00000000000..17c6fc98aa3 --- /dev/null +++ b/test/cli/shared-items-project/Main/Main.vcxproj @@ -0,0 +1,56 @@ + + + + + Release + x64 + + + + 17.0 + Win32Proj + {074143a3-6080-409a-a181-24e4e468bfd8} + Blub + 10.0 + + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/test/cli/shared-items-project/Main/MainFile.cpp b/test/cli/shared-items-project/Main/MainFile.cpp new file mode 100644 index 00000000000..545a063c426 --- /dev/null +++ b/test/cli/shared-items-project/Main/MainFile.cpp @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + Shared::TestClass test{}; + return 0; +} \ No newline at end of file diff --git a/test/cli/shared-items-project/Shared/Shared.vcxitems b/test/cli/shared-items-project/Shared/Shared.vcxitems new file mode 100644 index 00000000000..6f904c81453 --- /dev/null +++ b/test/cli/shared-items-project/Shared/Shared.vcxitems @@ -0,0 +1,22 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {3633ee6f-e5e8-46fc-87c9-f13a18db966a} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + \ No newline at end of file diff --git a/test/cli/shared-items-project/Shared/TestClass.cpp b/test/cli/shared-items-project/Shared/TestClass.cpp new file mode 100644 index 00000000000..89683fe6689 --- /dev/null +++ b/test/cli/shared-items-project/Shared/TestClass.cpp @@ -0,0 +1,11 @@ +#include "TestClass.h" + +using namespace Shared; + +TestClass::TestClass() +{ +} + +TestClass::~TestClass() +{ +} diff --git a/test/cli/shared-items-project/Shared/TestClass.h b/test/cli/shared-items-project/Shared/TestClass.h new file mode 100644 index 00000000000..da8799606a0 --- /dev/null +++ b/test/cli/shared-items-project/Shared/TestClass.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Shared +{ + class TestClass + { + public: + explicit TestClass(); + virtual ~TestClass(); + }; +} // namespace Shared diff --git a/test/cli/shared-items-project/Solution.sln b/test/cli/shared-items-project/Solution.sln new file mode 100644 index 00000000000..6a4aac0fd2f --- /dev/null +++ b/test/cli/shared-items-project/Solution.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34607.119 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "Shared\Shared.vcxitems", "{3633EE6F-E5E8-46FC-87C9-F13A18DB966A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Main", "Main\Main.vcxproj", "{074143A3-6080-409A-A181-24E4E468BFD8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {074143A3-6080-409A-A181-24E4E468BFD8}.Release|x64.ActiveCfg = Release|x64 + {074143A3-6080-409A-A181-24E4E468BFD8}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {510D1526-E6EE-452F-A697-173A3D4C4E93} + EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Shared\Shared.vcxitems*{074143a3-6080-409a-a181-24e4e468bfd8}*SharedItemsImports = 4 + Shared\Shared.vcxitems*{3633ee6f-e5e8-46fc-87c9-f13a18db966a}*SharedItemsImports = 9 + EndGlobalSection +EndGlobal