写一个小工具来可视化VisualStudio中项目之间的依赖关系(1.针对sln中的信息)

本篇主要对问题进行分析,尝试对项目间依赖关系进行解析,但只是解析出了.sln中的信息。对于完整的工具,详见下一篇

目标

Visual Studio 中,一个“解决方案”(.sln)包含多个“项目”(.vcxproj)。
而项目之间有依赖关系:
在这里插入图片描述
当对一个项目进行生成时,它总会先确保其所依赖的项目先被生成。这样整体就会以一个正确的顺序生成。

当项目变多时,我期望能用一个图来可视化这些关系以便观察。但我暂时还没找到特别方便的工具。


不过,想了一下,其实这样的工具不难实现,我在之前的博客《为代码文件的include关系生成Mermaid图》《为代码中的类继承关系生成Mermaid图》都做了类似的东西。这次的区别只在于——所谓的“关系”变为了“项目间依赖关系”。

而“项目间依赖关系”的数据一定是保存在文件中的,我想应该是.sln文件或.vcxproj文件,他们都是可阅读的文本文件,而非不可阅读的二进制文件。因此我可以对其进行解析。

因此,本篇的目标是完成这样的小工具。大体步骤如下:

  1. 研究“项目间依赖关系”的信息如何从代码上获取
  2. 解析出“项目间依赖关系”并生成Mermaid图

找到“项目间依赖关系”信息存在哪里

对于这个问题,有多种方法解决,例如:

  1. 查阅官方文档。优点是权威,缺点是不一定能找到,花费的时间长短也不一定。
  2. 先有一个自己的假设,比如 .sln文件或 .vcxproj文件中写了一个项目依赖的项目的名字 。然后通过 ”字符串搜索“ 的方式来找到具体的位置。当自己有假设时这个方法的优点就是快。但缺点是不稳定,因为假设不一定对,而”字符串搜索“的结果也可能有歧义。
  3. 通过实验比较指定项目依赖项的“前”与“后”,文件之间的差异

此时我决定采用第3种。因为构建这样一个实验是相对简单的,而且通过实验一定能知道结果。
(当然并不是说这三种方法是互斥的,它们其实也可以结合,比如从 2 和 3 方法中获得思路并最后在官方文档中求证)


我做了小实验(详细操作见【附录:实验细节】)之后发现,在指定了一个项目的依赖项目之后,.sln会添加内容:
(下图中test2项目依赖testProjA项目)
在这里插入图片描述
又经过对更复杂的工程的.sln文件进行比对后,我得出结论:

.sln中,每一个项目都以Project后跟一系列项目的数据的一行做开头,以EndProject这一行做结尾。在其中 ProjectSection(ProjectDependencies) = postProject 一行之后跟着数行其依赖的项目所对应的编码,然后以EndProjectSection结束。

写成代码的话就是:

扫描二维码关注公众号,回复: 13130797 查看本文章
Project("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}") = "项目A的名字", "项目A的.vcxproj文件", "{项目A的编码}"
	ProjectSection(ProjectDependencies) = postProject
		{
    
    项目A的所依赖的第1个项目的编码} = {
    
    项目A的所依赖的第1个项目的编码}
		{
    
    项目A的所依赖的第2个项目的编码} = {
    
    项目A的所依赖的第2个项目的编码}
		{
    
    项目A的所依赖的第3个项目的编码} = {
    
    项目A的所依赖的第3个项目的编码}
	EndProjectSection
EndProject

编写代码

在上一步分析出格式之后,“解析出依赖关系”变成了一个纯字符串处理的问题。

我的思路大概是这样:

  1. 以“行”为单位读取.sln文件内容
  2. 将行按项目来分割,标准是:以Project为开头的行起始,以EndProject的行结束。这些行的内容对应存储为一个ProjectInfo
  3. 解析出ProjectInfo项目的名字。和项目的编码。
  4. 遍历每一个ProjectInfo,找到 ProjectSection(ProjectDependencies) = postProject 一行,然后对接下来的每一行读取其编码,只需要比对其他ProjectInfo中的编码就可以知道其依赖项目的名字了。重复此操作直到 EndProjectSection
  5. 将上一步得到的依赖关系按照 Mermaid 的格式输出出来

具体代码如下:

#include <iostream>
#include<vector>
using namespace std;

//一个项目所对应的信息
struct ProjectInfo
{
    
    
    //读取到的每一行信息
    vector<string> lines;

    //项目名字
    string Name;
    //项目编码
    string Code;

    //所依赖的项目
    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;
}

int main()
{
    
    
    //.sln文件的路径
    const string slnFile = "D:/Temp/renderdoc.sln";

    //读取文件
    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;
        }
    }

    //遍历每一个项目找到其名字与编号
    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.Code = StringSplitter(SubStrs[3], '{', '}')[0];
    }

    //遍历每一个项目找到其依赖的项目
    for (ProjectInfo& p : projects)
    {
    
    
        bool DependenciesLines = false;

        for (auto line : p.lines)
        {
    
    
            if (DependenciesLines)
            {
    
    
                if (line.find("EndProjectSection") != string::npos)//如果找到"EndProjectSection"则结束写依赖的项目
                    DependenciesLines = false;
                else    //这是一条表示依赖的行
                {
    
    
                    //依赖的项目的编码
                    string DpdProjCode = StringSplitter(line, '{', '}')[0];

                    //找到依赖的项目
                    for (ProjectInfo& other : projects)
                        if (other.Code == DpdProjCode)
                            p.DependProjects.push_back(&other);
                }
            }

            if (line.find("ProjectSection(ProjectDependencies) = postProject") != string::npos)//如果找到"ProjectSection(ProjectDependencies) = postProject"则开始写依赖的项目
                DependenciesLines = true;
        }
    }

    //画Mermaid图
    for (auto p : projects)
    {
    
    
        for (auto d : p.DependProjects)
            cout << p.Name << "-->" << d->Name << endl;
    }
}

测试

我对 baldurk/renderdoc/renderdoc.sln测试后结果如下:

renderdoc
crash_generation_server
crash_generation_client
exception_handler
breakpad_common
NV
renderdoccmd

发现问题:原来信息不止存于sln,还存于vcxproj

我发现在上一个测试中,依赖的信息明显比在VisualStudio的IDE中看到的少。

观察后发现,在IDE中能看到一些依赖关系,例如:
在这里插入图片描述
但是在.sln文件中这些依赖关系并不能看到:

Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qrenderdoc", "qrenderdoc\qrenderdoc_local.vcxproj", "{A14A6AE5-02B1-35FE-BE59-B3E7C273B40B}"
EndProject

看来.sln中并不包含所有的依赖信息。

我假设.vcxproj中也有信息,并也以“项目的编码”的形式来指定其依赖的项目。
因此在qrenderdoc_local.vcxproj中对其所依赖的项目version(代码257FD75C-4D17-4A23-A754-23BFD85887A0)进行搜索,发现可以找到:
在这里插入图片描述
很明显:
.vcxproj的格式是xml,其ProjectReference节点中指明了其依赖的项目。

看来,当前的代码需要扩展。内容就放在之后吧。
(下一篇:《写一个小工具来可视化VisualStudio中项目之间的依赖关系(2.补全vcxproj中的信息)》

附录:实验细节

1. 创建解决方案与项目

在这里插入图片描述
这会为我创建一个解决方案test2.sln和一个同名的工程test2.vcxproj
在这里插入图片描述

2. 在解决方案下创建新项目

右键解决方案,选择新建项目
在这里插入图片描述
在这里插入图片描述
随后解决方案将有两个项目了
在这里插入图片描述

3. 复制此时的文件夹,作为【改动前版本】

注意保存当前的工程文件(比如关闭VisualStudio,会提示保存)

然后复制
在这里插入图片描述

4. 做操作,指定依赖关系

打开解决方案的属性面板
在这里插入图片描述
指定:test2依赖于testProjA
在这里插入图片描述
然后点确定应用

5. 复制此时的文件夹,作为【改动后版本】

注意保存当前的工程文件(比如关闭VisualStudio,会提示保存)

然后复制
在这里插入图片描述

6. 比较【改动前版本】与【改动后版本】

我这里使用 Meld 作为工具来比较:
在这里插入图片描述
结果:
在这里插入图片描述
改动不止一个,不过由于只有.sln是可阅读的文本文件,因此只能从其入手:

幸运的是,这里面确实有想要的信息:
在这里插入图片描述
可以推断出,每个项目都对应一个编码,比如testProjA对应82C67BD6-F17D-442B-B964-5067771F555E,而test2想要指定其依赖于testProjA时将用编码来指代。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u013412391/article/details/113794789
今日推荐