目录
1. 背景
初次接手一个大工程时, 往往因为复杂的项目依赖而遇到各种编译问题, 同时如果能图形化其中的依赖关系对理解整个项目有莫大的帮助, 就像能从山顶俯瞰大陆的感觉. 本文即着重于实现此功能.
2. 入手
2.1 分析sln文件
下面给出了两个Project, 其中project1的GUID为E37ACEB3-9187-43F7-AA78-000000000001
project2的GUID为E37ACEB3-9187-43F7-AA78-000000000002,
postProject部分给出了依赖关系: project2依赖于project1
Project("{2150E333-AAAA-42A3-9474-1A3956D46DE9}") = "project1", "project1.vcxproj", "{E37ACEB3-9187-43F7-AA78-000000000001}"
EndProject
Project("{2150E333-AAAA-42A3-9474-1A3956D46DE9}") = "project2", "project2.vcxproj", "{E37ACEB3-9187-43F7-AA78-000000000002}"
ProjectSection(ProjectDependencies) = postProject
{E37ACEB3-9187-43F7-AA78-000000000001} = {E37ACEB3-9187-43F7-AA78-000000000001}
EndProjectSection
EndProject
2.2 给出正则表达式
Python正则表达式解析project: r'Project[^=]+=\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"\{([^"]+)\}"'
group(1)为project_name, group(2)为project_vcproj, group(3)为GUID
Python正则表达式解析postProject: r'\{([\w-]+)\}\s=\s\{([\w-]+)\}'
group(1)应该等于group(2), 都是依赖的项目的GUID.
3 程序
请见visual_Project_dependency-Python工具类资源-CSDN下载
4. demo
5. 补充 - 另外一种情况
还有一种依赖关系保存在project文件里,形式如下:
<ProjectReference Include="..\MsgCreator\MsgCreator.vcxproj">
<Project>{d3eada85-50f9-4a1d-a2f7-18612358b436}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
表示当前工程依赖MsgCreator. vcxproj其实是个XML文件,直接用BeautifulSoup打开查找<Project>项即可。
6. 补充 - 完整代码
#!/usr/bin/python
# -*- coding: gb2312 -*-
#########################################################################
#2022-09-24 support case 2: denpendency in vcxproj.
#2018-08-01 born
########################################################################
import sys,re
from bs4 import BeautifulSoup
import os
#import os for opening project file to search below kind of dependencies(case 2).
#<ProjectReference Include="..\MsgCreator\MsgCreator.vcxproj">
# <Project>{d3eada85-50f9-4a1d-a2f7-18612358b436}</Project>
# <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
#</ProjectReference>
projectsCommentMap = {
'project1':'comment1'
,'project2': r'comment2: <BR/>aaa'
}
class Project:
def __init__(self, projectStr, slnpath):
self.comments = ''
projectNameGUIDpattern = r'Project[^=]+=\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"\{([^"]+)\}"'
m = re.search(projectNameGUIDpattern,projectStr)
self.name = m.group(1)
self.GUID = m.group(3).upper()
self._fullpath = os.path.join(slnpath,m.group(2))
#case 1: dependency specified in solution file.
projectDependenciesPattern = r'\{([\w-]+)\}\s=\s\{([\w-]+)\}'
m = re.findall(projectDependenciesPattern,projectStr)
self.dependencyList = []
for match in m:
if(match[0]==match[1]):
upperDep = match[0].upper()
self.dependencyList.append(upperDep)
#case 2: denpendency specified in specific project file.
# <ProjectReference Include="..\MsgCreator\MsgCreator.vcxproj">
# <Project>{d3eada85-50f9-4a1d-a2f7-18612358b436}</Project>
# <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
# </ProjectReference>
with open(self._fullpath, "r") as file:
contents = file.read()
projxml = BeautifulSoup(contents, "xml")
find_depen = projxml.find_all('Project')
for dep in find_depen:
if dep.parent and dep.parent.name=='ProjectReference':
parentProjGUID = dep.text.strip('{}')
self.dependencyList.append(parentProjGUID.upper())
self.dependencyProjects = []
def __hash__(self):
return hash(self.GUID)
def __eq__(self,other):
if(self.name==other.name and self.GUID==other.GUID):
return True
return False
new_node_nocomment = '''%d [shape=none label=<<table border="0" cellspacing="0"><tr><td port="port1" border="1" color="red"><B>%s</B></td></tr></table>>]\n'''
new_node_withcomment = '''%d [shape=none label=<<table border="0" cellspacing="0"><tr><td port="port1" border="1" bgcolor="green"><B>%s</B></td></tr><tr><td port="port2" border="1">%s</td></tr></table>>]\n'''
if __name__ == '__main__':
'''Usage: this_file 2010_sln_file'''
slnFileName = sys.argv[1]
#slnFileName = r'C:\cpp\xalan-c-Xalan-C_1_11_0\Projects\Win32\VC15\Xalan.sln'
import os
slnpath = os.path.dirname(slnFileName)
slnFile = open(slnFileName,'r')
slnStr = slnFile.read()
print(len(slnStr))
projectStartPos = 0
projectEndPos = 0
PROJECT_START_CONSTANT = 'Project("{'
PROJECT_END_CONSTANT = 'EndProject'
project_list = []
while(1):
projectStartPos = slnStr.find(PROJECT_START_CONSTANT,projectStartPos)
if(projectStartPos<0):
break;
projectEndPos = slnStr.find(PROJECT_END_CONSTANT,projectStartPos)
if(projectEndPos<0):
print('NO EndProject closes the project')
break;
project_string = slnStr[projectStartPos:projectEndPos]
project_list.append(Project(project_string, slnpath))
projectStartPos = projectEndPos
print("Total# of projects: %d"%(len(project_list)))
guid2ProjectMap = {}
for project in project_list:
guid2ProjectMap[project.GUID] = project
#update dependencyProjects
for project in project_list:
for dependGuid in project.dependencyList:
if dependGuid in guid2ProjectMap:
project.dependencyProjects.append(guid2ProjectMap[dependGuid])
else:
print('project %s depend on a project not in solution:%s'%(project.name, dependGuid))
#generate DOT file
dot_file_name = 'temp.dot'
dot = open(dot_file_name,'w+')
dot.write("digraph Tree {\n")
#dot.write('node [shape=record color=blue fontname="bold"] ;\n')
#generate ID for project
project2IDMap = {}
refProjectList = set([])
i = 0
for project in project_list:
project2IDMap[project]=i
i+=1;
for project in project_list:
if(len(project.dependencyProjects)>0):
refProjectList.add(project)
for dependProject in project.dependencyProjects:
refProjectList.add(dependProject)
singlefile = open('single.txt','w+')
for project in project_list:
if(project in refProjectList):
if(project.name in projectsCommentMap):
projectcomments = projectsCommentMap[project.name]
dot.write(new_node_withcomment%(project2IDMap[project], project.name,projectcomments))
else:
dot.write(new_node_nocomment%(project2IDMap[project], project.name))
if(len(project.dependencyProjects)>0):
for depend_project in project.dependencyProjects:
dot.write("%d -> %d ;\n"%(project2IDMap[depend_project],project2IDMap[project]))
else:
singlefile.write(project.name)
singlefile.write('\n')
dot.write("}")
dot.close()
singlefile.close()
最后附一张开源库Xalan的项目依赖关系图: