#c_sharp #t4
Не подскажите, пожалуйста, как обновлять / запускать генерацию кода из tt-шаблонов
(t4) по кнопке/по команде?
причем, хотелось, бы чтобы механизм обновления был привязан к solution-у (то есть,
при открытии solution-а в другой студии на другой машине, чтобы механизм оставался
рядом, чтобы можно было в любом случае по кнопке / по команде запустить генерацию,
даже на другой машине, без предварительных настроек/установок [upd: зачеркнул вариант
настроек, т.к. вариант с вызовом Custom Tool из студии, наверное, подходит, если этот
tool с собой])
генерация кода при билде - не совсем подходит, т.к. сама генерация может занимать
там пол минуты (примерно, не суть), такая задержка при каждом билде не желательна,
потому что генерация необходима, относительно редко
есть вариант самостоятельного запуска TextTransform.exe как написано здесь (Run T4
programmatically); но проблемка с этим вариантом в том, что кодогенерация использует
EnvDTE.DTE (на сколько я понимаю, это объект самой вижуал-студии, с помощью которого
можно, например, разбирать уже написанный код проекта); при вызове TextTransform.exe
откуда-то не из студии, этот EnvDTE.DTE будет = null, и тогда не сработает код шаблона
// UPD: код, который дает Null reference exception
// когда вызывал TextTransform.exe из своего консольного приложения
var visualStudio
= (this.Host as IServiceProvider)
.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
Всего таких шаблонов в solution-е несколько (скажем, 10, не суть); сейчас для обновления
открываю каждый из шаблонов, добавляю пробел где-нибудь в комментарии (чтобы файл был
измененным); при сохранении измененного файла вызывается кодогенерация. В принципе,
такое решение приемлемо, но не красивое и не уверен, что коллеги одобрят
Upd (дополнительное пояснение) : подразумевается, чтобы коллеги сами могли генерировать
код и чтобы у них не возникло первым впечатлением, что что-то не просто... Два интерфейса
(public interface IService и public interface IServiceCallback) в одном проекте являются
как бы моделью на основе которой генерируется код. Для коллег надо написать инструкцию,
как редактировать эти интерфейсы, и как сделать, чтобы сгенерировался код. Будет симпатичнее,
если в инструкции будет описана какая-то одна команда для обновления кода. Чтобы максимально
упростить добавление кода.
Ответы
Ответ 1
Внезапно оказалось, что в Visual Studio уже есть пункт в меню Build > Transform All T4 Templates. Он перегенерирует все файлы *.tt в проекте. Этот пункт есть, по крайней мере в VS 2017 и VS 2019. К сожалению, нет под рукой версии Community. Поэтому неизвестно, есть ли эта кнопка там или только в платных версиях Студии. В комментариях выяснилось, что это полностью устраивает автора. Однако, есть сведения, что иногда это не работает, в частности, проблемы с .NET Core. Также могут возникнуть проблемы с конкретными платформами x64/x86, требуется указать Any CPU.Ответ 2
Можно получить ссылку на EnvDTE из стороннего приложения. Объектная модель автоматизации Visual Studio. Интерфейсы EnvDTE DTE (Development Tools Environment) переводится, как среда средств разработки. Пара слов про EnvDTE / EnvDTE80. Если я правильно понял, то просто EnvDTE это базовый интерфейс. Далее EnvDTE80 (соответствующий VisualStudio.DTE.8.0) расширяет просто EnvDTE, добавляя какой-то функционал. EnvDTE100 (VisualStudio.DTE.10.0) расширяет EnvDTE90 (VisualStudio.DTE.9.0). Интерфейс DTE является абстракцией верхнего уровня для visual студии (в рамках automation model). Для того, чтобы получить на него ссылку из стороннего приложения, можно использовать ProgID COM identifier. Например, этот идентификатор для 2010 студии такой: "VisualStudio.DTE.10.0". Вот пример получения DTE из стороннего c#-ного приложения: // Get the ProgID for DTE 8.0. System.Type t = System.Type.GetTypeFromProgID( "VisualStudio.DTE.10.0", true); // Create a new instance of the IDE. object obj = System.Activator.CreateInstance(t, true); // Cast the instance to DTE2 and assign to variable dte. EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)obj; // Show IDE Main Window dte.MainWindow.Activate(); Надо отметить, что создание экземпляра через CreateInstance(...) обозначает запуск deven.exe. Но само окно студии появится после запуска Activate(). Но! если студия уже запущена, то можно так: EnvDTE80.DTE2 dte2; dte2 = (EnvDTE80.DTE2) System.Runtime.InteropServices.Marshal.GetActiveObject( "VisualStudio.DTE.10.0"); А в случае нескольких запущенных студий можно так: using EnvDTE80; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; [DllImport("ole32.dll")] private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc); [DllImport("ole32.dll")] private static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot); public static DTE2 GetByID(int ID) { //rot entry for visual studio running under current process. string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", ID); IRunningObjectTable rot; GetRunningObjectTable(0, out rot); IEnumMoniker enumMoniker; rot.EnumRunning(out enumMoniker); enumMoniker.Reset(); IntPtr fetched = IntPtr.Zero; IMoniker[] moniker = new IMoniker[1]; while (enumMoniker.Next(1, moniker, fetched) == 0) { IBindCtx bindCtx; CreateBindCtx(0, out bindCtx); string displayName; moniker[0].GetDisplayName(bindCtx, null, out displayName); if (displayName == rotEntry) { object comObject; rot.GetObject(moniker[0], out comObject); return (EnvDTE80.DTE2)comObject; } } return null; } Таким образом: Process Devenv; ... //Get DTE by Process ID EnvDTE80.DTE2 dte2 = GetByID(Devenv.Id); Далее, например, можно получить коллекцию проектов так: Projects projects = (Projects)dte.GetObject("CSharpProjects"); Вот еще пара полезных статей: Creating EnvDTE.DTE for VS 2017 from outside the IDE How to get DTE from Visual Studio process ID
Комментариев нет:
Отправить комментарий