dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.07k stars 1.17k forks source link

The generated content of GeneratedInternalTypeHelper.g.cs is not stable #2690

Open tmat opened 4 years ago

tmat commented 4 years ago

Problem description:

Since switching WPF projects in Roslyn repo to Microsoft.NET.Sdk.WindowsDesktop the design time build is failing intermittently.

image

This is because we use Public API analyzer that checks public API list against a baseline. The public types defined in GeneratedInternalTypeHelper.g.cs are not listed in the baseline, but appear sometimes in the build.

I observed that during command line build GeneratedInternalTypeHelper.g.cs is created with a declaration of class GeneratedInternalTypeHelper and a few moments later it is replaced with an empty file. It seems likely that this sometimes does not happen during design time build.

Actual behavior:

Binary log for the design time build here: \\mlangfs1\public\jorobich\GenerateInternalTypeHelper

Expected behavior:

The public types are either always generated or never generated. In addition, I'd expect source file content to not be rewritten during build. An alternative approach to do this is to always generate the code but enclose it in #if. Then you can define a conditional compilation symbol and pass it to the compiler depending on whether the type should be active or not.

Minimal repro:

This is an intermittent issue happening during work in Roslyn.sln.

rladuca commented 4 years ago

This comment seems pertinent here.

https://github.com/dotnet/wpf/blob/76a8e61535542704a714b3d6f62862995129887a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/MarkupCompiler.cs#L157-L176

rladuca commented 4 years ago

Summary of my current understanding:

Markup Compilation Pass 1

Since an intellisense build does not clean generated files, you can have a situation where the initial intellisense build generates some types, but a subsequent incremental build does not since MCPass2 emptied out the file contents.

rladuca commented 4 years ago

This could be related to the temp project building the same name binary as the real project. We generate the temporary project and compile it in-between MCPass1 and MCPass2. The output assembly is named the same as the normal project's assembly. This temporary assembly will always have a version of GeneratedInternalTypeHelper with actual types inside of it as the decision to empty that file is not made until MCPass2 is over. Subsequently, the actual build of the real project can potentially be done with an emptied version of the file.

We might be able to fix this in the following fashion:

If the temporary project was completely separate from the real one (in output as well) we would always generate one assembly with a filled GeneratedInternalTypeHelper and then the real assembly would be consistently generated with or without GeneratedInternalTypeHelper based on MCPass2's result.

The build as is, should be deterministic, but depends on specific knowledge gained during the markup compilation passes. This is fairly opaque to external users. Changing this will give two distinct assemblies that will generally have unvarying inputs regardless of how the build is done.

As is, Roslyn has a workaround for their analyzers, and further issues are theoretical. We can look at this more carefully in the .NET 5 timeframe, putting this on the backlog for now.

tmat commented 4 years ago

@rladuca Thanks for the analysis.

davkean commented 4 years ago

This is a pretty annoying bug; I went to attempt to workaround it by adding it the to the "public API" but then you get the reverse error when its not generated. I've see this popup in 4 separate code bases:

1) Roslyn (http://github.com/dotnet/roslyn) 2) Project System (http://github.com/dotnet/project-system) 3) Editor (internal code base) 4) Common Project System (internal code base).

fiercekittenz commented 2 years ago

Are there any updates to this issue? I just encountered it today for the first time. I have several multi-targeted .NET projects (targeting net48 and net5.0-windows).