From ae069b5747f892436daa16cfe991d53e8680f0b3 Mon Sep 17 00:00:00 2001 From: Robert Reif Date: Thu, 30 Jul 2026 16:19:10 -0400 Subject: [PATCH] add support for vcxproj ExcludedFromBuild- #8759 --- lib/importproject.cpp | 69 +++++++++++++++++------ test/cli/exclude/DebugX64.cpp | 8 +++ test/cli/exclude/ReleaseX64.cpp | 8 +++ test/cli/exclude/exclude.cppcheck | 16 ++++++ test/cli/exclude/exclude.slnx | 6 ++ test/cli/exclude/exclude.vcxproj | 94 +++++++++++++++++++++++++++++++ test/cli/exclude/foo.h | 1 + test/cli/exclude_test.py | 20 +++++++ 8 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 test/cli/exclude/DebugX64.cpp create mode 100644 test/cli/exclude/ReleaseX64.cpp create mode 100644 test/cli/exclude/exclude.cppcheck create mode 100644 test/cli/exclude/exclude.slnx create mode 100644 test/cli/exclude/exclude.vcxproj create mode 100644 test/cli/exclude/foo.h create mode 100644 test/cli/exclude_test.py diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 242c1719ba9..dcd521a4ae5 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -617,12 +617,13 @@ namespace { std::string platformStr; }; - struct ConditionalGroup { - explicit ConditionalGroup(const tinyxml2::XMLElement *idg){ + struct Conditional { + explicit Conditional(const tinyxml2::XMLElement *idg){ const char *condAttr = idg->Attribute("Condition"); if (condAttr) mCondition = condAttr; } + explicit Conditional(const std::string &condition) : mCondition(condition) {} static void replaceAll(std::string &c, const std::string &from, const std::string &to) { std::string::size_type pos; @@ -751,8 +752,8 @@ namespace { std::string mCondition; }; - struct ItemDefinitionGroup : ConditionalGroup { - explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : ConditionalGroup(idg), additionalIncludePaths(std::move(includePaths)) { + struct ItemDefinitionGroup : Conditional { + explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : Conditional(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) { @@ -802,8 +803,8 @@ namespace { Standards::cppstd_t cppstd = Standards::CPPLatest; }; - struct ConfigurationPropertyGroup : ConditionalGroup { - explicit ConfigurationPropertyGroup(const tinyxml2::XMLElement *idg) : ConditionalGroup(idg) { + struct ConfigurationPropertyGroup : Conditional { + explicit ConfigurationPropertyGroup(const tinyxml2::XMLElement *idg) : Conditional(idg) { for (const tinyxml2::XMLElement *e = idg->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "UseOfMfc") == 0) { useOfMfc = true; @@ -816,6 +817,37 @@ namespace { bool useOfMfc = false; bool useUnicode = false; }; + + struct ItemGroupClCompile { + explicit ItemGroupClCompile(std::string filename) : mFilename(std::move(filename)) {} + ItemGroupClCompile(const tinyxml2::XMLElement *element, std::string file) : mFilename(std::move(file)) { + for (const tinyxml2::XMLElement* childElement = element->FirstChildElement(); childElement; childElement = childElement->NextSiblingElement()) { + const char *name = childElement->Name(); + if (!name) + continue; + if (std::strcmp(name, "ExcludedFromBuild") == 0) { + const char *condition = childElement->Attribute("Condition"); + const char *text = childElement->GetText(); + if (!condition || !text || std::strcmp(text, "true") != 0) + continue; + mConditions.emplace_back(condition); + } + // TODO: ForcedIncludeFiles and PrecompiledHeaderFile + } + } + bool exclude(const ProjectConfiguration& p, std::vector& errors) const { + if (mConditions.empty()) + return false; + for (const std::string& condition : mConditions) { + Conditional conditional(condition); + if (conditional.conditionIsTrue(p, mFilename, errors)) + return true; + } + return false; + } + std::string mFilename; + std::list mConditions; + }; } static std::list toStringList(const std::string &s) @@ -923,7 +955,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename)); std::list projectConfigurationList; - std::list compileList; + std::list compileList; std::list itemDefinitionGroupList; std::vector configurationPropertyGroups; std::string includePath; @@ -954,7 +986,8 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X const char *include = e->Attribute("Include"); if (include && Path::acceptFile(include)) { std::string toInclude = Path::simplifyPath(Path::isAbsolute(include) ? include : Path::getPathFromFilename(filename) + include); - compileList.emplace_back(toInclude); + findAndReplace(toInclude, "$(MSBuildThisFileDirectory)", "./"); + compileList.emplace_back(e, toInclude); } } } @@ -1016,7 +1049,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X 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)); + compileList.emplace_back(pathToFile); } for (const auto &p : sharedProject.includePaths) { std::string path = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + p); @@ -1026,8 +1059,8 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X // Project files PathMatch filtermatcher(fileFilters, Path::getCurrentPath()); - for (const std::string &cfilename : compileList) { - if (!fileFilters.empty() && !filtermatcher.match(cfilename)) + for (const ItemGroupClCompile& compile : compileList) { + if (!fileFilters.empty() && !filtermatcher.match(compile.mFilename)) continue; for (const ProjectConfiguration &p : projectConfigurationList) { @@ -1040,7 +1073,11 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X continue; } - FileSettings fs{cfilename, Standards::Language::None, 0}; // file will be identified later on + // check if the file should be excluded for this configuration + if (compile.exclude(p, errors)) + continue; + + FileSettings fs{ compile.mFilename, Standards::Language::None, 0}; // file will be identified later on fs.cfg = p.name; // TODO: detect actual MSC version fs.msc = true; @@ -1053,7 +1090,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X } std::string additionalIncludePaths; for (const ItemDefinitionGroup &i : itemDefinitionGroupList) { - if (!i.conditionIsTrue(p, cfilename, errors)) + if (!i.conditionIsTrue(p, compile.mFilename, errors)) continue; fs.standard = Standards::getCPP(i.cppstd); fs.defines += ';' + i.preprocessorDefinitions; @@ -1071,7 +1108,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X } bool useUnicode = false; for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) { - if (!c.conditionIsTrue(p, cfilename, errors)) + if (!c.conditionIsTrue(p, compile.mFilename, errors)) continue; // in msbuild the last definition wins useUnicode = c.useUnicode; @@ -1081,7 +1118,7 @@ bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::X fs.defines += ";UNICODE=1;_UNICODE=1"; } fsSetDefines(fs, fs.defines); - fsSetIncludePaths(fs, Path::getPathFromFilename(filename), toStringList(includePath + ';' + additionalIncludePaths), variables); + fsSetIncludePaths(fs, Path::getPathFromFilename(compile.mFilename), toStringList(includePath + ';' + additionalIncludePaths), variables); for (const auto &path : sharedItemsIncludePaths) { fs.includePaths.emplace_back(path); } @@ -1754,5 +1791,5 @@ bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, c ProjectConfiguration p; p.configuration = configuration; p.platformStr = platform; - return ConditionalGroup::evalCondition(condition, p); + return Conditional::evalCondition(condition, p); } diff --git a/test/cli/exclude/DebugX64.cpp b/test/cli/exclude/DebugX64.cpp new file mode 100644 index 00000000000..cfb1fce687a --- /dev/null +++ b/test/cli/exclude/DebugX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "DebugX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/exclude/ReleaseX64.cpp b/test/cli/exclude/ReleaseX64.cpp new file mode 100644 index 00000000000..8fa6e6d0f82 --- /dev/null +++ b/test/cli/exclude/ReleaseX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "ReleaseX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/exclude/exclude.cppcheck b/test/cli/exclude/exclude.cppcheck new file mode 100644 index 00000000000..50d7da84486 --- /dev/null +++ b/test/cli/exclude/exclude.cppcheck @@ -0,0 +1,16 @@ + + + exclude-cppcheck-build-dir + exclude.slnx + false + true + true + true + 2 + 100 + + Debug + + + exclude + diff --git a/test/cli/exclude/exclude.slnx b/test/cli/exclude/exclude.slnx new file mode 100644 index 00000000000..837662784ac --- /dev/null +++ b/test/cli/exclude/exclude.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/cli/exclude/exclude.vcxproj b/test/cli/exclude/exclude.vcxproj new file mode 100644 index 00000000000..cc326aaec2f --- /dev/null +++ b/test/cli/exclude/exclude.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {c9d1dca1-d8ff-4c05-9159-f00816645319} + exclude + 10.0 + + + + StaticLibrary + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + true + + + true + + + + + + + + + \ No newline at end of file diff --git a/test/cli/exclude/foo.h b/test/cli/exclude/foo.h new file mode 100644 index 00000000000..5d5f8f0c9e7 --- /dev/null +++ b/test/cli/exclude/foo.h @@ -0,0 +1 @@ +int foo(); diff --git a/test/cli/exclude_test.py b/test/cli/exclude_test.py new file mode 100644 index 00000000000..4f674aac34e --- /dev/null +++ b/test/cli/exclude_test.py @@ -0,0 +1,20 @@ + +# python -m pytest exclude_test.py + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) +__proj_dir = os.path.join(__script_dir, 'exclude') + +def test_exclude(): + args = [ + '--template=cppcheck1', + '--project=exclude/exclude.cppcheck', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + filename = os.path.join('exclude', 'DebugX64.cpp') + assert ret == 0, stdout + assert stderr == '[%s:6]: (error) Division by zero.\n' % filename