Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How should we represent source-generators / analyzers that are off by default? #30872

Open
ericstj opened this issue Feb 25, 2023 · 16 comments
Open
Assignees
Labels
untriaged Request triage from a team member

Comments

@ericstj
Copy link
Member

ericstj commented Feb 25, 2023

When we first added source generators/analyzers to our shared frameworks in .NET 6.0 they were meant to be always on and used like "add-on" features. New API that was explicitly addressed through some user gesture - like applying an attribute.

Now in .NET 8.0 more generators are coming online that intend to apply some behavior to existing code with no source changes. The user's source itself does not opt-in to using the generator, but the generator will change the user's code in some way.

One example of this is: dotnet/runtime#44493
This is meant to automatically replace usage of API that is not linker safe with source generated code that is linker safe.

There may be more in the future if we come up with additional generators that are meant to change the user's code in some meaningful way.

In light of these, we should think about how we want to represent these generators and activate them / disable them.

Today the conventions for generators / analyzers in both nuget packages and the ref-packs are "always on". There is not a great convention for disabling generators passed to the compiler. See dotnet/roslyn#55518

One case where this is happening today is

<!-- The RequestDelegateGenerator is bundled into the Microsoft.AspNetCore.App ref pack.-->
<!-- We want this generator to be disabled by default so we remove it from the list of-->
<!-- analyzers here. Since this depends on the analyzer having already been added to the-->
<!-- `Analyzer` item group by previous tasks in the build, we must wrap this work in a-->
<!-- target that executes right before the compilation loop.-->
<Target Name="RemoveRequestDelegateGenerator" BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer
Remove="@(Analyzer->HasMetadata('FileName')->WithMetadataValue('FileName', 'Microsoft.AspNetCore.Http.Generators'))"
Condition="'$(Language)'=='C#' AND '$(EnableRequestDelegateGenerator)' != 'true'"/>
</ItemGroup>
</Target>

Where ASP.NET doesn't want the generator enabled by default and runs a target to remove it. This works, but requires targets be added to the SDK for each generator, along with separate handling in the nuget package (in cases of OOB generators).

Describe the solution you'd like

Some consistent way to represent and interact with optional generators. Ideally it shouldn't involve running targets.

Potential solutions:

  1. A way to represent disabled generators in the ref packs and packages and SDK support for "enabling" them.
  2. A way to represent disabled by default generators in the actual generator API, then compiler support for "enabling" them.
  3. Some other type of gesture in the source for enabling/disabling. EG: assembly level attribute / additional data to compiler / etc.
    etc

cc @eerhardt @chsienki @dsplaisted @davidfowl @captainsafia @layomia @terrajobst @stephentoub

@captainsafia
Copy link
Member

captainsafia commented Feb 28, 2023

I'll chime in with a gotcha around the approach that the ASP.NET generators currently use that @RussKie and I recently ran into, specifically when projects reference the ASP.NET target framework but don't reference the SDK. One example might be test projects that need to reference ASP.NET APIs without being ASP.NET apps themselves. In these cases, there is no way to disable the generator without duplicating the logic that is in the Web SDK in the project's targets.

@dsplaisted
Copy link
Member

dsplaisted commented Mar 1, 2023

To me it seems like we'd probably want number 2 (having the compiler recognize that some generators are disabled by default and having a way to enable them). Otherwise we would only be able to enable and disable generators on a per-assembly basis, which seems rather limiting.

@ericstj
Copy link
Member Author

ericstj commented Mar 3, 2023

In these cases, there is no way to disable the generator without duplicating the logic that is in the Web SDK in the project's targets.

Yeah, we can't have the disabling logic in specific SDKs. It needs to be in the same place that raises the generator - which today is the .NET SDK as it resolves ref-packs. Probably we'd put it as some knob on ResolveTargetingPackAssets.

Otherwise we would only be able to enable and disable generators on a per-assembly basis, which seems rather limiting.

Yeah, it could get limiting if we have a lot of them. I could also see us just say that generators with unique enabled states need to go in unique assemblies. We're pretty granular today.

What say our compiler folks @jaredpar @chsienki @RikkiGibson? I'd also like some PMs to chime in here before we slide into building a new "AnalyzerReference" system.

@jaredpar
Copy link
Member

jaredpar commented Mar 3, 2023

Think we need to distinguish the desired behavior here:

  1. Disabled generators are passed to the compiler but do not execute. They are effectively a no-op
  2. Disabled generators are not even passed to the compiler.

This is an important distinction because it limits the type of gestures that we can support. For example:

Some other type of gesture in the source for enabling/disabling. EG: assembly level attribute / additional data to compiler /

Pretty much all of these necessitate option (1) above cause by the time we can see these data points the decisions on what generators are available has already been made.

As for the various options thrown out so far:

  • Assembly level attribute: don't think that is viable. It forces more up front binding before we can even start the generator phase. That's fine for build but probably not for IDE.
  • Source gesture: A syntactic approach may work. Imagine some hypothetical #generator enable Super.Awesome.Generator. Have to think about that some more but it seems potentially plausible.
  • MSBuild property / item group: zero problems at the compiler / IDE level.

Yeah, it could get limiting if we have a lot of them. I could also see us just say that generators with unique enabled states need to go in unique assemblies. We're pretty granular today.

At some point we need to put a cap on how many DLLs we're passing to the compiler. ASP.NET basic project passes 300+ already in just references. The compiler is fast and we cache but at some point our speed gets limited by the time it takes to read things off of disk 😄 It also increases the complexity of our analyzer load strategy (more opportunities for DLLs to mix versions). Would be nice if we didn't have to be so granular here.

@KathleenDollard, @CyrusNajmabadi, @jjonescz

@ericstj
Copy link
Member Author

ericstj commented Mar 7, 2023

How things get disabled is actually a bit more flexible than one might assume. The end result is that the generator decides not to do its thing - which can happen pretty late - exactly as late as it decides to do its thing today which can be influenced by attributes. I'm not saying this is "ideal" since it does involve some overhead for disabled generators, but then we have other overhead for untriggered analyzers, unused references, etc. Right now we have a lot of options and we're trying to get some help in deciding which is best.

At some point we need to put a cap on how many DLLs we're passing to the compiler

Indeed, but if we start caring about that granularity and combining generators then that means we need to push the decision into the compiler or generator.

We can meet to further discuss this.

@marcpopMSFT marcpopMSFT added the untriaged Request triage from a team member label Mar 8, 2023
@marcpopMSFT marcpopMSFT self-assigned this Mar 8, 2023
@ericstj
Copy link
Member Author

ericstj commented Mar 10, 2023

We met on this today. The decision was that initially we'll consolidate the analyzer removals in a common place in the SDK after it raises those items from the targeting packs. @captainsafia and @layomia -- can you collaborate on this and submit a PR to the SDK in preview 3 to do this? Probably it should occur here, but @dsplaisted can advise better:

<Target Name="ResolveTargetingPackAssets" DependsOnTargets="ResolveFrameworkReferences"

The properties we use to set these should have some consistent naming since those will be user visible (for the time being) and set by AOT tooling - @eerhardt can you weigh in on where to set such properties to enable the generators?

@dsplaisted, per the meeting today you mentioned you'd like to sketch out what general SDK support for such functionality might look like so that we could remove those targets hacks and represent the disable loggers in the targeting packs. I'll leave this issue open to track that.

@eerhardt
Copy link
Member

eerhardt commented Mar 10, 2023

@eerhardt can you weigh in on where to set such properties to enable the generators?

For ASP.NET's EnableRequestDelegateGenerator, I'm defaulting it in the Microsoft.NET.Sdk.Web targets file:

#31109

The ConfigurationBinder source generator is a bit trickier though, since it will ship both in the ASP.NET targeting pack and in a NuGet package. The defaulting logic may need to be duplicated for that one.

@ericstj
Copy link
Member Author

ericstj commented Mar 13, 2023

Where do we put other AOT-specific settings? Seems like the default is more about AOT + ASP.NET framework reference and less about the web-SDK.

IOW: won't putting the default for EnableRequestDelegateGenerator make it a problem when someone has an app that doesn't reference the WebSDK and pulishes it for AOT? Same as @captainsafia's comment here #30872 (comment)

@eerhardt
Copy link
Member

Where do we put other AOT-specific settings?

What "AOT-specific settings" are you are referring to? For dotnet/runtime it is in the AOT .targets files:

https://github.com/dotnet/runtime/blob/a84fff950a83c9c3a38e8da9eae89639f83b4f62/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets#L39-L44

For trimming-specific-settings (which also get set when publishing AOT), they are in ILLink.targets:

https://github.com/dotnet/runtime/blob/a84fff950a83c9c3a38e8da9eae89639f83b4f62/src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets#L30-L47

Neither of these places is good for "ASP.NET AOT-specific settings".

Seems like the default is more about AOT + ASP.NET framework reference and less about the web-SDK.

I'm not sure I totally get the difference. Where are we supposed to put ASP.NET's MSBuild defaulting logic, if not in the Microsoft.NET.Sdk.Web?

@dsplaisted
Copy link
Member

I'm not sure I totally get the difference. Where are we supposed to put ASP.NET's MSBuild defaulting logic, if not in the Microsoft.NET.Sdk.Web?

I'm not sure if this is what you're asking about, but the logic to disable source generators by default should go in Microsoft.NET.Sdk. That's so that (for example) a test project that references a web project won't have the source generators enabled that should be off by default.

@RussKie
Copy link
Member

RussKie commented Mar 14, 2023

That's so that (for example) a test project that references a web project won't have the source generators enabled that should be off by default.

👍 as mentioned by @captainsafia in #30872 (comment).

@captainsafia
Copy link
Member

We met on this today. The decision was that initially we'll consolidate the analyzer removals in a common place in the SDK after it raises those items from the targeting packs. @captainsafia and @layomia -- can you collaborate on this and submit a PR to the SDK in preview 3 to do this? Probably it should occur here, but @dsplaisted can advise better:

I've opened #31265 to consolidate where/how the removals happen.

@ericstj
Copy link
Member Author

ericstj commented Mar 31, 2023

@captainsafia -- @layomia and I were looking at this yesterday and noticed that you moved the place where the generator is removed, but didn't move where EnableRequestDelegateGenerator is set.

We were thinking about user scenarios and it seems like this only gets set for project that both uses the WebSdk and sets PublishAot to true. Do we think that it's likely that folks would want this if they didn't set PublishAot to true, but were still part of an AOT'ed application -- for instance in a library that makes up that application? What about if the application wasn't a WebProject, but referenced web projects and got the FrameworkReference from a ProjectReference?

@captainsafia
Copy link
Member

Assuming that you're referring to the logic here:

<!--
Enable the RequestDelegateGenerator by default for NativeAot apps.
This will be enabled for all PublishTrimmed apps once the RequestDelegateGenerator supports all MinimalApi features.
-->
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<EnableRequestDelegateGenerator Condition="'$(EnableRequestDelegateGenerator)' == ''">true</EnableRequestDelegateGenerator>
</PropertyGroup>

We set it in the WebSDK to address the specific scenario of folks who are building web apps + have AoT enabled.

Do we think that it's likely that folks would want this if they didn't set PublishAot to true, but were still part of an AOT'ed application -- for instance in a library that makes up that application?

In this case, the user would have to set the <EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator> explicitly in their library project.

What about if the application wasn't a WebProject, but referenced web projects and got the FrameworkReference from a ProjectReference?

In this case, the generator would be off-by-default because of the logic that's been moved to the ResolveTargetingPackAssets target unless they explicitly toggled.

In summary, while in preview and while AoT work in underway, we only want to override the off-by-default behavior in very specific scenarios, where projects using the WebSDK where native AoT is enabled is one of them.

For anything else, users will have to explicitly opt-in at the moment.

@ericstj
Copy link
Member Author

ericstj commented Apr 3, 2023

I'm just wondering if users will expect that they need to manually toggle EnableRequestDelegateGenerator when moving code into other projects in the same solution. What is going to make them realize they need to do that? Is it a linker warning?

@eerhardt
Copy link
Member

eerhardt commented Apr 4, 2023

if users will expect that they need to manually toggle EnableRequestDelegateGenerator when moving code into other projects in the same solution

I think this falls into the same category as any trim/aot incompatible code. I could have a solution with an app project and a library project. I decide I want to set PublishAot=true in my app. Build, and I don't get any warnings (because the app.csproj isn't doing anything incompatible with AOT). However, the library is doing incompatible things. But nothing enabled the AOT analyzer in the library. I don't get the warnings until I dotnet publish the app, which will catch all the warnings in the application including any references. This is how users will learn to set EnableAotAnalyzer=true in their library projects. Once they see publish-time warnings and want to move them up to build-time.

The same thing will happen for EnableRequestDelegateGenerator. If you move some MapGet code from the app to a library (which doesn't have the Generator enabled) you won't see warnings at build time (unless you already set EnableAotAnalyzer=true in the library project, then you will see them at build time). But once you actually publish you will see the warnings no matter what. That will be the indication that you should set EnableRequestDelegateGenerator=true in your library that calls MapGet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
untriaged Request triage from a team member
Projects
None yet
Development

No branches or pull requests

8 participants