Для того, чтобы начать разбираться в большом проекте, нужно понять, какие в проекте есть логические модули и как они связаны. В случае использования Visual Studio в дорогих редакциях продукта доступны функции построения различных графов зависимостей. К сожалению, во-первых, студии с меню «Architecture» реально дорого стоят. Во-вторых, в старых проектах, которые до сих пор собираются и имеют проектные файлы только для Visual Studio 2008, сама студия особенно ничего не предлагает.

Конечно, стоит отметить, что для такой цели существуют дорогие инструменты (например, Understand). Но для начала мне хватило того, что я набросал небольшой скрипт на PowerShell. Скрипт этот генерирует dot-файл для GraphViz на основе sln файла. В результате получается схема как на рисунке ниже, с которой уже можно начинать разбираться в проекте.



Далее расскажу, как работает этот скрипт.

Первое, что мне понадобилось сделать — это провести анализ файлов sln и файлов проектов, которые там прописываются. С sln файлами сразу повезло, т.к. в разных версиях студии (начиная с Visual Studio 2008) список проектов выглядит одинаково, как набор строк следующего вида:

Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BaseClasses Apr05", "..\..\3rd_party\BaseClasses_Apr05\baseclasses90.vcproj", "{2A939EC4-235F-4879-9D38-C865FFD54D82}"
EndProject

Вытащить список всех таких строк можно одной строкой на PowerShell:

Select-String -LiteralPath $slnFile -Pattern "Project\(.*vcproj"

Понятно, что $slnFile — это путь к файлу, а «Project\(.*vcproj» — регулярное выражение для выборки строк. В файле sln все пути относительные. Следующей задачей будет вытащить относительные пути из полученных строк и на их основе построить полные пути к файлам. Сделать это можно еще одной строкой:

Join-Path -Path $slnPath -ChildPath ($s -split ',')[1].Trim().Replace('"','')

Здесь предполагается, что $s содержит одну из ранее полученных строк. Понятно, что происходит разделение на подстроки, которые разделены запятой, командой ($s -split ','). Вторая подстрока (получаем с помощью [1]) как раз содержит относительный путь. Удаляем лишние пробелы с помощью Trim и удаляем кавычки с помощью Replace. Потом к относительному пути прикрепляем путь к sln файлу командой Join-Path. Обернув это в цикл, получили набор путей к проектам. Теперь время заняться файлами проектов, чтобы найти зависимости между ними.

Microsoft в момент перехода на Vistual Studio 2010 поменяла формат проектных файлов. Но хорошо то, что файлы солюшенов и в VS2008 и в VS2010 имеют XML формат. Это позволяет использовать язык запросов XPath, чтобы легко получить нужное поле. В PowerShell это делается командой Select-Xml. С ее помощью из файла проекта несложно вытащить зависимости. Также, хочется, чтобы разные типы проектов в конечном графе отображались разными цветами. Для этого надо выяснить какие типы бывают. Это можно сделать, натравив следующую команду на каталог с кучей проектов:

PS D:\test> Get-ChildItem -Path . -Filter *.vcxproj -Recurse | Select-Xml -XPath "//*[local-name()=`"ConfigurationType`"]/text()" | % { Write-Output $_.Node.Value }

Можно заметить, что в PowerShell поток с результатами выполнения команд удобно перенаправлять в следующую команду с помощью символа «|». Команда Get-ChildItem помогает найти все файлы типа vcxproj, а Select-Xml выбирает из них тип конфигурации. Для каждого файла выводим тип проекта на экран. При большом количестве файлов вывод получается не очень информативен, но в PowerShell есть команда Group-Object, которая спасает положение группируя результаты. Итого получаем что-то вроде этого:

PS D:\test> Get-ChildItem -Path . -Filter *.vcxproj -Recurse | Select-Xml -XPath "//*[local-name()=`"ConfigurationType`"]/text()" | % { Write-Output $_.Node.Value } | Group-Object

Count Name                      Group
----- ----                      -----
   92 StaticLibrary             {StaticLibrary, StaticLibrary, StaticLibrary, StaticLibrary...}
  287 Application               {Application, Application, Application, Application...}
  424 DynamicLibrary            {DynamicLibrary, DynamicLibrary, DynamicLibrary, DynamicLibrary...}

Аналогично можно сделать для файлов типов vcproj, csproj и других. В vcproj файлов типы конфигураций кодируются числами, но что за ними стоит выяснить не сложно, открыв проект в Visual Studio.

На данном этапе у нас есть проекты, их типы и связи между ними. Можно формировать GraphViz файл. Делается это очень просто — сразу записываем заголовок следующего вида:

digraph ProjectName {
size="60,60"; rankdir=LR; overlap=false; splines=true; dpi=300;
node[color=mediumorchid3, style=filled, shape=box, fontsize=10, fontcolor=white];
edge[arrowhead=vee, arrowtail=inv, arrowsize=.7, fontsize=10, fontcolor=navy];
labelloc=t; label="Solution: ProjectName.sln"; fontsize=14;

А далее все проекты и связи между ними:

"project1.vcproj" [color=indigo];
"project1.vcproj" -> "project2.vcproj" [color="#C3E500"];
"project2.vcproj" [color=indigo];

Цвет связи привязан к проекту, чтобы было легче читать граф. Т.е. у каждого проекта все исходящие связи одного цвета. Сделать это можно использовав HSL (от англ. Hue, Saturation, Lightness) представление цвета. Фиксируем S и L компоненты, а H меняем при обработке очередного файла проекта. Потом HSL преобразуем в RGB и записываем в выходной файл.

Исходный код функции выбора цвета, а также всего остального скрипта можно найти на Bitbucket.

Комментарии (2)


  1. FiresShadow
    20.05.2015 08:34

    Resharper тоже так умеет. Не думаю, что граф проектов может быть полезен, чтобы

    начать разбираться в большом проекте.
    Хотя в других редких случаях может быть полезен: например, если нужно убедиться, что в приложении на основе трехзвенной архитектуры в папке клиента не окажется dll-файлов с sql-ными текстами запросов.


    1. jia3ep Автор
      20.05.2015 10:30
      +1

      Да, так умеет Resharper и не только. Я отметил, что, во-первых, подобные средства не бесплатны, а во-вторых — всегда интересно, что ты можешь сделать сам.
      Насколько граф полезен — это каждый решает сам для себя. В моем случае (обратите внимание на столбец Count в выводе Group-Object), это оказалось полезным.