project-system icon indicating copy to clipboard operation
project-system copied to clipboard

Microsoft.TextTemplating.targets and FastUpToDate

Open innominateAtWork opened this issue 1 year ago • 6 comments

The Microsoft.TextTemplating.targets will run t4 transformations on any .tt files in your project on build. https://learn.microsoft.com/en-us/visualstudio/modeling/code-generation-in-a-build-process?view=vs-2022&tabs=csharp

However if a .tt file (and nothing else) is changed then FastUpToDate will prevent a rebuild (and thus prevent the t4 transform from rerunning).

Ideally Microsoft.TextTemplating.targets will add .tt files and their outputs to the appropriate UpToDate item groups.

I tried to work around this with

<ItemGroup>
  <T4InFiles Include="**\*.tt" />
  <T4OutFiles Include="@(T4InFiles->'%(FullPath)'->Replace('.tt', '.cs'))" />     
        
  <UpToDateCheckInput Include="@(T4InFiles)" Set="T4Files" />
  <UpToDateCheckOutput Include="@(T4OutFiles)" Set="T4Files" />
</ItemGroup>

However that doesn't work if you have more than one .tt file you end up with a warning like the below and the build runs everytime.

WARNING: Potential build performance issue in 'MyProject.csproj'. The project does not appear up-to-date after a successful build: Input UpToDateCheckInput item 'C:\MyProject\File1.tt' is newer (2024-06-05 20:27:00.678) than earliest output 'C:\MyProject\File2.cs' (2024-06-05 20:26:28.652), not up-to-date. See https://aka.ms/incremental-build-failure.

innominateAtWork avatar Jun 06 '24 00:06 innominateAtWork

Actually the work around is just

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

the .tt files just need to be compared to the main set of outputs

It still would be nice if this was included in Microsoft.TextTemplating.targets

innominateAtWork avatar Jun 06 '24 13:06 innominateAtWork

I agree with your analysis here. Another option would be to use something like UpToDateCheckBuilt with Original metadata, so that the.tt file would be compared directly with its corresponding .cs file (given it's a 1:1 correspondence).

Microsoft.TextTemplating.targets is not defined in this repo, nor owned by this team. Could you file a feedback ticket with this suggestion? I will try and find the correct owner internally and route it to them.

drewnoakes avatar Jun 17 '24 05:06 drewnoakes

Per https://stackoverflow.com/q/77024759/24874, the generated files are considered Compile items, and they are modified after the build starts, so you can end up with overbuild resembling:

Input Compile item 'MyFile.generated.cs' has been modified since the last successful build started, not up-to-date.

As these are Compile items, and not UpToDateCheck* items, they cannot be removed from the FUTDC without also removing them from the language service.

We could consider adding support for metadata on FUTDC items (Compile, Content, etc.) that causes the FUTDC to ignore them.

For example:

<ItemGroup>
  <Compile Update="*.generated.cs" IgnoreInFastUpToDateCheck="true" />
</ItemGroup>

drewnoakes avatar Nov 26 '24 06:11 drewnoakes

Here's a repo showing an example of generating source during builds that works with the FUTDC: https://github.com/drewnoakes/generate-code-sample

drewnoakes avatar Dec 04 '24 00:12 drewnoakes

Here's a repo showing an example of generating source during builds that works with the FUTDC: https://github.com/drewnoakes/generate-code-sample

@drewnoakes I try to fix a problem exactly what is mentioned here (generating a compile item during build). I looked at your sample and tried it with VS2022 and VS2026, but for me it still shows the error that we wanted to get rid of. When I only modify an ".input" file and nothing else, the response is:

Build started at 9:13...
1>FastUpToDate: Build acceleration is enabled for this project via a feature flag. See "Tools | Options | Environment | Preview Features" to control this setting. See https://aka.ms/vs-build-acceleration. (GenerateCode)
1>FastUpToDate: Comparing timestamps of inputs and outputs: (GenerateCode)
1>FastUpToDate:     Adding UpToDateCheckBuilt outputs: (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\bin\Debug\net6.0\GenerateCode.dll (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\obj\Debug\net6.0\GenerateCode.dll (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\obj\Debug\net6.0\GenerateCode.pdb (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\bin\Debug\net6.0\GenerateCode.pdb (GenerateCode)
1>FastUpToDate:     Adding newest import input: (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\GenerateCode.csproj (GenerateCode)
1>FastUpToDate:     Adding GeneratorInput inputs: (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\Folder\Bar.input (GenerateCode)
1>FastUpToDate:         C:\Temp\ReqTest\generate-code-sample\GenerateCode\Foo.input (GenerateCode)
1>FastUpToDate: Input GeneratorInput item 'C:\Temp\ReqTest\generate-code-sample\GenerateCode\Foo.input' is newer (2025-11-27 09:13:02.178) than earliest output 'C:\Temp\ReqTest\generate-code-sample\GenerateCode\obj\Debug\net6.0\GenerateCode.pdb' (2025-11-27 09:12:40.892), not up-to-date. (GenerateCode)
1>FastUpToDate: Up-to-date check completed in 0.5 ms (GenerateCode)
1>------ Build started: Project: GenerateCode, Configuration: Debug Any CPU ------
1>C:\Program Files\dotnet\sdk\10.0.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.EolTargetFrameworks.targets(32,5): warning NETSDK1138: The target framework 'net6.0' is out of support and will not receive security updates in the future. Please refer to https://aka.ms/dotnet-core-support for more information about the support policy.
1>  GenerateCode -> C:\Temp\ReqTest\generate-code-sample\GenerateCode\bin\Debug\net6.0\GenerateCode.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 9:13 and took 00.684 seconds ==========
WARNING: Potential build performance issue in 'GenerateCode.csproj'. The project does not appear up-to-date after a successful build: Input Compile item 'C:\Temp\ReqTest\generate-code-sample\GenerateCode\obj\Debug\net6.0\Foo.cs' (2025-11-27 09:13:02.770) has been modified since the last successful build started (2025-11-27 09:13:02.445), not up-to-date. See https://aka.ms/incremental-build-failure.

And as a result, the subsequent build is not skipped but it builds the project once again.

Even a rebuild shows FUTDC warnings (at least on VS2026):

Rebuild started at 9:13...
1>------ Rebuild All started: Project: ReqnrollCalculator.Specs, Configuration: Debug Any CPU ------
Restored W:\Reqnroll\Reqnroll.ExploratoryTestProjects\ReqnrollCalculator\ReqnrollCalculator.Specs\ReqnrollCalculator.Specs.csproj (in 30 ms).
1>  ReqnrollCalculator.Specs -> W:\Reqnroll\Reqnroll.ExploratoryTestProjects\ReqnrollCalculator\ReqnrollCalculator.Specs\bin\Debug\net8.0\ReqnrollCalculator.Specs.dll
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
========== Rebuild completed at 9:14 and took 01.997 seconds ==========
WARNING: Potential build performance issue in 'ReqnrollCalculator.Specs.csproj'. The project does not appear up-to-date after a successful build: Input Compile item 'W:\Reqnroll\Reqnroll.ExploratoryTestProjects\ReqnrollCalculator\ReqnrollCalculator.Specs\Features\Addition.feature.cs' (2025-11-27 09:14:00.132) has been modified since the last successful build started (2025-11-27 09:13:59.036), not up-to-date. See https://aka.ms/incremental-build-failure.

What do I do wrong?

gasparnagy avatar Nov 27 '25 08:11 gasparnagy

OK. I can answer my question after some additional research. The problem is that since the generated files are included to the Compile group at top level (for visibility in Visual Studio), they are automatically treated as input for FUTDC. And because they are modified during build itself, in the subsequent build VS will perform an additional build, because FUTDC thinks that these files are newer then the start of the previous build.

The solution I have found for this problem is to move the inclusion of these generated items from the top-level to inside the target itself that generates them. As FUTDC will only evaluate the top level items, it will not "see" them as input, so the problem will not appear. So basically I need to remove the <Compile> lines from the props file and add the following to the target:

<ItemGroup>
  <Compile Include="@(GeneratorInput -> '%(GeneratedOutputPath)')" />
</ItemGroup>

The site-effect of this is that the generated files are not visible in Visual Studio. This might be ok in some cases, to force showing them without compromising the FUTDC, I tried to add them top-level as a None item (None items are not considered as input for FUTDC):

<None Include="@(GeneratorInput -> '%(GeneratedOutputPath)')">
  <DependentUpon>%(GeneratedDependentUpon)</DependentUpon>
  <Link>%(GeneratedLink)</Link>
</None>

This sounds like a bit of hack, but seems to work. Of course I could define an own new item group (e.g. GeneratorOutput) and make it visible via AvailableItemName. That works as well.

One additional note: this all seems to only work if the generated files are in the IntermediateOutputPath (ie obj) folder not in the main project folder (which is a good practice anyway), because if they are in the main folder, VS will add them as Compile anyway and there is no way to convince FUTDC that it should not be treated as input (even <Compile Remove="..." /> is not enough).

gasparnagy avatar Nov 27 '25 12:11 gasparnagy