Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine solution configuration (debug/release) when running a T4 template

Tags:

t4

I have a T4 template that can output either optimized content or standard content based on a flag. Currently I'm manually changing the flag based on my needs.

What I'd love to do is set the flag based on the Configuration of the Solution in Visual Studio. If set to build in Debug mode, I would output standard content. If set to build in Release mode, I would optimize the content instead. I found another T4 question that looks promising: T4 Text Template - Is it possible to get compilation symbols from host?

However, in my case I would want to do something like the following:

<#@ template language="C#" hostspecific="True" 
    compilerOptions="/d:$(ConfigurationName)" #>

Since I can use $(SolutionDir) in an assembly directive:

<#@ assembly name="$(SolutionDir)\myreference.dll" #>

I would think the /d:$(ConfigurationName) would get me where I needed to go, and then I could do the following to set my flag:

<#
#if Debug 
 optimize = false;
#else 
 optimize = true;
#endif 
#>

Alas, this doesn't seem to work. I've also attempted using:

Host.ResolveParameterValue("-", "-", "ConfigurationName");

Also to no avail. Any ideas?

like image 341
Emil Lerch Avatar asked Apr 07 '11 23:04

Emil Lerch


People also ask

How to debug T4 Template?

To debug a design-time text template, save the text template file, and then choose Debug T4 Template on the shortcut menu of the file in Solution Explorer. To debug a run-time text template, simply debug the application to which it belongs.

What is TT file in C#?

TT stands for - Visual Studio Text Template is a software development tool created by the Microsoft. Further explanation - TT file contains text block and control logic used for generating new files. To write the Text Template file we can use either - Visual C# or Visual Basic Code.


3 Answers

No sooner do I ask but I find a snippet at the bottom of this MSDN article that gets me where I need to be. The answer here is to use the IServiceProvider interface to get the Visual Studio DTE. Here's code that is getting it done (apologies in advance for the hard-coded "Debug"):

    var serviceProvider = Host as IServiceProvider;
    var dte = serviceProvider.GetService(typeof(DTE)) as DTE;
    var configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
    optimize = (configName != "Debug"); 

UPDATE

This code will check to see if the active project's current configuration has optimizations turned on. It still has a hard-coded property name, but one that's much less likely to change. Also, using the project's optimization flag makes a lot of sense for my scenario (trying to decide if I should turn on optimizations in my own code):

    var serviceProvider = Host as IServiceProvider;
    var dte = serviceProvider.GetService(typeof(EnvDTE.DTE)) as DTE;
    config = dte.Solution
                .FindProjectItem(Host.TemplateFile)
                .ContainingProject
                .ConfigurationManager
                .ActiveConfiguration;
    foreach(Property prop in config.Properties)
    {
        if (prop.Name == "Optimize")
        {
            optimize = (bool)prop.Value;
            break;
        }
    }
like image 128
Emil Lerch Avatar answered Oct 19 '22 02:10

Emil Lerch


For people trying to get this work at design-time (file save) as well as at build-time (F5/F6), two methods are necessary.

Emil describes the design-time method. For build-time, you first have to specify a T4 parameter in your project file:

<ItemGroup>
  <T4ParameterValues Include="BuildConfiguration">
    <Value>$(Configuration)</Value>
    <Visible>false</Visible>    
  </T4ParameterValues>
</ItemGroup>

Then you have to reference it at the top of your .tt:

<#@ parameter type="System.String" name="BuildConfiguration" #>

And then look for whichever of them happens to be provided:

string configurationName = Host.ResolveParameterValue("-", "-", "BuildConfiguration");
if (string.IsNullOrWhiteSpace(configurationName))
{
    var serviceProvider = (IServiceProvider)Host;
    var dte = (DTE)serviceProvider.GetService(typeof(DTE));
    configurationName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
}

It is necessary to include the logic for both if you want your template to work in both scenarios. The design-time method does not work at build-time (the DTE Host is not around to provide the Solution), and the build-time method does not work at design-time (MSBuild is not around to provide the parameter).

like image 30
Calvin Fisher Avatar answered Oct 19 '22 01:10

Calvin Fisher


If trying to do this in a VS2017 ASP.Net Core project then following is a solution that worked for me with highlights from my post here.

This MSDN blog by Jeremy Kuhne and this blog by Thomas Levesque and several other links such as this MSDN doc helped get it working in VS2017.

I did not have to add anything to the beginning of the .csproj file since VS2017 has the files already included by default.

In Visual Studio 2017, the Text Template Transformation component is automatically installed as part of the Visual Studio extension devlopment workload. You can also install it from the Individual components tab of Visual Studio Installer, under the Code tools category. Install the Modeling SDK component from the Individual components tab.

I ended up with the following .csproj changes at the end of the file:

  <PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <!-- Run the Transform task at the start of every build -->
    <TransformOnBuild>true</TransformOnBuild>
    <!-- -->
    <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <!-- Transform every template every time -->
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
  </PropertyGroup>

  <!-- add AFTER import for $(MSBuildToolsPath)\Microsoft.CSharp.targets -->
  <Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

    <ItemGroup>
    <T4ParameterValues Include="BuildConfiguration">
        <Value>$(Configuration)</Value>
        <Visible>False</Visible>
    </T4ParameterValues>
  </ItemGroup>

    <Target Name="CreateT4ItemListsForMSBuildCustomTool" BeforeTargets="CreateT4ItemLists" AfterTargets="SelectItemsForTransform">
    <ItemGroup>
        <T4Transform Include="@(CreateT4ItemListsInputs)" Condition="'%(CreateT4ItemListsInputs.Generator)' == 'MSBuild:TransformAll'" />
    </ItemGroup>
  </Target>

This in the T4 Template:

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="EnvDTE" #>
<#  
    //Build time
    string configName = Host.ResolveParameterValue("-", "-", "BuildConfiguration");
    if (string.IsNullOrWhiteSpace(configName))
    {
        try
        {
            //Design time.
            var serviceProvider = (IServiceProvider)Host;
            EnvDTE.DTE dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
            configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
        }
        catch(Exception ex)
        {
            configName = ex.Message;
        }
    }
#>
<#=configName#>

The following property settings on the .tt file:

Build Action: None
Copy to Output Directory: Do Not Copy
Custom Tool: MSBuild:TransformAll
like image 1
Soenhay Avatar answered Oct 19 '22 03:10

Soenhay