I have a T4 template that I would like to be transformed when I build my project. When I add the following lines to my .csproj Visual Studio attempts to transform my template on build, but gets an exception.
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v17.0\TextTemplating\Microsoft.TextTemplating.targets" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
The exception:
Error An exception was thrown while trying to compile the transformation code. The following Exception was thrown:
System.MissingMethodException: Method not found: 'System.Collections.Immutable.ImmutableArray`1<Byte> System.Reflection.Metadata.MetadataReader.GetBlobContent(System.Reflection.Metadata.BlobHandle)'.
at Microsoft.CodeAnalysis.MetadataReaderExtensions.CreateAssemblyIdentityOrThrow(MetadataReader reader, Version version, AssemblyFlags flags, BlobHandle publicKey, StringHandle name, StringHandle culture, Boolean isReference)
at Microsoft.CodeAnalysis.MetadataReaderExtensions.ReadAssemblyIdentityOrThrow(MetadataReader reader)
at Microsoft.CodeAnalysis.PEAssembly..ctor(AssemblyMetadata owner, ImmutableArray`1 modules)
at Microsoft.CodeAnalysis.AssemblyMetadata.GetOrCreateData()
at Microsoft.CodeAnalysis.AssemblyMetadata.GetModules()
at Microsoft.CodeAnalysis.AssemblyMetadata.IsValidAssembly()
at Microsoft.CodeAnalysis.CommonReferenceManager`2.GetMetadata(PortableExecutableReference peReference, CommonMessageProvider messageProvider, Location location, DiagnosticBag diagnostics)
at Microsoft.CodeAnalysis.CommonReferenceManager`2.ResolveMetadataReferences(TCompilation compilation, Dictionary`2 assemblyReferencesBySimpleName, ImmutableArray`1& references, IDictionary`2& boundReferenceDirectiveMap, ImmutableArray`1& boundReferenceDirectives, ImmutableArray`1& assemblies, ImmutableArray`1& modules, DiagnosticBag diagnostics)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.ReferenceManager.CreateAndSetSourceAssemblyFullBind(CSharpCompilation compilation)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.ReferenceManager.CreateSourceAssemblyForCompilation(CSharpCompilation compilation)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetRuntimeMetadataVersion(EmitOptions emitOptions)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetRuntimeMetadataVersion(EmitOptions emitOptions, DiagnosticBag diagnostics)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CreateModuleBuilder(EmitOptions emitOptions, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, IEnumerable`1 manifestResources, CompilationTestData testData, DiagnosticBag diagnostics, CancellationToken cancellationToken)
at Microsoft.CodeAnalysis.Compilation.CheckOptionsAndCreateModuleBuilder(DiagnosticBag diagnostics, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, CompilationTestData testData, CancellationToken cancellationToken)
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream metadataPEStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, RebuildData rebuildData, CompilationTestData testData, CancellationToken cancellationToken)
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, RebuildData rebuildData, CancellationToken cancellationToken)
at Microsoft.VisualStudio.TextTemplating.CompilerBridge.Compile()
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.Compile(String source, String inputFile, IEnumerable`1 references, Boolean debug, SupportedLanguage language, String compilerOptions). Line=0, Column=0 Company.Projects.Abstractions C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Microsoft\VisualStudio\v17.0\TextTemplating\Microsoft.TextTemplating.targets 347
The template does execute successfully if I "Run Custom Tool" on it from withing Visual Studio. However I did notice that if I remove the following reference to the System.Memory assembly in my template I get a similar error when I "Run Custom Tool"
<#@ assembly name="System.Memory" #>
The error with that line removed:
Error Compiling transformation: The type 'ReadOnlySpan<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. Company.Project.Abstractions C:\Development\Company.Project\src\SDK\Company.Project.Abstractions\Types\Template.tt 22
How can I get my T4 Template to transform during build? It seems like it is failing to use the System.Memory assembly during build though referencing it works fine with "Run Custom Tool."
Note: the instructions at Invoke text transformation in the build process recommend finding this line in my project:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
I don't have that line in any of my projects. Adding it changes my target framework to .NET Framework 4 which breaks a whole lot of other things. Hopefully it isn't a necessary piece as adding that line causes even more problems.
Update
I reviewed my code to make sure I'm targeting netstandard2.0. Basically I built everything in a library targeting netstandard2.0 just to be sure, then brought the code back into my template.
I tried referencing System.Collections.Immutable within the template as well as install the NuGet package in the project containing the template with still no change.
Added lines for reference:
<#@ assembly name="System.Collections.Immutable" #>
<#@ import namespace="System.Collections.Immutable" #>
Finally, I attempted to reduce my template to just setting and outputting a single string but this hasn't proved fruitful. I continue to get the error. But then if I remove the Import at the top and re-add it sometimes the error will go away on the next compile. Sometimes not. This has made it very hard to identify the problem code. I think my only recourse is to start from scratch, building as I go, until I generate the error.
Update 2 Started from scratch and found the issue. Just including this assembly triggers the MissingMethodException, even without any code using it:
<#@ assembly name="System.Memory" #>
However, if I remove it adding this line back in triggers another exception:
var myArray = JsonSerializer.Deserialize<JsonArray>(fileJson);
The exception:
Compiling transformation: The type 'ReadOnlySpan<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
So it seems to do what I want to do I need to reference System.Memory, but even in the simplest code I can't reference System.Memory without introducing the original exception. For now I'm going to see if I can use alternative libraries to achieve my goal.
Add assembly directive with
System.Collections.Immutable. That may solve your problem but could also introduce some additional ones. Be aware that it is safer to use onlynetstandard2.0APIs in the template code. The confusion comes from the fact that running the template tranformation via the custom tool in Visual Studio and running it in a build via MSBuild uses different hosts with different prerequisites.