visual-studio.net-core.net-assemblyt4

Cannot integrate TextTransformCore.exe to a Visual Studio 2022 .NET 9 project


I'm attempting to navigate the nightmare that is integration of T4 in anything past .NET 4.8.

The high-level goal is, in a single solution (possibly in a single project or otherwise), compile a utility DLL in .NET 9.0, reference it in some T4 files, and then run the T4 generation.

If the utility DLL references essentially nothing other than System, then generation works fine. However, it needs access to several symbols, any of which break the build:

etc. Introducing these symbols produces the behaviour described here:

T4 Template Could not load file or assembly 'System.Runtime, Version = 4.2.0.0'

Using .NET Core assemblies in T4 templates

where System.Runtime fails to load.

I will not be modifying assembly bindings in \Users\ as in this answer because this solution needs to be portable to other stock environments.

The extension RdjNL described in this answer does not seem possible because it does not support template parameters, and I need to be able to pass in a template parameter to load the utility assembly using T4ParameterValues.

This leaves me with an attempt to migrate to the new TextTransformCore, which is already worse in that it has no in-IDE generation support, and also refuses to work as a manual post-build step.

This .csproj should be reproducible:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Platforms>x64</Platforms>
    <TargetFramework>net9.0-windows</TargetFramework>
    <OutputType>Library</OutputType>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  </PropertyGroup>

  <PropertyGroup>
    <!-- Legacy T4-related properties -->
    <!-- Force the use of the new T4 generator. The legacy one doesn't work with newer .NET. -->
    <TextTransformPath>$(DevEnvDir)TextTransformCore.exe</TextTransformPath>

    <!--
      Custom parameter to pass into T4 so it can load the DLL.
      This is a nightmare because most project properties aren't available from an msbuild context.
    -->
    <ProtocolAssembly>$(SolutionDir)Protocol\bin\$(Platform)\$(Configuration)\$(TargetFramework)\Protocol.dll</ProtocolAssembly>
  </PropertyGroup>

  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <!-- 
    As shown, this throws
    MyTemplate.tt(0,0) : error : Running transformation: System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
    
    If -P is added:
    -P &quot;C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.0\ref\net9.0&quot;
    then the entire runtime implodes with dozens of errors like
    
    1>Error : error : There was a problem loading the assembly 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.0\ref\net9.0\System.ComponentModel.TypeConverter.dll' The following Exception was thrown:
    1>System.IO.FileLoadException: Could not load file or assembly 'System.ComponentModel.TypeConverter, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL'.
    1>   at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
    1>   at Microsoft.VisualStudio.TextTemplating.Core.SessionLoadContext.LoadFromAssemblyPath(String assemblyPath)
    1>   at Microsoft.VisualStudio.TextTemplating.Core.TransformationRunner.LoadExplicitAssemblyReferences(IEnumerable`1 references)
    1>EXEC : warning : MyTemplate.tt(1,1) : warning CS8021: Compiling transformation: CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options.
    1>EXEC : error : MyTemplate.tt(1,1) : error CS0518: Compiling transformation: CS0518: Predefined type 'System.Void' is not defined or imported
    1>EXEC : error : MyTemplate.tt(1,1) : error CS0518: Compiling transformation: CS0518: Predefined type 'System.Void' is not defined or imported
    1>EXEC : error : MyTemplate.tt(1,1) : error CS0518: Compiling transformation: CS0518: Predefined type 'System.Boolean' is not defined or imported
    1>EXEC : warning : MyTemplate.tt(1,1) : warning CS1701: Compiling transformation: CS1701: Assuming assembly reference 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Microsoft.VisualStudio.TextTemplating.Core' matches identity 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
    -->
    <Exec Command="&quot;$(TextTransformPath)&quot; -r &quot;$(ProtocolAssembly)&quot; $(ProjectDir)MyTemplate.tt" />
  </Target>
    
</Project>

with this template:

<#@ template language="C#" debug="true" linePragmas="true" hostspecific="false" visibility="internal" #>
<#@ output extension=".py" encoding="utf-8" #>

and any stub DLL that has a reference to the symbols I described above.

I am going to write a self-answer describing that downgrading to .NET 8.0 works around the issue as that's what Microsoft.VisualStudio.TextTemplating.Core also references.


Solution

  • The giveaway is this line:

    warning CS1701: Compiling transformation: CS1701: Assuming assembly reference 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Microsoft.VisualStudio.TextTemplating.Core' matches identity 'System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy

    Until I figure out something more clever for mapping .NET 8 to .NET 9, the simplest work-around is to change the DLL such that it targets .NET 8. This workaround is successful. The corresponding .csproj needs to have

    <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>