Skip to content

Commit

Permalink
Added Support for Shared Items Projects (#6286)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
DevFelixFaber and Felix Faber authored Aug 27, 2024
1 parent 711ea51 commit af5b6ef
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 9 deletions.
125 changes: 117 additions & 8 deletions lib/importproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ ImportProject::Type ImportProject::import(const std::string &filename, Settings
}
} else if (endsWith(filename, ".vcxproj")) {
std::map<std::string, std::string, cppcheck::stricmp> variables;
if (importVcxproj(filename, variables, emptyString, fileFilters)) {
std::vector<SharedItemsProject> sharedItemsProjects;
if (importVcxproj(filename, variables, emptyString, fileFilters, sharedItemsProjects)) {
setRelativePaths(filename);
return ImportProject::Type::VS_VCXPROJ;
}
Expand Down Expand Up @@ -446,7 +447,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const
variables["SolutionDir"] = path;

bool found = false;

std::vector<SharedItemsProject> sharedItemsProjects;
while (std::getline(istr,line)) {
if (!startsWith(line,"Project("))
continue;
Expand All @@ -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;
}
Expand Down Expand Up @@ -698,14 +699,15 @@ static void loadVisualStudioProperties(const std::string &props, std::map<std::s
}
}

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)
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)
{
variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename));

std::list<ProjectConfiguration> projectConfigurationList;
std::list<std::string> compileList;
std::list<ItemDefinitionGroup> itemDefinitionGroupList;
std::string includePath;
std::vector<SharedItemsProject> sharedItemsProjects;

bool useOfMfc = false;

Expand Down Expand Up @@ -737,8 +739,10 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
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))
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);
}
}
}
}
Expand All @@ -756,14 +760,54 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
loadVisualStudioProperties(projectAttribute, variables, includePath, additionalIncludeDirectories, itemDefinitionGroupList);
}
}
} else if (labelAttribute && std::strcmp(labelAttribute, "Shared") == 0) {
for (const tinyxml2::XMLElement *e = node->FirstChildElement(); 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<std::string> 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;

Expand Down Expand Up @@ -809,13 +853,78 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
}
fsSetDefines(fs, fs.defines);
fsSetIncludePaths(fs, Path::getPathFromFilename(filename), toStringList(includePath + ';' + additionalIncludePaths), variables);
for (const auto &path : sharedItemsIncludePaths) {
fs.includePaths.emplace_back(path);
}
fileSettings.push_back(std::move(fs));
}
}

return true;
}

ImportProject::SharedItemsProject ImportProject::importVcxitems(const std::string& filename, const std::vector<std::string>& fileFilters, std::vector<SharedItemsProject> &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;
Expand Down
11 changes: 10 additions & 1 deletion lib/importproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> includePaths;
std::vector<std::string> sourceFiles;
};

bool importSln(std::istream &istr, const std::string &path, const std::vector<std::string> &fileFilters);
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);
static SharedItemsProject importVcxitems(const std::string &filename, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
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);
bool importBcb6Prj(const std::string &projectFilename);

static void printError(const std::string &message);
Expand Down
21 changes: 21 additions & 0 deletions test/cli/more-projects_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 == ''
56 changes: 56 additions & 0 deletions test/cli/shared-items-project/Main/Main.vcxproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{074143a3-6080-409a-a181-24e4e468bfd8}</ProjectGuid>
<RootNamespace>Blub</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
<Import Project="$(SolutionDir)\Shared\Shared.vcxitems" Label="Shared" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="MainFile.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
7 changes: 7 additions & 0 deletions test/cli/shared-items-project/Main/MainFile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <TestClass.h>

int main(void)
{
Shared::TestClass test{};
return 0;
}
22 changes: 22 additions & 0 deletions test/cli/shared-items-project/Shared/Shared.vcxitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<ItemsProjectGuid>{3633ee6f-e5e8-46fc-87c9-f13a18db966a}</ItemsProjectGuid>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectCapability Include="SourceItemsFromImports" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)TestClass.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)TestClass.cpp" />
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions test/cli/shared-items-project/Shared/TestClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "TestClass.h"

using namespace Shared;

TestClass::TestClass()
{
}

TestClass::~TestClass()
{
}
11 changes: 11 additions & 0 deletions test/cli/shared-items-project/Shared/TestClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

namespace Shared
{
class TestClass
{
public:
explicit TestClass();
virtual ~TestClass();
};
} // namespace Shared
28 changes: 28 additions & 0 deletions test/cli/shared-items-project/Solution.sln
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit af5b6ef

Please sign in to comment.