Get Visual Studio to run a T4 Template on every build
How do I get a T4 template to generate its output on every build? As it is now, it only regenerates it when I make a change to the template.
I have found other questions similar to this:
T4 transformation and build order in Visual Studio (unanswered)
How to get t4 files to build in visual studio? (answers开发者_JAVA技巧 are not detailed enough [while still being plenty complicated] and don't even make total sense)
There has got to be a simpler way to do this!
I agree with GarethJ - in VS2010 it is much easier to regenerate tt templates on each build. Oleg Sych's blog describes how to do it. In short:
- Install Visual Studio SDK
- Install Visual Studio 2010 Modeling and Visualization SDK
- Open in text editor project file and
add to the end of file but before
</Project>
That's it. Open your project. On each build all *.tt templates will be reprocessed
<!-- This line could already present in file. If it is so just skip it -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- process *.tt templates on each build -->
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
I used JoelFan's answer to come up w/ this. I like it better because you don't have to remember to modify the pre-build event every time you add a new .tt file to the project.
- add TextTransform.exe to your
%PATH%
- created a batch file named transform_all.bat (see below)
- create a pre-build event "
transform_all ..\..
"
transform_all.bat
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: set the working dir (default to current dir)
set wdir=%cd%
if not (%1)==() set wdir=%1
:: set the file extension (default to vb)
set extension=vb
if not (%2)==() set extension=%2
echo executing transform_all from %wdir%
:: create a list of all the T4 templates in the working dir
dir %wdir%\*.tt /b /s > t4list.txt
echo the following T4 templates will be transformed:
type t4list.txt
:: transform all the templates
for /f %%d in (t4list.txt) do (
set file_name=%%d
set file_name=!file_name:~0,-3!.%extension%
echo: \--^> !file_name!
TextTransform.exe -out !file_name! %%d
)
echo transformation complete
There is a great NuGet package that does just this:
PM> Install-Package Clarius.TransformOnBuild
Details about the package can be found here and the GitHub repo is here.
I used MarkGr's answer and developed this solution. First, create a batch file called RunTemplate.bat in a separate tools folder above the main solution folder. The batch file just has the line:
"%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\texttransform.exe" -out %1.cs -P %2 -P "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.5" %1.tt
This batch file takes 2 parameters... %1 is the path to the .tt file without the .tt extension. %2 is the path to any DLLs referred to by Assembly directives in the template.
Next, go into the Project Properties of the project containing the T4 template. Go into Build Events and add the following Pre-build event command line:
$(SolutionDir)..\..\tools\RunTemplate.bat $(ProjectDir)MyTemplate $(OutDir)
replacing MyTemplate with filename of your .tt file (i.e. MyTemplate.tt) without the .tt extension. This will have the result of expanding the template to produce MyTemplate.cs before building the project. Then the actual build will compile MyTemplate.cs
Recently found this great VS plugin, Chirpy.
Not only does it generate your T4 on a build, but it allows T4-based approach to minification of javascript, CSS, and even lets you use LESS syntax for your CSS!
Probably the simplest way is to install a Visual Studio extension called AutoT4.
It runs all T4 templates on build automagically.
The pre-build can be reduced to a single line:
forfiles /p "$(ProjectDir)." /m "*.tt" /s /c "cmd /c echo Transforming @path && \"%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\1.2\TextTransform.exe\" @file"
This transforms all .tt
files in the project and lists them to the build output.
If you don't want the build output then you have to work around some "interesting behaviour":
forfiles /p "$(ProjectDir)." /m "*.tt" /s /c "cmd /c @\"%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\1.2\TextTransform.exe\" @file"
Of course, you can pull this out into a batch file to which you pass the project directory path if you wish.
NB The path may require some tweaking. The path above is where VS 2008 installed it on my machine; but you might find that the version number between TextTemplating
and TextTransform.exe
is different.
Check out C:\Program Files (x86)\Common Files\Microsoft Shared\TextTemplating there is a command line transformation exe in there. Alternatively write a MSBuild task with a custom host and do the transform yourself.
Thanks to GitHub.com/Mono/T4, at the moment you can do it for both .NET Core and Visual Studio builds by adding this to your .csproj
file:
<ItemGroup>
<DotNetCliToolReference Include="dotnet-t4-project-tool" Version="2.0.5" />
<TextTemplate Include="**\*.tt" />
</ItemGroup>
<Target Name="TextTemplateTransform" BeforeTargets="BeforeBuild">
<ItemGroup>
<Compile Remove="**\*.cs" />
</ItemGroup>
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet t4 %(TextTemplate.Identity)" />
<ItemGroup>
<Compile Include="**\*.cs" />
</ItemGroup>
</Target>
If you transform your templates to different programming languages you should add something like <Compile Remove="**\*.vb" />
and <Compile Include="**\*.vb" />
in order to get these files compiled even if you don't have generated files yet.
Remove
and Include
trick only needed for first time generation, or you can make the XML-shorter like this:
<ItemGroup>
<DotNetCliToolReference Include="dotnet-t4-project-tool" Version="2.0.5" />
<TextTemplate Include="**\*.tt" />
</ItemGroup>
<Target Name="TextTemplateTransform" BeforeTargets="BeforeBuild">
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet t4 %(TextTemplate.Identity)" />
</Target>
and just run build twice (for the first time). If you already have generated files committed to the repository there will be no problems on rebuilds with both examples.
In the Visual Studio you might want to see something like this:
instead of this:
So add something like this to your project file:
<ItemGroup>
<Compile Update="UInt16Class.cs">
<DependentUpon>UInt16Class.tt</DependentUpon>
</Compile>
<Compile Update="UInt32Class.cs">
<DependentUpon>UInt32Class.tt</DependentUpon>
</Compile>
<Compile Update="UInt64Class.cs">
<DependentUpon>UInt64Class.tt</DependentUpon>
</Compile>
<Compile Update="UInt8Class.cs">
<DependentUpon>UInt8Class.tt</DependentUpon>
</Compile>
</ItemGroup>
Complete example here: GitHub.com/Konard/T4GenericsExample (includes generation of multiple files from single template).
Expanding on Seth Reno and JoelFan's answers, I came up with this. With this solution don't need to remember to modify the pre-build event every time you add a new .tt file to the project.
Implementation Procedure
- Create a batch file named transform_all.bat (see below)
- Create a pre-build event
transform_all.bat "$(ProjectDir)" $(ProjectExt)
for each project with a .tt you want to build
transform_all.bat
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: set the correct path to the the app
if not defined ProgramFiles(x86). (
echo 32-bit OS detected
set ttPath=%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\
) else (
echo 64-bit OS detected
set ttPath=%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\1.2\
)
:: set the working dir (default to current dir)
if not (%1)==() pushd %~dp1
:: set the file extension (default to vb)
set ext=%2
if /i %ext:~1%==vbproj (
set ext=vb
) else if /i %ext:~1%==csproj (
set ext=cs
) else if /i [%ext%]==[] (
set ext=vb
)
:: create a list of all the T4 templates in the working dir
echo Running TextTransform from %cd%
dir *.tt /b /s | findstr /vi obj > t4list.txt
:: transform all the templates
set blank=.
for /f "delims=" %%d in (t4list.txt) do (
set file_name=%%d
set file_name=!file_name:~0,-3!.%ext%
echo: \--^> !!file_name:%cd%=%blank%!
"%ttPath%TextTransform.exe" -out "!file_name!" "%%d"
)
:: delete T4 list and return to previous directory
del t4list.txt
popd
echo T4 transformation complete
NOTES
The text transformation assumes the code in the T4 template is the same language as your project type. If this case does not apply to you, then you will have to replace the
$(ProjectExt)
argument with the extension of the files you want the code generate..TT
files must be in the project directory else they won't build. You can build TT files outside the project directory by specifying a different path as the first argument (i.e. replace"$(ProjectDir)"
with the path containing the TT files.)Remember also to set the correct path to the
transform_all.bat
batch file.
For example, I placed it in my solution directory so the pre-build event was as follows"$(SolutionDir)transform_all.bat" "$(ProjectDir)" $(ProjectExt)
If you're using Visual Studio 2010, you can use the Visual Studio Modeling and Visualization SDK: http://code.msdn.microsoft.com/vsvmsdk
This contains msbuild tasks for executing T4 templates at build time.
Have a look at Oleg's blog for more explanation: http://www.olegsych.com/2010/04/understanding-t4-msbuild-integration
Hey, my script can also parse output extension
for /r %1 %%f in (*.tt) do (
for /f "tokens=3,4 delims==, " %%a in (%%f) do (
if %%~a==extension "%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\texttransform.exe" -out %%~pnf.%%~b -P %%~pf -P "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.5" %%f
)
)
echo Exit Code = %ERRORLEVEL%
Just create transform_all.bat $(SolutionDir)
pre-build event, and all *.tt files in your solution will be transformed automaticaly.
In Visual Studio 2017 (probably next versions too), you should add this in Pre-build event:
"$(DevEnvDir)TextTransform.exe" -out "$(ProjectDir)YourTemplate.cs" "$(ProjectDir)YourTemplate.tt"
p.s. Change path to your template if it's located not in root project directory.
Dynamo.AutoTT will do what you need. You can configure it to watch files via a regex or generate on build. It also allows you to specify which T4 templates you want it to trigger.
You can download it from here : https://github.com/MartinF/Dynamo.AutoTT
Just build it, copy the dll and AddIn files into
C:\Users\Documents\Visual Studio 2012\Addins\
and away you go.
If you want to get it going in VS2012 you will need to modify the a Dynamo.AutoTT.AddIn file and set the Version to 11.0 inside the AddIn file;
You just need to add this command to the pre-build event of the project:
if $(ConfigurationName) == Debug $(MSBuildToolsPath)\Msbuild.exe /p:CustomBeforeMicrosoftCSharpTargets="$(ProgramFiles)\MSBuild\Microsoft\VisualStudio\v11.0\TextTemplating\Microsoft.TextTemplating.targets" $(ProjectPath) /t:TransformAll
The check on configuration = debug, makes sure that you don't regenerate the code in the release mode, when you do the build on the TFS build server for instance.
T4Executer does this for VS2019. You can specify templates to ignore on build, and there is a execute after build option.
Here is my solution - similar to the accepted answer. We had a problem with our source control. The target .cs files are read-only and the T4 was failing. Here is the code, that runs T4 in temp folder, compares target files, and copies it only in case of same change. It does not fix the problem with read.only files, but at least it does not occur very often:
Transform.bat
ECHO Transforming T4 templates
SET CurrentDirBackup=%CD%
CD %1
ECHO %1
FOR /r %%f IN (*.tt) DO call :Transform %%f
CD %CurrentDirBackup%
ECHO T4 templates transformed
goto End
:Transform
set ttFile=%1
set csFile=%1
ECHO Transforming %ttFile%:
SET csFile=%ttFile:~0,-2%cs
For %%A in ("%ttFile%") do Set tempTT=%TEMP%\%%~nxA
For %%A in ("%csFile%") do Set tempCS=%TEMP%\%%~nxA
copy "%ttFile%" "%tempTT%
"%COMMONPROGRAMFILES(x86)%\microsoft shared\TextTemplating\11.0\TextTransform.exe" "%tempTT%"
fc %tempCS% %csFile% > nul
if errorlevel 1 (
:: You can try to insert you check-out command here.
"%COMMONPROGRAMFILES(x86)%\microsoft shared\TextTemplating\11.0\TextTransform.exe" "%ttFile%"
) ELSE (
ECHO no change in %csFile%
)
del %tempTT%
del %tempCS%
goto :eof
:End
You can try to add your check-out command on a line (:: You can try ....)
In your project set this as a prebuild action:
Path-To-Transform.bat "$(ProjectDir)"
In visual studio 2013, right click the T4 template and set the transform on build property to true.
Here is how I tacked it. Link. Basically building on top of a great blog( blogs.clariusconsulting.net/kzu/how-to-transform-t4-templates-on-build-without-installing-a-visual-studio-sdk/ can't post more that 2 links :( ) I came up with This .targets file for use with visual studio proj files.
It's useful when you are using other dll-s inside of your .tt and you want the result to change as the dll-s are changing.
How it works:
- Create the tt, add the assembly name="$(SolutionDir)path\to\other\project\output\foo.dll and set up the transformation and result to be as expected
Remove the assembly references from .tt
Inside the proj file use this code to set up transform on build:
<PropertyGroup> <!-- Initial default value --> <_TransformExe>$(CommonProgramFiles)\Microsoft Shared\TextTemplating\10.0\TextTransform.exe</_TransformExe> <!-- If explicit VS version, override default --> <_TransformExe Condition="'$(VisualStudioVersion)' != ''">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe</_TransformExe> <!-- Cascading probing if file not found --> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\10.0\TextTransform.exe</_TransformExe> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\11.0\TextTransform.exe</_TransformExe> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\12.0\TextTransform.exe</_TransformExe> <!-- Future proof 'til VS2013+2 --> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\13.0\TextTransform.exe</_TransformExe> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\14.0\TextTransform.exe</_TransformExe> <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\15.0\TextTransform.exe</_TransformExe> <IncludeForTransform>@(DllsToInclude, '&quot; -r &quot;')</IncludeForTransform> </PropertyGroup>
First part Locates TextTransform.exe
$(IncludeForTransform)
will be equal toc:\path\to\dll\foo.dll' -r c:\path\to\dll\bar.dll
because that's the way to add references for the TextTransform on the command line<Target Name="TransformOnBuild" BeforeTargets="BeforeBuild"> <!--<Message Text="$(IncludeForTransform)" />--> <Error Text="Failed to find TextTransform.exe tool at '$(_TransformExe)." Condition="!Exists('$(_TransformExe)')" /> <ItemGroup> <_TextTransform Include="$(ProjectDir)**\*.tt" /> </ItemGroup> <!-- Perform task batching for each file --> <Exec Command=""$(_TransformExe)" "@(_TextTransform)" -r "$(IncludeForTransform)"" Condition="'%(Identity)' != ''" /> </Target>
<_TextTransform Include="$(ProjectDir)**\*.tt" />
this creates a list of all tt files inside the project and subdirectories<Exec Command="...
produces a line for each of the found .tt files that looks like"C:\path\to\Transform.exe" "c:\path\to\my\proj\TransformFile.tt" -r"c:\path\to\foo.dll" -r "c:\path\to\bar.dll"
The only thing left to do is add the paths to the dlls inside of:
<ItemGroup> <DllsToInclude Include="$(ProjectDir)path\to\foo.dll"> <InProject>False</InProject> </DllsToInclude> <DllsToInclude Include="$(ProjectDir)path\to\bar.dll"> <InProject>False</InProject> </DllsToInclude> </ItemGroup>
Here
<InProject>False</InProject>
hides these items from the Solution View
So now you should be able to generate your code on build and on change of dll-s.
You can remove the custom tool (from properties inside of Visual Studio) so the VS does not try to transform and fail miserably every time. Because we removed the assembly references in step 2
You just install NuGet Package: Clarius.TransformOnBuild
Then, every time you click Rebuild project (or Solution), your .tt files will run
Here's a pre-build event using only Microsoft Tooling and standard paths. It's tested in vs2019/netcore3.1.
Replace "AppDbContext.tt" with your project-relative file path:
"$(MSBuildBinPath)\msbuild" "$(SolutionPath)" /t:$(ProjectName):Transform /p:TransformFile="AppDbContext.tt" /p:CustomAfterMicrosoftCommonTargets="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets"
Microsoft also has a guide to make macros like "$(SolutionDirectory)" available in the template by using T4ParameterValues in your project file.
Some guy built a nuget package for this.
Side note: I get compile errors from both TextTemplate.exe and that package (because that package calls TextTemplate.exe) but not from Visual Studio. So apparently the behavior is not the same; heads up.
EDIT: This ended up being my problem.
精彩评论