воскресенье, 2 февраля 2014 г.

Массовое добавление ссылки на проект в Visual Studio (часть 2, T4)

В любых проектах по мере увеличения объема кодовой базы растет энтропия, которую приходится уменьшать при помощи различных рефакторингов, в числе которых есть разделение сборки на части, т.е. создание нового проекта и перемещение в него типов, отвечающих за решение определенного круга задач. В большом решении (solution) с сотнями проектов такой рефакторинг над общей сборкой может оказаться очень непростой и трудозатратной задачей, если, конечно, ее не автоматизировать.
Рутинную работу с внутренностями решения в Visual Studio можно осуществить несколькими способами:
  • вручную через IDE (самоубийство);
  • редактируя файлы проектов и решения с помощью скриптов, например PowerShell (по сути изобретение маленьких велосипедов);
  • используя предоставленные Microsoft средства для доступа к внутренностям студии
    • через StudioShell;
    • через t4 ( как по мне - наиболее предпочтительный вариант, если конечно вы не фанат PS скриптов ).
    • через написание расширения к студии. Этот вариант не самый удобный, так как для проверки работы расширения придется каждый раз его компилировать и устанавливать. Однако, если разрабатываемая автоматизация не одноразовая - возможно в конечном итоге удобнее будет использовать именно этот вариант.
В прошлом посте я описывал решение задачи массового добавления ссылки на проект при помощи StudioShell. Сейчас опишу решение той же самой задачи при помощи t4. Напомню формулировку:
Добавить ссылку на "dev.Common.Exceptions" к тем проектам, которые имеют ссылку на "dev.Common.Types".

T4


Для начала надо установить Tangible T4 Editor - расширение к Visual Studio, которое добавляет подсветку синтаксиса t4 шаблонов, интелисенс (хоть и ограниченный в бесплатной версии до основных системных библиотек), а также в платной версии - дебагинг кода шаблонов и набор UML диаграмм, на основе которых можно генерировать код. После установки текст шаблона из уныло-непонятно-одноцветного превращается в удобочитаемо-раскрашенный.
Кроме того, после регистрации на форуме можно получить доступ к галерее t4 шаблонов (в Visual Studio при открытом .tt файле Menu-Tangible T4-Template Gallery). Как раз оттуда надо забрать 2 шаблона, помогающие в работе с внутренностями решения: VisualStudioHelper.ttinclude и VisualStudioReferenceHelper.ttinclude, которые находятся в папке tangible\includes. Эти файлы, как и другие общие подключаемые шаблоны, будем хранить в папке "_tt Templates" в корне решения как физически (в файловой системе), так и логически (в решении).
Шаблон для добавления ссылок на текущий проект, если имеется ссылка на "dev.Common.Types", выглядит следующим образом:
<#@ template hostspecific="true" language="C#" debug="True"#>
<#@ output extension=".txt" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ include file="$(SolutionDir)\_tt Templates\VisualStudioHelper.ttinclude" #>
<#@ include file="$(SolutionDir)\_tt Templates\VisualStudioReferenceHelper.ttinclude" #>
<#
 IEnumerable<Project> projects = 
 this.VisualStudioHelper.GetAllProjects();
 var currentProject = this.VisualStudioHelper.CurrentProject;
 var newReferenceProjectName = currentProject.Name;
 var oldReferenceProjectName = "dev.Common.Types";
 
 foreach (Project project in projects)
 {
  var projRefManager = new ProjectReferenceManager(project);
  if (project.Name != newReferenceProjectName
    && projRefManager.HasReference(oldReferenceProjectName)
    && !projRefManager.HasReference(newReferenceProjectName))
  {
   WriteLine(project.Name + " - Adding reference to "
    + newReferenceProjectName);
   try 
   {   
    // раскомментить эту строку если действительно
    // надо добавлять референсы на проекты     
    projRefManager.AddProject(currentProject);
    project.Save();
    WriteLine("+++ Reference to " + newReferenceProjectName
     + " added.");
   }
   catch ( Exception e )
   {
    WriteLine("--- " + e.Message + "\t" + e.StackTrace);
    WriteLine("========================================================");
   }
  }
 }
#>
Ключевые моменты:
  1. Две директивы include подключают шаблоны с классами-помощниками.
  2. $(SolutionDir) - папка решения;
  3. VisualStudioHelper - основной класс-помощник по работе с решением. Имеет массу полезных методов вроде получения всех проектов решения или получения элементов проекта. По сути является фасадом к не очевидной иерархии классов из пространства имен EnvDTE.
  4. ProjectReferenceManager - фасад к работе со ссылками между проектами.
По ходу работы над этим шаблоном пришлось изменить хелпер VisualStudioReferenceHelper. Сначала наткнулся на NullReferenceException в методе HasReference, поэтому в него пришлось добавить проверки на null. Кроме того, метод AddReference упорно не хотел работать, выдавая странную ошибку
"Неопознанная ошибка (Exception from HRESULT: 0x80004005 (E_FAIL))". После недолгих поисков пробросил в хелпер метод AddProject, и все заработало.
Шаблон с тестовым решением выложен на github. Как бонус в проекте dev.Common.Exceptions можно найти еще 2 шаблона, демонстрирующие возможности работы с решением:
  • NamespacesAccounting.tt выводит список всех проектов в решении, отсортированный по количеству файлов в проектах.
  • AccountingItemsInProjects.tt в каждый проект добавляет файл About.txt, в котором выводится список используемых пространств имен, отсортированный по количеству использований.

Заключение

Возможность использования t4 для работы с решением для меня было неожиданным открытием, впрочем как и использование t4 в runtime. Поменять что-нибудь в структуре решения, массово добавить файлы во все проекты, собрать необходимую статистику по коду и тому подобные задачи уже не кажутся сложноавтоматизируемыми. Если хорошо поискать, например, на codeproject.com, то можно найти разные интересные штуки (еще одна) с использованием t4.
В общем инструмент очень полезный. Для изучения его возможностей рекомендую использовать следующие ресурсы:

Комментариев нет:

Отправить комментарий