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 Visual Studio Visualization and Modeling SDK.
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:
$(ProgramFiles)\Microsoft Visual Studio 11.0\VSSDK\VisualStudioIntegration\Common\Assemblies\v4.0
$(ProgramFiles)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\
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:
<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:
Overwrite files that are read-only, for example because they are not checked out:
Transform every template every time:
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:
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>
<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:
T4ParameterValues only works when you use MSBuild. When you transform the template using Visual Studio, the parameters will have default values.
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?