A Project Dependency Graph Utility for Visual Studio 2008
来源:互联网 发布:mac查看硬盘使用时间 编辑:程序博客网 时间:2024/04/29 09:00
Introduction
I recently wanted to look at the dependencies of a fairly large set of projects in a solution (not the one in the screenshot), and discovered that while there are apps/tools that do that, they are either Code Project articles for previous versions of Visual Studio or they create an unreadable smear of boxes and lines, because they were never designed to handle a solution with 50 or more projects. So, I decided to create a textual, treeview based browser of dependencies.
It's very simple, offering both a hierarchical view of project dependencies or a flattened list of dependencies, and also a tree-view showing projects that are dependencies of other projects (the right-hand side of the image above).
Limitations
- Will undoubtedly become obsolete with VS2010
- Works only with C# projects (any other project type is not parsed)
- Works only with VS2008 solution files
Hierarchical View of Project Dependencies
Note how the view is hierarchical, allowing you to drill into each project's dependencies.
Flattened View of Project Dependencies
In this view, the application drills into project dependencies for you and presents all unique dependencies in a flattened list:
Project's Dependency on Other Projects
You can also pick a project and find out what projects reference the selected project:
The Code
The code is really simple. Very little error checking and pretty much brute force implementation. The one annoying thing I discovered is that the solution file is not an XML document, whereas the project files (csproj) are. Makes one wonder.
Reading the Solution File
Basically, this involves a lot of string checking and processing, in which a dictionary of project names and project paths is created.
public class Solution{ protected Dictionary<string, string> projectPaths; public Dictionary<string, string> ProjectPaths { get { return projectPaths; } } public Solution() { projectPaths = new Dictionary<string, string>(); } // Example: // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NumericKeypadComponent", // "NumericKeypadComponent\NumericKeypadComponent.csproj", // "{05D03020-4604-4CE9-8F99-E1D93ADEDF15}" // EndProject public void Read(string filename) { StreamReader sr = new StreamReader(filename); string solPath = Path.GetDirectoryName(filename); while (!sr.EndOfStream) { string line = sr.ReadLine(); if (!String.IsNullOrEmpty(line)) { if (line.StartsWith("Project")) { string projName = StringHelpers.Between(line, '=', ','); projName = projName.Replace('"', ' ').Trim(); string projPath = StringHelpers.RightOf(line, ','); projPath = StringHelpers.Between(projPath, '"', '"'); projPath = projPath.Replace('"', ' ').Trim(); // virtual solution folders appear as projects but don't end with .csproj // gawd, imagine what happens if someone creates a foo.csproj // virt. solution folder! if (projPath.EndsWith(".csproj")) { // assume relative paths. Probably not a good assumption projPath = Path.Combine(solPath, projPath); // we don't allow projects with the same name, even if different paths. projectPaths.Add(projName, projPath); } } } } sr.Close(); }}
Reading a Project File
Ah, an XML file! Woohoo! It took a while for me to realize that I needed to specify the XML namespace along with the element name. As in, several hours of fussing, pulling hair out, and finally stumbling across some documentation in MSDN that gave an example of using Elements
with a namespace. Sigh.
Similar to the Solution
class, this class builds a dictionary of referenced projects, where the key is the referenced project name and the value is the referenced project path.
public class Project{ protected Dictionary<string, string> referencedProjects; protected List<Project> dependencies; public string Name { get; set; } public Dictionary<string, string> ReferencedProjects { get { return referencedProjects; } } public List<Project> Dependencies { get { return dependencies; } } public Project() { referencedProjects = new Dictionary<string, string>(); dependencies = new List<Project>(); } // Example: // <Project ToolsVersion="3.5" DefaultTargets="Build" // xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> // <ItemGroup> // <ProjectReference Include="..\Cx.Attributes\Cx.Attributes.csproj"> // <Project>{EFDBD81C-64BE-47F3-905E-7618B61BD224}</Project> // <Name>Cx.Attributes</Name> // </ProjectReference> public void Read(string filename) { XDocument xdoc = XDocument.Load(filename); XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003"; foreach (var projRef in from el in xdoc.Root.Elements(ns + "ItemGroup").Elements(ns + "ProjectReference") select new { Path = el.Attribute("Include").Value, Name = el.Element(ns + "Name").Value }) { string projPath = Path.GetDirectoryName(filename); projPath = Path.Combine(projPath, projRef.Path); referencedProjects.Add(projRef.Name, projPath); } }}
Parsing the Solution File
A separate method is used to put the solution and projects together into yet another dictionary, this time the key being the project name and the value being aProject
instance. This method also populates the Dependencies
collection in theProject
class (yeah, that's really bad practice, I know).
protected Dictionary<string, Project> projects;protected void ParseSolution(string filename){ projects = new Dictionary<string, Project>(); Solution sol = new Solution(); sol.Read(filename); foreach (KeyValuePair<string, string> kvp in sol.ProjectPaths) { Project proj = new Project(); proj.Name = kvp.Key; proj.Read(kvp.Value); projects.Add(proj.Name, proj); } foreach (KeyValuePair<string, Project> kvp in projects) { foreach (string refProjName in kvp.Value.ReferencedProjects.Keys) { Project refProject = projects[refProjName]; kvp.Value.Dependencies.Add(refProject); } }}
Populating the Dependency Graph
There are two ways of populating the dependency graph: hierarchical or flat. The code for both is similar--the big difference is that child nodes aren't being created. And another ugly kludge in the flattened implementation is how I search for assemblies already in the node collection. Yuck!
/// <summary>/// Sets up initial project name and first level of dependencies./// From there, child dependencies are either added hierarchically or flattened./// </summary>protected void PopulateNewLevel(TreeNode node, ICollection<Project> projects){ List<string> nodeNames = new List<string>(); foreach (Project p in projects) { TreeNode tn = new TreeNode(p.Name); node.Nodes.Add(tn); if (asTree) { PopulateNewLevel(tn, p.Dependencies); } else { // flatten the dependency hierarchy, removing duplicates PopulateSameLevel(tn, p.Dependencies); } }}protected void PopulateSameLevel(TreeNode node, ICollection<Project> projects){ foreach (Project p in projects) { bool found = false; foreach (TreeNode child in node.Nodes) { if (child.Text == p.Name) { found = true; break; } } if (!found) { TreeNode tn = new TreeNode(p.Name); node.Nodes.Add(tn); } PopulateSameLevel(node, p.Dependencies); }}
Populating the "Is Dependency Of" Graph
Also straightforward, also has a kludge to remove duplicate project names.
protected void PopulateDependencyOfProjects(TreeNode node, ICollection<Project> projects){ foreach (Project p in projects) { TreeNode tn = new TreeNode(p.Name); node.Nodes.Add(tn); foreach (Project pdep in projects) { foreach (Project dep in pdep.Dependencies) { if (p.Name == dep.Name) { bool found = false; // the project pdep has a dependency on the project p. // p is a dependency of pdep foreach (TreeNode tnDep in tn.Nodes) { if (tnDep.Text == pdep.Name) { found = true; break; } } if (!found) { TreeNode tn2 = new TreeNode(pdep.Name); tn.Nodes.Add(tn2); } } } } }}
Conclusion
Hopefully someone will find this useful and maybe even build upon it! It was a short and fun to write application. One thing I'd like to add is sorting the project names.
History
6/17/2009 - Initial Version
6/25/2009
- Projects are now sorted alphabetically
- Added a synchronize option, which finds the project in the opposing tree, selects it, and opens the tree. This is really useful to look at both project dependencies and dependencies of a project at the same time.
7/5/2009 - Added rendering of graph using graphviz. Thanks to Dmitri Nesteruk for making the original changes to this application and for making his rendering code public.
- A Project Dependency Graph Utility for Visual Studio 2008
- Visual Studio 2008 Project for CUDA 3.2
- Visual Studio 2010分析工具之Dependency Graph【z】
- The Code Project Add-In for Visual Studio 2008
- Visual Studio 2010 建模学习(二) - 依赖图 (Dependency Graph)
- Visual Studio Database Project
- PowerCommands for Visual Studio 2008
- x264 for Visual Studio 2008
- PowerCommands for Visual Studio 2008
- Visual Assist X for Visual Studio 2008
- Target 'Pods' of project 'Pods' was rejected as an implicit dependency for 'libPods.a' because its a
- HOWTO: Get the project flavor (subtype) of a Visual Studio project from an add-in
- Compile gradle project with another project as a dependency
- Visual Studio Team Project 删除
- cmake to visual studio project
- Visual Studio C++ Project 配置
- AutoCode 2010 [A powerful add-in for Visual Studio .NET]
- create a deployment package for Webservice in Visual Studio
- WinCE学习笔记---第一天
- Basic AJAX Example for Zend Framework
- 一些有用的链接
- POJ - 3613 Floyd的思想+矩阵乘法
- 十种为自己鼓励的方法
- A Project Dependency Graph Utility for Visual Studio 2008
- HDU-2063 二分图匹配
- sqlserver查找存储过程关键字方法之三
- AT指令(中文详解版)(一)
- 选择器
- VS2005和VS2008快捷键大全
- AT指令(中文详解版)(二)
- c#如何绑定dll或者ocx组件中的事件
- Struts标签(