Export (0) Print
Expand All

Code Generation in a Build Process

Text transformation can be invoked as part of the build process of a Visual Studio solution. There are build tasks that are specialized for text transformation. The T4 build tasks run design-time text templates, and they also compile run-time (preprocessed) text templates.

There are some differences in what the build tasks can do, depending on which build engine you use. When you build the solution in Visual Studio, a text template can access the Visual Studio API (EnvDTE) if the hostspecific="true" attribute is set. But that isn’t true when you build the solution from the command line or when you initiate a server build through Visual Studio. In those cases, the build is performed by MSBuild and a different T4 host is used.

This means that you can’t access things like project file names in the same way when you build a text template in MSBuild. However, you can pass environment information into text templates and directive processors by using build parameters.

To enable build tasks on your development computer, install Modeling SDK for Visual Studio.

If your build server runs on a computer on which Visual Studio is not installed, copy the following files to the build computer from your development machine. Substitute the most recent version numbers for ‘*’.

  • $(ProgramFiles)\MSBuild\Microsoft\VisualStudio\v*.0\TextTemplating

    • Microsoft.VisualStudio.TextTemplating.Sdk.Host.*.0.dll

    • Microsoft.TextTemplating.Build.Tasks.dll

    • Microsoft.TextTemplating.targets

  • $(ProgramFiles)\Microsoft Visual Studio *.0\VSSDK\VisualStudioIntegration\Common\Assemblies\v4.0

    • Microsoft.VisualStudio.TextTemplating.*.0.dll

    • Microsoft.VisualStudio.TextTemplating.Interfaces.*.0.dll (several files)

    • Microsoft.VisualStudio.TextTemplating.VSHost.*.0.dll

  • $(ProgramFiles)\Microsoft Visual Studio *.0\Common7\IDE\PublicAssemblies\

    • Microsoft.VisualStudio.TextTemplating.Modeling.*.0.dll

You’ll have to edit your project file to configure some of the features in MSBuild.

In solution explorer, choose Unload from the context menu of your project. That allows you to edit the .csproj or .vbproj file in the XML editor.

When you’ve finished editing, choose Reload.

In the .vbproj or .csproj file, find a line like this:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

- or -

<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />

After that line, insert the Text Templating import:

<!-- Optionally make the import portable across VS versions -->
  <PropertyGroup>
    <!-- Get the Visual Studio version – defaults to 10: -->
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <!-- Keep the next element all on one line: -->
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  </PropertyGroup>


<!-- This is the important line: -->
  <Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

There are some properties that you can insert into your project file to control the transformation task:

  • Run the Transform task at the start of every build:

    <PropertyGroup>
        <TransformOnBuild>true</TransformOnBuild>
    </PropertyGroup>
    
  • Overwrite files that are read-only, for example because they are not checked out:

    <PropertyGroup>
        <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    </PropertyGroup>
    
  • Transform every template every time:

    <PropertyGroup>
        <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
    </PropertyGroup>
    

    By default, the T4 MSBuild task regenerates an output file if it is older than its template file, or any files that are included, or any files that have previously been read by the template or by a directive processor that it uses. Notice that this is a much more powerful dependency test than is used by the Transform All Templates command in Visual Studio, which only compares the dates of the template and output file.

To perform just the text transformations in your project, invoke the TransformAll task:

msbuild myProject.csproj /t:TransformAll

To transform a specific text template:

msbuild myProject.csproj /t:Transform /p:TransformFile="Template1.tt"

You can use wildcards in TransformFile:

msbuild dsl.csproj /t:Transform /p:TransformFile="GeneratedCode\**\*.tt"

There is no specific built-in integration with a source control system. However, you can add your own extensions, for example to check out and check in a generated file.By default, the text transform task avoids overwriting a file that is marked as read- only, and when such a file is encountered, an error is logged in the Visual Studio error list, and the task fails.

To specify that read-only files should be overwritten, insert this property:

<OverwriteReadOnlyOuputFiles>true</OverwriteReadOnlyOuputFiles>

Unless you customize the postprocessing step, a warning will be logged in the error list when a file is overwritten.

Text transformation happens before other tasks in the build process. You can define tasks that are invoked before and after transformation, by setting the properties $(BeforeTransform) and $(AfterTransform):

<PropertyGroup>
    <BeforeTransform>CustomPreTransform</BeforeTransform>
    <AfterTransform>CustomPostTransform</AfterTransform>
  </PropertyGroup>
  <Target Name="CustomPreTransform">
    <Message Text="In CustomPreTransform..." Importance="High" />
  </Target>
  <Target Name="CustomPostTransform">
    <Message Text="In CustomPostTransform..." Importance="High" />
  </Target>

In AfterTransform, you can reference lists of files:

  • GeneratedFiles - a list of files written by the process. For those files that overwrote existing read-only files, %(GeneratedFiles.ReadOnlyFileOverwritten) will be true. These files can be checked out of source control.

  • NonGeneratedFiles - a list of read-only files that were not overwritten.

For example, you define a task to check out GeneratedFiles.

These properties are used only by MSBuild. They do not affect code generation in Visual Studio. They redirect the generated output file to a different folder or file. The target folder must already exist.

<ItemGroup>
  <None Include="MyTemplate.tt">
    <Generator>TextTemplatingFileGenerator</Generator>
    <OutputFilePath>MyFolder</OutputFilePath>
    <LastGenOutput>MyTemplate.cs</LastGenOutput>
  </None>
</ItemGroup>

A useful folder to redirect to is $(IntermediateOutputPath).

If you specify and output filename, it will take precedence over the extension specified in the output directive in the templates.

<ItemGroup>
  <None Include="MyTemplate.tt">
    <Generator>TextTemplatingFileGenerator</Generator>
    <OutputFileName>MyOutputFileName.cs</OutputFileName>
    <LastGenOutput>MyTemplate.cs</LastGenOutput>
  </None>
</ItemGroup>

Specifying an OutputFileName or OutputFilePath isn’t recommended if you are also transforming templates inside VS using Transform All, or running the single file generator. You will end up with different file paths depending on how you triggered the transformation. This can be very confusing.

The host has a default set of paths where it searches for assemblies referenced in templates. To add to this set:

<ItemGroup>
    <T4ReferencePath Include="$(VsIdePath)PublicAssemblies\" />
    <!-- Add more T4ReferencePath items here -->
</ItemGroup>

To set the folders that will be searched for include files, provide a semicolon-separated list. Usually you add to the existing folder list.

<PropertyGroup>
    <IncludeFolders>
$(IncludeFolders);$(MSBuildProjectDirectory)\Include;AnotherFolder;And\Another</IncludeFolders>
</PropertyGroup>

You can set parameter values in the project file. For example, you can pass build properties and environment variables:

<ItemGroup>
  <T4ParameterValues Include="ProjectFolder">
    <Value>$(ProjectDir)</Value>
    <Visible>false</Visible>
  </T4ParameterValues>
</ItemGroup>

In a text template, set hostspecific in the template directive. Use the parameter directive to get values:

<#@template language="c#" hostspecific="true"#>
<#@ parameter type="System.String" name="ProjectFolder" #>
The project folder is: <#= ProjectFolder #>

In a directive processor, you can call ResolveParameterValue:

string value = Host.ResolveParameterValue("-", "-", "parameterName");
NoteNote

ResolveParameterValue gets data from T4ParameterValues only when you use MSBuild. When you transform the template using Visual Studio, the parameters will have default values.

Visual Studio macros like $(SolutionDir) don’t work in MSBuild. You can use project properties instead.

Edit your .csproj or .vbproj file to define a project property. This example defines a property named myLibFolder:

<!-- Define a project property, myLibFolder: -->
<PropertyGroup>
    <myLibFolder>$(MSBuildProjectDirectory)\..\libs</myLibFolder>
</PropertyGroup>

<!-- Tell the MSBuild T4 task to make the property available: -->
<ItemGroup>
    <T4ParameterValues Include="myLibFolder">
      <Value>$(myLibFolder)</Value>
    </T4ParameterValues>
  </ItemGroup>

Now you can use your project property in assembly and include directives:

<#@ assembly name="$(myLibFolder)\MyLib.dll" #>
<#@ include file="$(myLibFolder)\MyIncludeFile.t4" #>

These directives get values from T4parameterValues both in MSBuild and in Visual Studio hosts.

Why would I want to transform templates in the build server? I already transformed templates in Visual Studio before I checked in my code.

If you update an included file, or another file read by the template, Visual Studio doesn’t transform the file automatically. Transforming templates as part of the build makes sure that everything’s up to date.

What other options are there for transforming text templates?

There is good guidance in the T4 MSbuild template, $(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets

Writing a T4 Text Template

Visual Studio Visualization and Modeling SDK

Oleg Sych: Understanding T4:MSBuild Integration

Show:
© 2014 Microsoft