T4 gotchyas in your environment

By | June 15, 2013

In a previous post I wrote about the wonders of T4 and how it helped the team I’m on circumvent the loss of intellisense in our highly-decoupled Adaptive Object Model framework.

In this post I want to apprise you of the various things we had to overcome to get T4 implemented in to our build process, and a couple of things I learned along the way despite what I was finding in other blogs, on MSDN, and elsewhere.

Source Control Considerations

When you’re working with T4 within a source control environment, you want to consider the impact that auto-generating cs files upon save/build can have in your source control environment. Namely, overwrite read-only files.

This is accomplished by a Project Property in the Visual Studio project housing your T4 templates:

<PropertyGroup>
<!-- So new .CS files override anything from source control automatically -->
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
</PropertyGroup>

Nothing special about that – it’s just something that’s used by the T4 build engine to know that it should disregard the ‘read only’ status of files. In general this is fine, because it’s the T4 (.tt) files you’re changing and really care about in source control.

If you’re able, you can even set up your SCC system to ignore the .cs files spit out by your T4 transformations – that would negate the need for this as they’d never be read only because your SCC wouldn’t be locking them.

Transform templates at build time

For MSBuild (the default builder of Visual Studio solutions & projects used on build servers) to properly transform your templates when it builds your project, you need to add another Project Property to your VS project:

<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>

Always run transforms

You can feel free to take or leave this one, but I like doing it in case I change the T4 template to output some more debugging data (Console.Writeline statements or something). Without this change, the T4 builder is smart enough to know that the output won’t change, so it won’t re-run the templates (and therefore I won’t get my new Writelines).

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

As is pretty evident, it basically says “Don’t only transform out of date templates, transform them all, all the time.” I could see where some might want to carefully consider this option – for us it wasn’t too big of a deal.

Don’t transform included template files

If you’re composing your TT files from multiple others (using the <#@ include file=””#> directive), set those files you’re including to NOT transform.

image

to

image

Why do this? Basically, if you have a look at the output of your templates, you’ll see that the .cs files created only have stuff in them for the “top level” files (the ones that include the others); the included files most likely have empty .cs files. So don’t let MSBuild waste its time trying to run the T4 transformation on files that have nothing to output. The second you make this change to the Custom Tool, you’ll see the .cs subfile of your .tt file disappear.

Get your references right

Through this process, our team migrated to building using a common drive letter (in our case T:) to a relative directory. So, my “imports” in my T4 files needed to go from something like
<#@ assembly name=”T:\MyOtherComponent.dll”#>

to

<#@ assembly name=”$(SolutionDir)..\Build\MyOtherComponent.dll”#>

And boy did I have problems. Everything I read said that this should work but time and time again it didn’t. My working hypothesis is that only a subset of the VS Macros work inside T4 files. Stuff that would be at or below the current project location, maybe. At any rate, that one did not.

I tried adding additional reference paths to the Project itself

image

Nada. I tried doing an Assembly.Load in the actual T4 code of the assembly I wanted. No dice because then the <#@ import #> directive didn’t work.

What finally did work? Another Project Property:

<ItemGroup>
<T4ReferencePath Include="$(SolutionDir)..\Build" />
</ItemGroup>

This little guy sets up a reference path JUST for T4. The thing to remember about using this is that you need to make sure in your T4 files you add the assemblies according to where they’re located w/in this this set of paths. This means my assembly add became
<#@ assembly name=”MyOtherComponent.dll” #>

and it worked just fine! It’s worth noting that the T4 toolkit I am running (gives me intellisense w/in .tt files) wouldn’t properly realize this and threw up warning (red) squigglies under my reference saying it didn’t exist. Had to go on faith, cross my fingers, and MSBuild from the command line as a test.

One thing that really annoyed me about this approach was that I got an annoying little item to show up in my Solution Explorer:

image

I couldn’t let that stand, so I found this little gem:

<ItemGroup>
<T4ReferencePath Include="$(SolutionDir)..\Build">
<InProject>False</InProject>
</T4ReferencePath>
</ItemGroup>

Who knew?

 

That concludes my (hopefully helpful) braindump of my first foray in to T4 land and integrating it with an MSBuild process. I hope you find it useful as you explore the wonders of making your code write its own code!

(Be on the lookout for updates to this post if I find/recall any other useful tidbits from T4land)