Understanding T4: MSBuild Integration
Visual Studio 2010 offers a new capability to perform template-based code generation at build-time with a set of MSBuild extensions available as part of the Visualization and Modeling SDK.
Contents
- Overview
- Configuring project for build-time template transformation
- Under the hood
- Project and Source Control Integration
- Configuring transformation environment
- Controlling transformation process
- Transforming templates by calling MSBuild explicitly
- Conclusion
- Download
Overview
Since its initial release, T4 has followed a well-established pattern of integrating code generators in Visual Studio and providing two different ways to generate code. The first and primary method of triggering template transformation process is at design time, by saving the template file. When a template (.tt) file is added to a C# or Visual Basic project, it is automatically associated with a custom tool called TextTemplatingFileGenerator. Visual Studio invokes the custom tool whenever the associated file is saved, causing the output file to be regenerated whenever the template file is modified. The second and alternative method of triggering template transformation process is at build time, with a command line utility called TextTransform. In order to generate code at build time, a developer needs to modify the MSBuild definition of a Visual Studio project and invoke this command utility for each of the template files that need to be transformed.
The main benefits of design-time code generation are its simplicity and automatic addition of the generated files in Visual Studio projects and source control. This approach works well when template files are self contained. However, when templates include other files or use external sources of metadata, such as model files or databases, the generated files may become out of date even if their templates don’t change. Build-time code generation ensures that output files are always current and makes sense in complex scenarios when keeping track of multiple templates becomes error prone. The main drawbacks of build-time code generation are the additional effort required to implement it, and more importantly, the set of challenges it presents around integration with Visual Studio projects and source control.
Visual Studio 2010 offers a new option for generating code from text templates at build time. This capability is implemented as a set of MSBuild tasks and targets that come with the Visual Studio Modeling and Visualization SDK. Here is where the files are located.
C:\>dir "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TextTemplating\v10.0" /b Microsoft.TextTemplating.Build.Tasks.dll Microsoft.TextTemplating.targets Microsoft.VisualStudio.TextTemplating.Sdk.Host.10.0.dll
The new MSBuild integration feature of T4 significantly reduces the effort required to implement and maintain build-time code generation and solves some of the challenges around integration with Visual Studio projects and source control.
Configuring project for build-time template transformation
In order to configure a Visual Studio project for build-time template transformation, you have to manually modify the MSBuild definition in the project file. At a minimum, you need to import Microsoft.TextTemplating.targets file and set TransformOnBuild property to true. Here is an extract from a C# project file that was modified this way.
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <PropertyGroup> <TransformOnBuild>true</TransformOnBuild> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
Note that Microsoft.TextTemplating.targets file has to be imported after the standard import – Microsoft.CSharp.targets for C# or Microsoft.VisualBasic.targets for Visual Basic projects. The TransformOnBuild variable can be defined anywhere in the file, such as in the main PropertyGroup of the project.
Here is a typical output you will see when building a customized project.
------ Build started: Project: T4MSBuild, Configuration: Debug x86 ------ Build started 4/10/2010 1:30:38 PM. ExecuteTransformations: Performing incremental T4 transformation Calculating whether transformed output is out of date... Transforming template Template.tt... Performing incremental T4 preprocessing Calculating whether preprocessed output is out of date... Preprocessing template PreprocessedTemplate.tt... GenerateTargetFrameworkMonikerAttribute: Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. CoreCompile: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702 /nostdlib+ /platform:x86 /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" /debug+ /out:obj\x86\Debug\T4MSBuild.dll /target:library PreprocessedTemplate.cs Template.cs "C:\Users\osych\AppData\Local\Temp\.NETFramework,Version=v4.0.AssemblyAttributes.cs" CopyFilesToOutputDirectory: Copying file from "obj\x86\Debug\T4MSBuild.dll" to "bin\Debug\T4MSBuild.dll". T4MSBuild -> C:\T4MSBuild\T4MSBuild\bin\Debug\T4MSBuild.dll Copying file from "obj\x86\Debug\T4MSBuild.pdb" to "bin\Debug\T4MSBuild.pdb". Build succeeded. Time Elapsed 00:00:00.55 ========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
In this sample output, note that a file called Template.tt was transformed and another file called PreprocessedTemplate.tt was preprocessed. In other words, the MSBuild functionality fully supports the preprocessed templates introduced in Visual Studio 2010.
Under the hood
When imported, Microsoft.TextTemplating.targets file modifies the normal project build process defined in Microsoft.Common.targets by inserting the following steps (targets) in the beginning of the build sequence.
- Create a list of candidate files for transformation
- Execute user-defined target before transformation
- Select template files for processing
- Transform and/or preprocess templates
- Execute user-defined target after transformation
T4 creates a list of candidate files for transformation by selecting all files in the project that have build action of None, Compile, Content or EmbeddedResource. Normally, .tt files have build action of None and get included automatically.
After the initial list of candidate files has been created, T4 invokes a user-defined target specified in the $(BeforeTransform) property. You can assign this property and provide a custom target if you need to perform any actions before the templates are processed.
After executing the user-defined BeforeTransform target, T4 will use the initial candidate list of files to select those associated with the TextTemplatingFileGenerator custom tool to be transformed in a set of items called @(T4TransformInputs) and those associated with the TextTemplatingFilePreprocessor custom tool to be pre-processed in a set of items called @(T4PreprocessInputs). It is important to remember that template files are selected for processing based on their association with one of the T4’s custom tools; the file extension or build action have no effect in this process.
After determining the actual lists of templates, T4 transforms and pre-processes them. This is done by the MSBuild tasks called TransformTemplates and PreprocessTemplates implemented in Microsoft.TextTemplating.Build.Tasks.dll. These tasks use a custom T4 host implemented in Microsoft.VisualStudio.TextTemplating.Sdk.Host.10.0.dll.
In T4 architecture, the host plays a very important role in the template transformation process. It provides the compilation and run-time environment for the code generation and determines how the included files are located, how the assembly references are resolved, how directive processors are selected and more. Each of the existing T4 hosts is implemented and configured differently, which can make them incompatible with templates that take advantage of the unique functionality of a particular host. For example, T4 templates in ASP.NET MVC and T4 Toolbox rely on the unique features of the MVC and Visual Studio hosts respectively. They are currently incompatible with the MSBuild host described here.
Even templates that don’t have hard-coded dependencies on a particular host will often require additional steps to configure the transformation environment before they can be processed by the MSBuild host. Detailed description of the configuration options is available later in this article.
After transforming and preprocessing templates, T4 invokes a second user-defined target specified in the $(AfterTransform) property. You can assign this variable and provide a custom target if you need to perform any actions when the templates are transformed but before the build starts.
Project and Source Control Integration
Unlike the standard Visual Studio host of T4, the MSBuild host described here does not integrate generated files with Visual Studio projects or source control. In particular, the generated files are not added to the project and source control automatically; the regenerated files are not checked out from source control automatically. This integration functionality relies on services provided by Visual Studio, which is not available in the MSBuild host.
Luckily, a limited awareness of source control exists in the MSBuild host of T4. By default, it will issue a warning for each read-only output file that could not be regenerated and an error, making sure that build will fail if one or more files could not be regenerated.
In order to resolve these errors, you can either locate and check out all required files manually, or transform all templates from the toolbar in Solution Explorer, which will process them using the Visual Studio host and check out all files automatically. Once the files have been regenerated, you will typically want to check them back in source control.
You can also instruct T4 to automatically overwrite read-only files during code generation by setting the $(OverwriteReadOnlyOutputFiles) property to true.
<PropertyGroup> <TransformOnBuild>true</TransformOnBuild> <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
This setting eliminates build errors, but introduces discrepancies between the code checked in the source control repository and the actual code compiled during the build. With this approach you can no longer rely on the source control repository alone to recreate a particular labeled build. If you want to debug or troubleshoot a particular build of the application later, you will have to save the complete source code in addition to the binary and symbol files produced by each build. This is necessary for all but trivial scenarios, and especially for packaged commercial software products and enterprise applications.
Configuring transformation environment
MSBuild functionality of T4 relies on a new engine host, which is different from both the default host used by the T4 custom tools in Visual Studio and the command line host used by TextTransform.exe. It relies on MSBuild items and properties to configure normal settings such as include search path, directive processors and assembly references, as well as settings unique to the MSBuild host.
$(IncludeFolders) property
Specifies a coma-separated list of directories that T4 will search to resolve files referenced by include directives. This property is empty by default.
<PropertyGroup> <IncludeFolders>$(MSBuildProjectDirectory)\Include</IncludeFolders> </PropertyGroup>
$(IncludeFolders) property is an equivalent of the –I parameter of the command-line host and the HKLM\Software\Microsoft\VisualStudio\10.0\TextTemplating\IncludeFolders registry setting of the Visual Studio host of T4.
@(T4ReferencePath) items
Specify individual folders where T4 will search to resolve assemblies referenced by assembly directives and custom directive processors. This item list is empty by default.
<ItemGroup> <T4ReferencePath Include="$(VsInstallDir)PublicAssemblies\" /> </ItemGroup>
@(T4ReferencePath) items are an equivalent of the –P parameter of the command-line host and have no direct equivalent in the Visual Studio host of T4, which uses global assembly cache, assemblies referenced by the project and some hard-coded search paths.
@(DirectiveProcessor) items
Specify definitions of the directive processors T4 will use when processing custom directives. This item list is empty by default.
<ItemGroup> <DirectiveProcessor Include="T4Toolbox.XsdProcessor" > <Class>T4Toolbox.XsdProcessor</Class> <CodeBase>C:\Program Files (x86)\T4 Toolbox\Bin\T4Toolbox.10.0.dll</CodeBase> </DirectiveProcessor> </ItemGroup>
Assembly location can be specified using either CodeBase or Assembly metadata item which allows you to reference the assembly from disk or from global assembly cache respectively.
<ItemGroup> <DirectiveProcessor Include="T4Toolbox.XsdProcessor" > <Class>T4Toolbox.XsdProcessor</Class> <Assembly>T4Toolbox.10.0, Version=10.3.7.1, Culture=neutral, PublicKeyToken=7e313accbcce84dc</Assembly> </DirectiveProcessor> </ItemGroup>
@(DirectiveProcessor) items are an equivalent of the –db option of the command-line host and the HKLM\Software\Microsoft\VisualStudio\10.0\TextTemplating\DirectiveProcessors registry setting of the Visual Studio host of T4.
@(T4ParameterValues) items
Specify parameters that custom directive processors and templates themselves can query using ResolveParameterValue method. This item list is empty by default.
<ItemGroup> <T4ParameterValues Include="ParameterName"> <Value>ParameterValue</Value> </T4ParameterValues> </ItemGroup>
@(T4ParameterValues) items are an equivalent of the –a option of the command-line host and have no direct equivalent in the Visual Studio host of T4.
.OutputFilePath metadata
Specifies the directory where template’s output file will be generated. This metadata item is empty by default.
<ItemGroup> <None Include="Template.tt"> <Generator>TextTemplatingFileGenerator</Generator> <OutputFilePath>$(IntermediateOutputPath)</OutputFilePath> <LastGenOutput>Template.cs</LastGenOutput> </None> </ItemGroup>
By default, T4 will place generated output file in the same directory with the template file. You can set this property to redirect generated output files to a different directory during build-time code generation. This property has no effect on design-time code generation performed by the TextTemplatingFileGenerator or TextTemplatingFilePreprocessor when the template file is saved in Visual Studio editor. Setting this property will result in different behavior in code generation at design time vs. build time.
.OutputFilePath metadata is an equivalent of the –out option of the command-line host and has no direct equivalent in the Visual Studio host of T4.
.OutputFileName metadata
Specifies the name of the generated output file. This metadata item is empty by default.
<ItemGroup> <None Include="Template.tt"> <Generator>TextTemplatingFileGenerator</Generator> <OutputFileName>Generated.cs</OutputFileName> <LastGenOutput>Template.cs</LastGenOutput> </None> </ItemGroup>
By default, T4 will name generated output file with the name of the template file and the extension specified in the output directive in the template. You can set this property to change the default file name during build-time code generation. This property has no effect on design-time code generation performed by the TextTemplatingFileGenerator or TextTemplatingFilePreprocessor when the template file is saved in Visual Studio editor. Setting this property will result in different behavior in code generation at design time vs. build time.
.OutputFileName metadata is an equivalent of the –out option of the command-line host and has no direct equivalent in the Visual Studio host of T4.
$(IncludeDslT4Settings) property
Determines whether DSL-specific settings should be turned on. This property can be set to true or false and has a default value of false.
<PropertyGroup> <IncludeDslT4Settings>true</IncludeDslT4Settings> </PropertyGroup>
This is a convenience property that configures various settings required for performing build-time template transformation in DSL projects.
Controlling transformation process
This section lists the various configuration settings that can be used to control the T4 template transformation process in MSBuild project files.
$(TransformOnBuild) property
Determines whether templates will be transformed at build-time automatically. This property can be set to true or false and has a default value of false. When $(TransformOnBuild) is false, templates will be transformed at design-time in Visual Studio and can also be transformed by calling MSBuild explicitly.
<PropertyGroup> <TransformOnBuild>true</TransformOnBuild> </PropertyGroup>
$(TransformOutOfDateOnly) property
Controls incremental template transformation and indicates whether only out of date or all templates should be processed. This property can be set to true or false and has a default value of true.
<PropertyGroup> <TransformOutOfDateOnly>false</TransformOutOfDateOnly> </PropertyGroup>
An out-of-date file is one where any of the input files used to generated the file were modified more recently than the file itself. The input files are the included template files and the files referenced by custom directive processors. In addition, a file that contains only “ErrorGeneratingOutput” written to it when the last transformation failed is also treated as being out of date.
T4 relies on the file tracking feature in MSBuild by logging all of the file read / write operations as they occur when generating an output file. If the tracking logs do not exist for a project, T4 assumes that the template is out of date and transforms it.
$(TrackFileAccess) property
Determines whether T4 will log file read / write operations to allow subsequent incremental template transformations. This property can be set to true or false and has a default value of true.
<PropertyGroup> <TrackFileAccess>false</TrackFileAccess> </PropertyGroup>
If you don’t rely on incremental template transformations, you may want to set this property to false to reduce the amount of space taken by the log files in the intermediate output directory (obj\Debug).
$(OverwriteReadOnlyOutputFiles) property
Indicates whether read-only output files will be overwritten during template transformation. This property can be set to true or false and has a default value of false.
<PropertyGroup> <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> </PropertyGroup>
When $(OverwriteReadOnlyOutputFiles) is false, T4 issues a warning for each output file that could not be regenerated because it is read-only and the build will fail. When $(OverwriteReadOnlyOutputFiles) is true, T4 will overwrite read-only files and report a warning for each overwritten file. These warnings are not considered to be compiler warnings for the purpose of the “Treat warnings as errors” project setting.
$(BeforeTransform) property
Specifies a user-defined MSBuild target that will be executed before template transformation. This property is empty by default.
<PropertyGroup> <TransformOnBuild>true</TransformOnBuild> <BeforeTransform>OnBeforeTransform</BeforeTransform> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" /> <Target Name="OnBeforeTransform"> <Message Text="This executes before templates are transformed" Importance="high"/> </Target>
$(AfterTransform) property
Specifies a user-defined MSBuild target that will be executed before template transformation. This property is empty by default.
<PropertyGroup> <TransformOnBuild>true</TransformOnBuild> <AfterTransform>OnAfterTransform</AfterTransform> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" /> <Target Name="OnAfterTransform"> <Message Text="This executes after templates are transformed" Importance="high"/> </Target>
@(GeneratedFiles) items
Are produced by by the TransformTemplates and PreprocessTemplates to indicate output files that were successfully generated. @(GeneratedFiles) have .ReadOnlyFileOverwritten metadata item set to true when $(OverwriteReadOnlyOutputFiles) property is set to true and a particular read-only output file was overwritten during code generation. @(GeneratedFiles) items can be used in the user-defined AfterTransform target.
@(NonGeneratedFiles) items
Are produced by by the TransformTemplates and PreprocessTemplates to indicate output files that could not be regenerated. The list of @(NonGeneratedFiles) can be examined in the user-defined AfterTransform target.
Transforming templates by calling MSBuild explicitly
In addition to automatically transforming templates at build time, T4 provides two targets you can call explicitly to transform or preprocess templates in a particular project by invoking MSBuild from command line.
TransformAll target
Calling the TransformAll target will transform all files associated with TextTemplatingFileGenerator and TextTemplatingFilePreprocessor in the specified project. It is an equivalent of setting $(TransformOnBuild) to true and performing template transformation at build time.
Syntax
msbuild t4msbuild.csproj /t:TransformAll
Sample Output
Transform target
Transform target allows you to specify which file or files have to be transformed. It expects you to provide a value for TransformFile property, which accepts individual file names as well as MSBuild wildcards.
Syntax
msbuild t4msbuild.csproj /t:Transform /p:TransformFile=*.tt
Sample Output
Conclusion
Visual Studio 2010 introduced a new capability based on MSBuild extensions for transforming and preprocessing text templates at build time. The new capability comes with a set of distinct advantages and drawbacks. Compared to the existing command line tool, TextTransform, it makes implementation and maintenance of build-time template transformation easier. However, compared to the existing Visual Studio tools, it does not support project and source control integration. The MSBuild extensions also rely on a brand-new T4 engine host, with a unique compilation and run-time environment for text templates. Templates that take advantage of specific capabilities provided by a particular host (such as ASP.NET MVC and T4 Toolbox templates) are not be compatible with the new MSBuild host. Templates designed to be host-agnostic, may require you to configure the MSBuild host before they can be processed correctly. In particular, locations of the included files, .NET assemblies and definitions of custom directive processors have to be supplied explicitly.
Download
- Visual Studio 2010 SDK (pre-requisite)
- Visual Studio 2010 Modeling and Visualization SDK (pre-requisite)



[…] Understanding T4: MSBuild Integration (Oleg Sych) […]