В любых проектах по мере увеличения объема кодовой базы растет энтропия, которую приходится уменьшать при помощи различных рефакторингов, в числе которых есть разделение сборки на части, т.е. создание нового проекта и перемещение в него типов, отвечающих за решение определенного круга задач. В большом решении (solution) с сотнями проектов такой рефакторинг над общей сборкой может оказаться очень непростой и трудозатратной задачей, если, конечно, ее не автоматизировать.
Рутинную работу с внутренностями решения в Visual Studio можно осуществить несколькими способами:- вручную через IDE (самоубийство);
- редактируя файлы проектов и решения с помощью скриптов, например PowerShell (по сути изобретение маленьких велосипедов);
- используя предоставленные Microsoft средства для доступа к внутренностям студии
- через StudioShell;
- через t4 ( как по мне - наиболее предпочтительный вариант, если конечно вы не фанат PS скриптов ).
- через написание расширения к студии. Этот вариант не самый удобный, так как для проверки работы расширения придется каждый раз его компилировать и устанавливать. Однако, если разрабатываемая автоматизация не одноразовая - возможно в конечном итоге удобнее будет использовать именно этот вариант.
В прошлом посте я описывал решение задачи массового добавления ссылки на проект при помощи StudioShell. Сейчас опишу решение той же самой задачи при помощи t4. Напомню формулировку:
Добавить ссылку на "dev.Common.Exceptions" к тем проектам, которые имеют ссылку на "dev.Common.Types".
Добавить ссылку на "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("========================================================"); } } } #>Ключевые моменты:
- Две директивы include подключают шаблоны с классами-помощниками.
- $(SolutionDir) - папка решения;
- VisualStudioHelper - основной класс-помощник по работе с решением. Имеет массу полезных методов вроде получения всех проектов решения или получения элементов проекта. По сути является фасадом к не очевидной иерархии классов из пространства имен EnvDTE.
- 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.
В общем инструмент очень полезный. Для изучения его возможностей рекомендую использовать следующие ресурсы:
- Oleg Sych's blog
- MSDN
- Документация и блог на сайте Tangible T4 Editor
Комментариев нет:
Отправить комментарий