Georg
Georg

Reputation: 5781

TextTemplating target in a .Net Core project

I have recently migrated a test project to .NET Core 2.0. That test project used text templates to generate some repetitive code. The previous project had a build target to generate all T4-templates before build. Therefore, the generated code is also not checked in into the VCS.

I had used this snippet in the project to ensure that templates are built:

<PropertyGroup>
  <!-- Default VisualStudioVersion to 15 (VS2017) -->
  <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
  <!-- Determinate VSToolsPath -->
  <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  <!-- Run T4 generation if there are outdated files -->
  <TransformOnBuild>True</TransformOnBuild>
  <TransformOutOfDateOnly>True</TransformOutOfDateOnly>
</PropertyGroup>
<!-- Import TextTemplating target -->
<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

My first approach was to keep this fragment and copy it to the new .NET Core project file.

Inside Visual Studio, this works because apparently, VSToolsPath is set correctly. However, when I run the .NET Core SDK tools, as for example dotnet test (as I do on the build server), VSToolsPath maps to Program Files\dotnet\sdk\2.0.3 and there, the text templating targets cannot be found.

Because that did not work, I also tried to simply install the Microsoft.VisualStudio.TextTemplating package from Nuget but that has two problems:

  1. it does not officially support .NET Core and installs for .NET 4.6.1 and
  2. Nuget does not seem to install anything, so I cannot adjust the paths in the project file.

Upvotes: 18

Views: 6313

Answers (5)

bc3tech
bc3tech

Reputation: 1342

This is possible with msbuild today but not yet dotnet build.

Documented here.

Upvotes: 0

Rhumborl
Rhumborl

Reputation: 16609

To expand on Konard's comment of "Update: https://github.com/mono/t4 is even better."

Install Mono/T4 (dotnet-t4) as a tool

  • If you are running an Azure devops pipeline, you can add it as a step - see the first part of https://stackoverflow.com/a/60667867/1901857.

  • If you are building in a linux dockerfile, add this before you build (we were using .net 6 on alpine, but it should be fine with other distros and versions):

# you will see a warning if this folder is not on PATH
ENV PATH="${PATH}:/root/.dotnet/tools"
RUN dotnet tool install -g dotnet-t4
  • If you want to use the tool on your dev machine, instead of VS TextTemplating, run a one-off install from powershell (but every dev in your team will need to do this):
dotnet tool install -g dotnet-t4

Run t4 on build - dev machine and pipeline

dotnet-t4 is setup very similarly to t5 in Konard's answer

Option 1 - dotnet-t4 installed on your dev machine

Add this to your csproj file. No conditional settings required.

  <!-- T4 build support for .NET Core (Begin) -->

  <ItemGroup>
    <TextTemplate Include="**\*.tt" />
  </ItemGroup>

  <Target Name="TextTemplateTransform" BeforeTargets="BeforeBuild">
    <ItemGroup>
      <Compile Remove="**\*.cs" />
    </ItemGroup>
    <Exec WorkingDirectory="$(ProjectDir)" Command="t4 %(TextTemplate.Identity)" />
    <ItemGroup>
      <Compile Include="**\*.cs" />
    </ItemGroup>
  </Target>

  <!-- T4 build support for .NET Core (End) -->

Option 2 - use VS templating on your dev machine

If you don't want everyone to install the tool locally, you can still add conditional build steps to the project, as per Konard's answer. Note that Visual Studio is now 64-bit, so you can use MSBuildExtensionsPath instead of MSBuildExtensionsPath32:

  <!-- T4 build support for .NET Core (Begin) -->

  <ItemGroup Condition="'$(MSBuildRuntimeType)'=='Core'">
    <TextTemplate Include="**\*.tt" />
  </ItemGroup>

  <Target Name="TextTemplateTransform" BeforeTargets="BeforeBuild" Condition="'$(MSBuildRuntimeType)'=='Core'">
    <ItemGroup>
      <Compile Remove="**\*.cs" />
    </ItemGroup>
    <Exec WorkingDirectory="$(ProjectDir)" Command="t4 %(TextTemplate.Identity)" />
    <ItemGroup>
      <Compile Include="**\*.cs" />
    </ItemGroup>
  </Target>

  <!-- T4 build support for .NET Core (End) -->

  <!-- T4 build support for Visual Studio (Begin) -->

  <PropertyGroup Condition="'$(MSBuildRuntimeType)'=='Full'">
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <TransformOnBuild>true</TransformOnBuild>
    <!--Other properties can be inserted here--> 
    <!--Set to true to force overwriting of read-only output files, e.g. if they're not checked out (default is false)--> 
    <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <!--Set to false to transform files even if the output appears to be up-to-date (default is true)-->  
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
  </PropertyGroup>

  <Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" Condition="'$(MSBuildRuntimeType)'=='Full'" />

  <!-- T4 build support for Visual Studio (End) -->

Upvotes: 1

Konard
Konard

Reputation: 3024

To support building T4 templates while building dotnet build you need to use Custom Text Template Host, which already exists for .NET Core (https://github.com/atifaziz/t5). To include it, add to your project in any ItemGroup this element: <DotNetCliToolReference Include="T5.TextTransform.Tool" Version="1.1.0-*" />. As Visual Studio already has it's own Text Template Host implementation, your added element should be conditioned only for .NET Core. For example:

<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Core'">
    <DotNetCliToolReference Include="T5.TextTransform.Tool" Version="1.1.0-*" />
</ItemGroup>

And at the same time you should condition out of .NET Core your settings for Visual Studio's Text Template Host, like this: Condition="'$(MSBuildRuntimeType)'=='Full'".

You should also add <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition="'$(MSBuildRuntimeType)'=='Full'" /> before importing Microsoft.TextTemplating.targets to make everything work correctly with .NET Core csproj in Visual Studio.

If you need to be able to clean up all generated code, you should rename your templates from *.tt to *.Generated.tt, all the code will be generated under *.Generated.cs and it will be possible to filter these file out at dotnet clean action.

The complete example of what it will look like in your csproj:

<!-- T4 build support for .NET Core (Begin) -->

<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Core'">
  <DotNetCliToolReference Include="T5.TextTransform.Tool" Version="1.1.0-*" />
  <TextTemplate Include="**\*.Generated.tt" />
  <Generated Include="**\*.Generated.cs" />
</ItemGroup>

<Target Name="TextTemplateTransform" BeforeTargets="BeforeBuild" Condition="'$(MSBuildRuntimeType)'=='Core'">
  <ItemGroup>
    <Compile Remove="**\*.cs" />
  </ItemGroup>
  <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tt %(TextTemplate.Identity)" />
  <ItemGroup>
    <Compile Include="**\*.cs" />
  </ItemGroup>
</Target>

<Target Name="TextTemplateClean" AfterTargets="Clean">
  <Delete Files="@(Generated)" />
</Target>

<!-- T4 build support for .NET Core (End) -->


<!-- T4 build support for Visual Studio (Begin) -->

<PropertyGroup Condition="'$(MSBuildRuntimeType)'=='Full'">
  <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  <!-- This is what will cause the templates to be transformed when the project is built (default is false) -->
  <TransformOnBuild>true</TransformOnBuild>
  <!-- Set to true to force overwriting of read-only output files, e.g. if they're not checked out (default is false) -->
  <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
  <!-- Set to false to transform files even if the output appears to be up-to-date (default is true)  -->
  <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition="'$(MSBuildRuntimeType)'=='Full'" />
<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" Condition="'$(MSBuildRuntimeType)'=='Full'" />

<!-- T4 build support for Visual Studio (End) -->

If you don't want to rename your template files and you don't need to clean up them, then replace:

  <TextTemplate Include="**\*.Generated.tt" />
  <Generated Include="**\*.Generated.cs" />

with:

  <TextTemplate Include="**\*.tt" />

And delete:

<Target Name="TextTemplateClean" AfterTargets="Clean">
  <Delete Files="@(Generated)" />
</Target>

For more information see:

How to set up code generation on dotnet build: https://notquitepure.info/2018/12/12/T4-Templates-at-Build-Time-With-Dotnet-Core/

How to set up code generation on build for Visual Studio and .NET Core csproj: https://thomaslevesque.com/2017/11/13/transform-t4-templates-as-part-of-the-build-and-pass-variables-from-the-project/

The full example of the generation of multiple files from a single T4 template: https://github.com/Konard/T4GenericsExample

Update:

GitHub.com/Mono/T4 is even better.

Upvotes: 6

Tim Maes
Tim Maes

Reputation: 612

Or just use T4Executer. You can set which templates to execute before build, after build or ignore specific templates. Works good with VS2017-19

Upvotes: 0

user3456014
user3456014

Reputation:

You are at the mercy of someone writing a port for dotnet core.

This is old: http://www.bricelam.net/2015/03/12/t4-on-aspnet5.html

Or use a different templating tool entirely, though of course I understand if that’s not an option.

Upvotes: 1

Related Questions