Страницы

Поиск по вопросам

Показаны сообщения с ярлыком t4. Показать все сообщения
Показаны сообщения с ярлыком t4. Показать все сообщения

среда, 22 января 2020 г.

Запускать генерацию tt-шаблонов (t4) по кнопке / по команде

#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

суббота, 14 декабря 2019 г.

В чем смысл текстовых шаблонов T4 и когда их имеет смысл использовать?

#c_sharp #net #visual_studio #t4


В чем смысл текстовых шаблонов T4? По сути, этого же можно добиться с помощью динамической
компиляции. Доступ к исходному коду так же не даёт никаких преимуществ, так как править
его не имеет смысла из за перегенерации.
    


Ответы

Ответ 1



Если вы имеете в виду T4, то их смысл — встроенный в систему шаблонизатор/DSL. T4 используется в двух вариантах: времени компиляции и времени выполнения. T4 времени выполнения обычно используется как шаблонизатор, и означает, что вы можете легко генерировать текстовые файлы, в которых большая часть текста постоянна, а куски необходимо генерировать программно. Простой пример — шаблон письма, в который нужно вставить имя адресата, адрес и наименование товара. Но гораздо более интересным является T4 времени разработки. Эта штука позволяет вам генерировать код на этапе компиляции (точнее, даже до неё). То есть, у вас появляется ещё одно прекрасное (хотя и немного неуклюжее — если не сравнивать с шаблонной магией Александреску) средство для настоящего метапрограммирования. Причём в отличие от решений, которые вы могли бы соорудить на коленке, T4 встроен в Visual Studio, и сгенерированные файлы просто видны в дереве Solution Explorer'а, и подхватываются компилятором. T4 используется для кодогенерации многими проектами, начиная с Entity Framework. Вот здесь ещё достаточно впечатляющий список. Вот, например, пример того, как автоматически генерировать определения Dependency Property. (Я тоже использовал T4 для генерации из текстового представления XAML-словарей для локализации.) Кодогенерация времени выполнения и правда не лучший метод использования T4. Для этих целей лучше подходит CodeDOM (или, кажется, Roslyn API). А для кодогенерации времени компиляции доступ к сгенерированным исходникам очень важен, например, для отладки. Дополнение к вопросу из комментария. Да, можно в шаблоне генерировать другой шаблон. Например, такой вот шаблон <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".tt" #> <# string open = "<" + "#" + "@", close = "#" + ">"; #> <#= open #> template debug="false" hostspecific="false" language="C#" <#= close #> <#= open #> assembly name="System.Core" <#= close #> <#= open #> import namespace="System.Linq" <#= close #> <#= open #> import namespace="System.Text" <#= close #> <#= open #> import namespace="System.Collections.Generic" <#= close #> <#= open #> output extension=".cs" <#= close #> class Test { public int i; } порождает подчинённый шаблон <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> class Test { public int i; } который в свою очередь порождает класс class Test { public int i; } Никаких специальных действий выполнять не нужно, просто добавьте шаблон T4 через Add → New item → Text Template (не Runtime Text Template!) В T4-файл положите текст шаблона, как он представлен выше, и вы получите такую вот необычную структуру вашего solution'а:

пятница, 19 октября 2018 г.

В чем смысл текстовых шаблонов T4 и когда их имеет смысл использовать?

В чем смысл текстовых шаблонов T4? По сути, этого же можно добиться с помощью динамической компиляции. Доступ к исходному коду так же не даёт никаких преимуществ, так как править его не имеет смысла из за перегенерации.


Ответ

Если вы имеете в виду T4, то их смысл — встроенный в систему шаблонизатор/DSL. T4 используется в двух вариантах: времени компиляции и времени выполнения.
T4 времени выполнения обычно используется как шаблонизатор, и означает, что вы можете легко генерировать текстовые файлы, в которых большая часть текста постоянна, а куски необходимо генерировать программно. Простой пример — шаблон письма, в который нужно вставить имя адресата, адрес и наименование товара.
Но гораздо более интересным является T4 времени разработки. Эта штука позволяет вам генерировать код на этапе компиляции (точнее, даже до неё). То есть, у вас появляется ещё одно прекрасное (хотя и немного неуклюжее — если не сравнивать с шаблонной магией Александреску) средство для настоящего метапрограммирования. Причём в отличие от решений, которые вы могли бы соорудить на коленке, T4 встроен в Visual Studio, и сгенерированные файлы просто видны в дереве Solution Explorer'а, и подхватываются компилятором.
T4 используется для кодогенерации многими проектами, начиная с Entity Framework. Вот здесь ещё достаточно впечатляющий список. Вот, например, пример того, как автоматически генерировать определения Dependency Property. (Я тоже использовал T4 для генерации из текстового представления XAML-словарей для локализации.)

Кодогенерация времени выполнения и правда не лучший метод использования T4. Для этих целей лучше подходит CodeDOM (или, кажется, Roslyn API). А для кодогенерации времени компиляции доступ к сгенерированным исходникам очень важен, например, для отладки.

Дополнение к вопросу из комментария. Да, можно в шаблоне генерировать другой шаблон.
Например, такой вот шаблон
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".tt" #>
<# string open = "<" + "#" + "@", close = "#" + ">"; #>
<#= open #> template debug="false" hostspecific="false" language="C#" <#= close #> <#= open #> assembly name="System.Core" <#= close #> <#= open #> import namespace="System.Linq" <#= close #> <#= open #> import namespace="System.Text" <#= close #> <#= open #> import namespace="System.Collections.Generic" <#= close #> <#= open #> output extension=".cs" <#= close #>
class Test { public int i; }
порождает подчинённый шаблон
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #>
class Test { public int i; }
который в свою очередь порождает класс
class Test { public int i; }
Никаких специальных действий выполнять не нужно, просто добавьте шаблон T4 через Add → New item → Text Template (не Runtime Text Template!)

В T4-файл положите текст шаблона, как он представлен выше, и вы получите такую вот необычную структуру вашего solution'а: