Skip to content

Commit af5b6ef

Browse files
DevFelixFaberFelix Faber
andauthored
Added Support for Shared Items Projects (#6286)
At work (Areus Gmbh), we use Shared Items Projects in order to deduplicate code. In contrast to conventional project files (`.vcxproj`), the compilation configuration is set by the "includee", which makes the code-reuse of the project more flexible. This pull-request adds support for Shared Items Projects (`.vcxitems`) in Visual studio projects / solutions. The idea is that for each `.vcxproj`, we check for referenced shared items projects. When all the source files and include paths are build, we add the ones defined in the referenced `.vcxitems`. The implementation probably isn't 100% robust, but this works for our projects and could serve as a starting point :) I apologize for the commit history - it's a bit wonky as we currently still use 2.11. I tried to merge the changes to the main branch so other could benefit from these changes as well, but seems like the git history looks a bit weird as a result. --------- Co-authored-by: Felix Faber <felix.faber@areus.de>
1 parent 711ea51 commit af5b6ef

9 files changed

Lines changed: 283 additions & 9 deletions

File tree

lib/importproject.cpp

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ ImportProject::Type ImportProject::import(const std::string &filename, Settings
203203
}
204204
} else if (endsWith(filename, ".vcxproj")) {
205205
std::map<std::string, std::string, cppcheck::stricmp> variables;
206-
if (importVcxproj(filename, variables, emptyString, fileFilters)) {
206+
std::vector<SharedItemsProject> sharedItemsProjects;
207+
if (importVcxproj(filename, variables, emptyString, fileFilters, sharedItemsProjects)) {
207208
setRelativePaths(filename);
208209
return ImportProject::Type::VS_VCXPROJ;
209210
}
@@ -446,7 +447,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
446447
variables["SolutionDir"] = path;
447448

448449
bool found = false;
449-
450+
std::vector<SharedItemsProject> sharedItemsProjects;
450451
while (std::getline(istr,line)) {
451452
if (!startsWith(line,"Project("))
452453
continue;
@@ -461,7 +462,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
461462
if (!Path::isAbsolute(vcxproj))
462463
vcxproj = path + vcxproj;
463464
vcxproj = Path::fromNativeSeparators(std::move(vcxproj));
464-
if (!importVcxproj(vcxproj, variables, emptyString, fileFilters)) {
465+
if (!importVcxproj(vcxproj, variables, emptyString, fileFilters, sharedItemsProjects)) {
465466
printError("failed to load '" + vcxproj + "' from Visual Studio solution");
466467
return false;
467468
}
@@ -698,14 +699,15 @@ static void loadVisualStudioProperties(const std::string &props, std::map<std::s
698699
}
699700
}
700701

701-
bool ImportProject::importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters)
702+
bool ImportProject::importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache)
702703
{
703704
variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename));
704705

705706
std::list<ProjectConfiguration> projectConfigurationList;
706707
std::list<std::string> compileList;
707708
std::list<ItemDefinitionGroup> itemDefinitionGroupList;
708709
std::string includePath;
710+
std::vector<SharedItemsProject> sharedItemsProjects;
709711

710712
bool useOfMfc = false;
711713

@@ -737,8 +739,10 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
737739
for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) {
738740
if (std::strcmp(e->Name(), "ClCompile") == 0) {
739741
const char *include = e->Attribute("Include");
740-
if (include && Path::acceptFile(include))
741-
compileList.emplace_back(include);
742+
if (include && Path::acceptFile(include)) {
743+
std::string toInclude = Path::simplifyPath(Path::isAbsolute(include) ? include : Path::getPathFromFilename(filename) + include);
744+
compileList.emplace_back(toInclude);
745+
}
742746
}
743747
}
744748
}
@@ -756,14 +760,54 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
756760
loadVisualStudioProperties(projectAttribute, variables, includePath, additionalIncludeDirectories, itemDefinitionGroupList);
757761
}
758762
}
763+
} else if (labelAttribute && std::strcmp(labelAttribute, "Shared") == 0) {
764+
for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) {
765+
if (std::strcmp(e->Name(), "Import") == 0) {
766+
const char *projectAttribute = e->Attribute("Project");
767+
if (projectAttribute) {
768+
// Path to shared items project is relative to current project directory,
769+
// unless the string starts with $(SolutionDir)
770+
std::string pathToSharedItemsFile;
771+
if (std::string(projectAttribute).rfind("$(SolutionDir)", 0) == 0) {
772+
pathToSharedItemsFile = projectAttribute;
773+
} else {
774+
pathToSharedItemsFile = variables["ProjectDir"] + projectAttribute;
775+
}
776+
if (!simplifyPathWithVariables(pathToSharedItemsFile, variables)) {
777+
printError("Could not simplify path to referenced shared items project");
778+
return false;
779+
}
780+
781+
SharedItemsProject toAdd = importVcxitems(pathToSharedItemsFile, fileFilters, cache);
782+
if (!toAdd.successful) {
783+
printError("Could not load shared items project \"" + pathToSharedItemsFile + "\" from original path \"" + std::string(projectAttribute) + "\".");
784+
return false;
785+
}
786+
sharedItemsProjects.emplace_back(toAdd);
787+
}
788+
}
789+
}
759790
}
760791
}
761792
}
762793
// # TODO: support signedness of char via /J (and potential XML option for it)?
763794
// we can only set it globally but in this context it needs to be treated per file
764795

765-
for (const std::string &c : compileList) {
766-
const std::string cfilename = Path::simplifyPath(Path::isAbsolute(c) ? c : Path::getPathFromFilename(filename) + c);
796+
// Include shared items project files
797+
std::vector<std::string> sharedItemsIncludePaths;
798+
for (const auto& sharedProject : sharedItemsProjects) {
799+
for (const auto &file : sharedProject.sourceFiles) {
800+
std::string pathToFile = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + file);
801+
compileList.emplace_back(std::move(pathToFile));
802+
}
803+
for (const auto &p : sharedProject.includePaths) {
804+
std::string path = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + p);
805+
sharedItemsIncludePaths.emplace_back(std::move(path));
806+
}
807+
}
808+
809+
// Project files
810+
for (const std::string &cfilename : compileList) {
767811
if (!fileFilters.empty() && !matchglobs(fileFilters, cfilename))
768812
continue;
769813

@@ -809,13 +853,78 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
809853
}
810854
fsSetDefines(fs, fs.defines);
811855
fsSetIncludePaths(fs, Path::getPathFromFilename(filename), toStringList(includePath + ';' + additionalIncludePaths), variables);
856+
for (const auto &path : sharedItemsIncludePaths) {
857+
fs.includePaths.emplace_back(path);
858+
}
812859
fileSettings.push_back(std::move(fs));
813860
}
814861
}
815862

816863
return true;
817864
}
818865

866+
ImportProject::SharedItemsProject ImportProject::importVcxitems(const std::string& filename, const std::vector<std::string>& fileFilters, std::vector<SharedItemsProject> &cache)
867+
{
868+
auto isInCacheCheck = [filename](const ImportProject::SharedItemsProject& e) -> bool {
869+
return filename == e.pathToProjectFile;
870+
};
871+
const auto iterator = std::find_if(cache.begin(), cache.end(), isInCacheCheck);
872+
if (iterator != std::end(cache)) {
873+
return *iterator;
874+
}
875+
876+
SharedItemsProject result;
877+
result.pathToProjectFile = filename;
878+
879+
tinyxml2::XMLDocument doc;
880+
const tinyxml2::XMLError error = doc.LoadFile(filename.c_str());
881+
if (error != tinyxml2::XML_SUCCESS) {
882+
printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
883+
return result;
884+
}
885+
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
886+
if (rootnode == nullptr) {
887+
printError("Visual Studio project file has no XML root node");
888+
return result;
889+
}
890+
for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) {
891+
if (std::strcmp(node->Name(), "ItemGroup") == 0) {
892+
for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) {
893+
if (std::strcmp(e->Name(), "ClCompile") == 0) {
894+
const char* include = e->Attribute("Include");
895+
if (include && Path::acceptFile(include)) {
896+
std::string file(include);
897+
findAndReplace(file, "$(MSBuildThisFileDirectory)", "./");
898+
899+
// Don't include file if it matches the filter
900+
if (!fileFilters.empty() && !matchglobs(fileFilters, file))
901+
continue;
902+
903+
result.sourceFiles.emplace_back(file);
904+
} else {
905+
printError("Could not find shared items source file");
906+
return result;
907+
}
908+
}
909+
}
910+
} else if (std::strcmp(node->Name(), "ItemDefinitionGroup") == 0) {
911+
ItemDefinitionGroup temp(node, "");
912+
for (const auto& includePath : toStringList(temp.additionalIncludePaths)) {
913+
if (includePath == "%(AdditionalIncludeDirectories)")
914+
continue;
915+
916+
std::string toAdd(includePath);
917+
findAndReplace(toAdd, "$(MSBuildThisFileDirectory)", "./");
918+
result.includePaths.emplace_back(toAdd);
919+
}
920+
}
921+
}
922+
923+
result.successful = true;
924+
cache.emplace_back(result);
925+
return result;
926+
}
927+
819928
bool ImportProject::importBcb6Prj(const std::string &projectFilename)
820929
{
821930
tinyxml2::XMLDocument doc;

lib/importproject.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,18 @@ class CPPCHECKLIB WARN_UNUSED ImportProject {
100100
bool importCompileCommands(std::istream &istr);
101101
bool importCppcheckGuiProject(std::istream &istr, Settings *settings);
102102
virtual bool sourceFileExists(const std::string &file);
103+
103104
private:
105+
struct SharedItemsProject {
106+
bool successful = false;
107+
std::string pathToProjectFile;
108+
std::vector<std::string> includePaths;
109+
std::vector<std::string> sourceFiles;
110+
};
111+
104112
bool importSln(std::istream &istr, const std::string &path, const std::vector<std::string> &fileFilters);
105-
bool importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters);
113+
static SharedItemsProject importVcxitems(const std::string &filename, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
114+
bool importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
106115
bool importBcb6Prj(const std::string &projectFilename);
107116

108117
static void printError(const std::string &message);

test/cli/more-projects_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,3 +843,24 @@ def test_compdb_D(tmpdir):
843843
assert stdout.splitlines() == out_expected
844844
assert stderr.splitlines() == []
845845
assert ret == 0, stdout
846+
847+
848+
def test_shared_items_project(tmpdir = ""):
849+
# tmpdir is unused
850+
solutionDir = os.path.join(os.getcwd(), 'shared-items-project')
851+
solutionFile = os.path.join(solutionDir, 'Solution.sln')
852+
853+
args = [
854+
'--platform=win64',
855+
'--project={}'.format(solutionFile),
856+
'--project-configuration=Release|x64',
857+
'-j1'
858+
]
859+
860+
exitcode, stdout, stderr = cppcheck(args)
861+
assert exitcode == 0
862+
lines = stdout.splitlines()
863+
864+
# Assume no errors, and that shared items code files have been checked as well
865+
assert any('2/2 files checked 100% done' in x for x in lines)
866+
assert stderr == ''
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<ItemGroup Label="ProjectConfigurations">
4+
<ProjectConfiguration Include="Release|x64">
5+
<Configuration>Release</Configuration>
6+
<Platform>x64</Platform>
7+
</ProjectConfiguration>
8+
</ItemGroup>
9+
<PropertyGroup Label="Globals">
10+
<VCProjectVersion>17.0</VCProjectVersion>
11+
<Keyword>Win32Proj</Keyword>
12+
<ProjectGuid>{074143a3-6080-409a-a181-24e4e468bfd8}</ProjectGuid>
13+
<RootNamespace>Blub</RootNamespace>
14+
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
15+
</PropertyGroup>
16+
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
17+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
18+
<ConfigurationType>Application</ConfigurationType>
19+
<UseDebugLibraries>false</UseDebugLibraries>
20+
<PlatformToolset>v143</PlatformToolset>
21+
<WholeProgramOptimization>true</WholeProgramOptimization>
22+
<CharacterSet>Unicode</CharacterSet>
23+
</PropertyGroup>
24+
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
25+
<ImportGroup Label="ExtensionSettings">
26+
</ImportGroup>
27+
<ImportGroup Label="Shared">
28+
<Import Project="$(SolutionDir)\Shared\Shared.vcxitems" Label="Shared" />
29+
</ImportGroup>
30+
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
31+
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
32+
</ImportGroup>
33+
<PropertyGroup Label="UserMacros" />
34+
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
35+
<ClCompile>
36+
<WarningLevel>Level3</WarningLevel>
37+
<FunctionLevelLinking>true</FunctionLevelLinking>
38+
<IntrinsicFunctions>true</IntrinsicFunctions>
39+
<SDLCheck>true</SDLCheck>
40+
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
41+
<ConformanceMode>true</ConformanceMode>
42+
</ClCompile>
43+
<Link>
44+
<SubSystem>Console</SubSystem>
45+
<EnableCOMDATFolding>true</EnableCOMDATFolding>
46+
<OptimizeReferences>true</OptimizeReferences>
47+
<GenerateDebugInformation>true</GenerateDebugInformation>
48+
</Link>
49+
</ItemDefinitionGroup>
50+
<ItemGroup>
51+
<ClCompile Include="MainFile.cpp" />
52+
</ItemGroup>
53+
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
54+
<ImportGroup Label="ExtensionTargets">
55+
</ImportGroup>
56+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include <TestClass.h>
2+
3+
int main(void)
4+
{
5+
Shared::TestClass test{};
6+
return 0;
7+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup Label="Globals">
4+
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<HasSharedItems>true</HasSharedItems>
6+
<ItemsProjectGuid>{3633ee6f-e5e8-46fc-87c9-f13a18db966a}</ItemsProjectGuid>
7+
</PropertyGroup>
8+
<ItemDefinitionGroup>
9+
<ClCompile>
10+
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
11+
</ClCompile>
12+
</ItemDefinitionGroup>
13+
<ItemGroup>
14+
<ProjectCapability Include="SourceItemsFromImports" />
15+
</ItemGroup>
16+
<ItemGroup>
17+
<ClInclude Include="$(MSBuildThisFileDirectory)TestClass.h" />
18+
</ItemGroup>
19+
<ItemGroup>
20+
<ClCompile Include="$(MSBuildThisFileDirectory)TestClass.cpp" />
21+
</ItemGroup>
22+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "TestClass.h"
2+
3+
using namespace Shared;
4+
5+
TestClass::TestClass()
6+
{
7+
}
8+
9+
TestClass::~TestClass()
10+
{
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
namespace Shared
4+
{
5+
class TestClass
6+
{
7+
public:
8+
explicit TestClass();
9+
virtual ~TestClass();
10+
};
11+
} // namespace Shared
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.9.34607.119
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "Shared\Shared.vcxitems", "{3633EE6F-E5E8-46FC-87C9-F13A18DB966A}"
7+
EndProject
8+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Main", "Main\Main.vcxproj", "{074143A3-6080-409A-A181-24E4E468BFD8}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Release|x64 = Release|x64
13+
EndGlobalSection
14+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
15+
{074143A3-6080-409A-A181-24E4E468BFD8}.Release|x64.ActiveCfg = Release|x64
16+
{074143A3-6080-409A-A181-24E4E468BFD8}.Release|x64.Build.0 = Release|x64
17+
EndGlobalSection
18+
GlobalSection(SolutionProperties) = preSolution
19+
HideSolutionNode = FALSE
20+
EndGlobalSection
21+
GlobalSection(ExtensibilityGlobals) = postSolution
22+
SolutionGuid = {510D1526-E6EE-452F-A697-173A3D4C4E93}
23+
EndGlobalSection
24+
GlobalSection(SharedMSBuildProjectFiles) = preSolution
25+
Shared\Shared.vcxitems*{074143a3-6080-409a-a181-24e4e468bfd8}*SharedItemsImports = 4
26+
Shared\Shared.vcxitems*{3633ee6f-e5e8-46fc-87c9-f13a18db966a}*SharedItemsImports = 9
27+
EndGlobalSection
28+
EndGlobal

0 commit comments

Comments
 (0)