目标
在上一篇博客《写一个小工具来可视化VisualStudio中项目之间的依赖关系(1.针对sln中的信息)》中,我通过实验的方式知道了.sln
文件中是如何存储依赖关系的,并编写了代码解析它。但问题是之后发现在.vcxproj
中也有依赖信息。
本篇的目标是:
- 解析出
.vcxproj
中的项目依赖关系,与之前从.sln
得到的信息结合,最终得到完整项目依赖图。
分析
经过上一篇的观察,我知道两个关键信息:
(1).vcxproj
的格式是xml
,因此我需要一个读取xml的第三方库来帮助我,这里我选择leethomason/tinyxml2。下载其中的tinyxml2.h
和tinyxml2.cpp
添加入工程。
(2).vcxproj
中ProjectReference
节点将存储了其依赖的项目的信息
代码
代码上不复杂,主要就是遍历文件夹下所有的vcxproj
文件,然后找到对应的节点,进而找到依赖信息。
完整代码如下:
#include <iostream>
#include <vector>
#include <map>
#include <io.h>
#include <algorithm>
#include"tinyxml2/tinyxml2.h"
using namespace std;
using namespace tinyxml2;
//一个项目所对应的信息
struct ProjectInfo
{
//读取到的每一行信息
vector<string> lines;
//项目名字
string Name;
//项目GUID
string GUID;
//所依赖的项目
vector<ProjectInfo* >DependProjects;
};
//从字符串中分割得到信息的辅助函数
vector<string> StringSplitter(string source, char startChar, char endChar)
{
vector<string> result;
string current;
bool working = false;
for (int i = 0; i < source.size(); i++)
{
if (working)
{
if (source[i] == endChar)
{
result.push_back(current);
current.clear();
working = false;
}
else
current.push_back(source[i]);
}
else if (source[i] == startChar)
working = true;
}
return result;
}
//递归地方式找到所有的文件
void GetFilesInFolder_recursion(string folder, vector<string>& files)
{
_finddata_t fileInfo; //储存得到的文件信息
//找第一个
auto findResult = _findfirst((folder + "\\*").c_str(), &fileInfo);
if (findResult == -1)
{
_findclose(findResult);
return;
}
//找之后的
do
{
string fileName = fileInfo.name;
if ((fileInfo.attrib == _A_NORMAL) || (fileInfo.attrib == _A_ARCH)) //是文件
{
files.push_back(folder + "/" + fileName);
}
else if (fileInfo.attrib == _A_SUBDIR)//是文件夹
{
if (fileName == ".") //跳过得到的这个路径
continue;
if (fileName == "..") //跳过得到的这个路径
continue;
GetFilesInFolder_recursion(folder + "/" + fileName, files);
}
} while (_findnext(findResult, &fileInfo) == 0);
_findclose(findResult);
}
//找到一个节点的所有子节点
vector<XMLElement*> FindAllChildrenElement(XMLElement* Parent)
{
vector<XMLElement*> result;
XMLElement* child = Parent->FirstChildElement();
if (child == nullptr)
return result;
while (true)
{
result.push_back(child);
if (child == Parent->LastChildElement())
break;
child = child->NextSiblingElement();
}
return result;
}
int main()
{
//.sln文件的路径
const string slnFile = "D:/000_NewNewWorkSpace/renderdoc/renderdoc.sln";
//.vcxproj所在的文件夹
const string vcxprojFolder = "D:/000_NewNewWorkSpace/renderdoc";
//读取文件
FILE* file;
{
fopen_s(&file, slnFile.c_str(), "r");
if (!file)
{
cout << "打开文件失败" << endl;
return -1;
}
}
//读取所有的项目信息
vector<ProjectInfo> projects;
{
ProjectInfo* CurrentProject = nullptr;//当前正在添加信息的项目
//读取每一行
char buff[1024];
while (fgets(buff, 1024, file) != nullptr)
{
string line = buff;
if (line.find("Project") == 0)//如果找到"Project"且是在开头,则说明是新项目
{
projects.push_back(ProjectInfo());
CurrentProject = &projects[projects.size() - 1];
}
if (CurrentProject != nullptr)//如果当前项目不为空,则添加此行到项目信息中
CurrentProject->lines.push_back(line);
if (line.find("EndProject") == 0)//如果找到"EndProject"且是在开头,则说明项目结束
CurrentProject = nullptr;
}
}
//一个Map用来映射项目的GUID与项目
std::map<string, ProjectInfo*> ProjectGUIDMapping;
//遍历每一个项目找到其名字与编号
for (ProjectInfo& p : projects)
{
//Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "renderdocshim", "renderdocshim\renderdocshim.vcxproj", "{6DEE3F12-F2F8-42CA-865A-578D0FD11387}"
auto SubStrs = StringSplitter(p.lines[0],'\"', '\"');
p.Name = SubStrs[1];
p.GUID = StringSplitter(SubStrs[3], '{', '}')[0];
ProjectGUIDMapping[p.GUID] = &p;
}
//遍历每一个项目找到其依赖的项目
for (ProjectInfo& p : projects)
{
bool DependenciesLines = false;
for (auto line : p.lines)
{
if (DependenciesLines)
{
if (line.find("EndProjectSection") != string::npos)//如果找到"EndProjectSection"则结束写依赖的项目
DependenciesLines = false;
else //这是一条表示依赖的行
{
//依赖的项目的GUID
string DpdProjGUID = StringSplitter(line, '{', '}')[0];
//加入此条依赖信息
if (std::find(p.DependProjects.begin(), p.DependProjects.end(), ProjectGUIDMapping[DpdProjGUID]) == p.DependProjects.end())
p.DependProjects.push_back(ProjectGUIDMapping[DpdProjGUID]);
}
}
if (line.find("ProjectSection(ProjectDependencies) = postProject") != string::npos)//如果找到"ProjectSection(ProjectDependencies) = postProject"则开始写依赖的项目
DependenciesLines = true;
}
}
//遍历找到所有的vcxproj文件
vector<string> vcxprojs;
{
vector<string> files;
GetFilesInFolder_recursion(vcxprojFolder, files);
for (auto f : files)
{
if (f.find(".vcxproj") == (f.size() - string(".vcxproj").size()))//只针对.vcxproj为后缀的
vcxprojs.push_back(f);
}
}
//遍历每一个vcxproj文件
for (auto f : vcxprojs)
{
//从文件中读取
XMLDocument doc;
doc.LoadFile(f.c_str());
//根节点
auto ProjectNode = doc.FirstChildElement("Project");
//找到此项目的GUID
string MyGUID;
for (auto ProjectNode_child : FindAllChildrenElement(ProjectNode))
{
if (string(ProjectNode_child->Name()).compare("PropertyGroup") == 0)
{
auto label = ProjectNode_child->FindAttribute("Label");
if (label && (string(label->Value()).compare("Globals") == 0))
{
MyGUID = StringSplitter(string(ProjectNode_child->FirstChildElement("ProjectGuid")->GetText()), '{', '}')[0];
}
}
}
//找到依赖项
for (auto ProjectNode_child : FindAllChildrenElement(ProjectNode))
{
if (string(ProjectNode_child->Name()).compare("ItemGroup") == 0)
{
for (auto ItemGroup_child : FindAllChildrenElement(ProjectNode_child))
{
if (string(ItemGroup_child->Name()).compare("ProjectReference") == 0)
{
auto DpdProject = ItemGroup_child->FirstChildElement("Project");
if (DpdProject != nullptr)
{
//依赖的项目的GUID
string DpdProjGUID = StringSplitter(string(DpdProject->GetText()), '{', '}')[0];
//变为大写
std::transform(DpdProjGUID.begin(), DpdProjGUID.end(), DpdProjGUID.begin(), ::toupper);
//加入此条依赖信息
if (std::find(ProjectGUIDMapping[MyGUID]->DependProjects.begin(), ProjectGUIDMapping[MyGUID]->DependProjects.end(), ProjectGUIDMapping[DpdProjGUID]) == ProjectGUIDMapping[MyGUID]->DependProjects.end())
ProjectGUIDMapping[MyGUID]->DependProjects.push_back(ProjectGUIDMapping[DpdProjGUID]);
}
}
}
}
}
}
//画Mermaid图
for (auto p : projects)
{
for (auto d : p.DependProjects)
cout << p.Name << "-->" << d->Name << endl;
}
}
测试
测试和上一次一样,还是 baldurk / renderdoc :
现在的依赖关系应该是完整的了。