-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Add infrastructure for RequestDelegateGenerator #45924
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
Conversation
47bbf9f
to
02e70ac
Compare
02e70ac
to
182f02d
Compare
Instead of disabling the generator by suppressing the emission of endpoints as in what was removed in 2d8a509, it's better to disable it by removing it from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than the 2 comments
src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.SourceGeneration.csproj
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a real quick skim over the high-level stuff. This looks really good.
<Suppression> | ||
<DiagnosticId>PKV004</DiagnosticId> | ||
<Target>net7.0</Target> | ||
</Suppression> | ||
<Suppression> | ||
<DiagnosticId>PKV004</DiagnosticId> | ||
<Target>net8.0</Target> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my learning - what are these files for? And why do we need both net7.0
and net8.0
in them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure of the reasons but I ran this delta past @wtgodbe since it was automatically introduced when I ran the GenerateProjectList
task after adding the new source generator project to the repo.
From my understanding, it's a requirement of the package validation SDK we use for validating changes across versions.
As to why we have both versions, I'm thinking we might be able to get away with just having net8
in the main branch but I'll let @wtgodbe share.
For now, I just settled with whatever the build produced here 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think net7.0 can be removed now that the source is targeting .NET 8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think net7.0 can be removed now that the source is targeting .NET 8.
Roger that. It probably makes sense to do this in a separate PR. I assume there's no harm to having net7.0
here since we've had it in the main branch for a while.
We might also want to add removing the older framework target as part of our updating branding instructions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that these files keep changing because the order of the namespaces emitted into the file keep changing. Anyone know the reason for this non-determinism?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ViktorHofer and @smasher164 own the Package Validation SDK - any idea on why there's a net7.0 & and a net8.0 section in the generated file, or why there may nondeterminism in the ordering of the file? It's been somewhat noisy lately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smasher164 can you please take a look? We might have caused a non-deterministic behavior by ordering the compatibility differences in ApiCompat. Or maybe not and something else is wrong.
@@ -4,17 +4,28 @@ | |||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> | |||
<!-- This is needed to support codepaths that use NullabilityInfoContext. --> | |||
<Features>$(Features.Replace('nullablePublicOnly', '')</Features> | |||
<PreserveCompilationContext>true</PreserveCompilationContext> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this? I thought this wasn't even used anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can test with reference assemblies, not runtime assemblies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you can view that code here.
This also means that we can run tests with changes in the repo incorporated. Say we add a new API in the framework that affects the output of the generator, this infrastructure allows us to reference those changes even if they aren't shipped yet.
src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
Outdated
Show resolved
Hide resolved
|
||
internal static partial class GeneratedRouteBuilderExtensions | ||
{{ | ||
internal class GenericThunks<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider using the new file class
feature for classes that we don't expect user code to need to interact with. That way code inside the user's project can't see these "implementation details" of our source generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will disappear anyways. @captainsafia I think on the issue for the source generator, we should output the current generated code and what the north start will look like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we would benefit the most from this in the SourceGeneratedRouteEndpointDataSource
type that is defined here but since the plan is to remove it eventually, I think the benefit is marginally and we can keep it as a nested private type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just something to keep in mind when writing a source generator - any internal
code here will be visible to the user's project. And people will definitely start using it in ways we don't expect. On the runtime team, we treat the internal code more or less like public API. If we change it in the future, it would be considered a breaking change. Thus the reason we made the language feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just something to keep in mind when writing a source generator - any internal code here will be visible to the user's project.
Yeah, we tend to exploit that aspect of the generator for our purposes here, we want user code to be able to see the strongly-typed MapAction
overloads so those overloads can be chosen and the user can opt-in to our code-generated logic.
I've added a sample of what the generator currently emits and some explanation of why it works and how it might evolve in the future in the issue here: #45524
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we tend to exploit that aspect of the generator for our purposes here, we want user code to be able to see the strongly-typed MapAction overloads so those overloads can be chosen and the user can opt-in to our code-generated logic.
Right - for the code we want the user to be able to call, we should use internal
. But for the implementation details, we don't want it visible to the user.
In your sample code, the SourceKey
class is an example of this. This is an implementation detail we don't want visible to the user. So we should use file class SourceKey
there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an implementation detail we don't want visible to the user. So we should use file class SourceKey there.
we're actually going to expose SourceKey or something like it at runtime publicly. Though, not in this PR.
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.cs
Outdated
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Parser.cs
Show resolved
Hide resolved
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs
Show resolved
Hide resolved
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Parser.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a detailed review, but I'm concerned about proper incrementality here.
Why? isn't it possible that you ship an MSBuild |
There is no NuGet package here. This source generator ships in the ASP.NET ref pack. |
Should we be adding For example, from generating log source: partial class Log
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "8.0.8.5613")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::Microsoft.AspNetCore.Http.Endpoint, global::System.Exception?> __ExecutingEndpointCallback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::Microsoft.AspNetCore.Http.Endpoint>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, "ExecutingEndpoint"), "Executing endpoint '{EndpointName}'", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "8.0.8.5613")]
public static partial void ExecutingEndpoint(global::Microsoft.Extensions.Logging.ILogger logger, global::Microsoft.AspNetCore.Http.Endpoint endpointName)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__ExecutingEndpointCallback(logger, endpointName, null);
}
}
} |
Also, generated source tends to use fully qualified type names with |
Also, what is the usual suffix for source generators projects? We currently have |
The naming here was partially inspired by the fact that the JSON source generator is under the With all that in mind, I actually think that the plural "Generators" is the way to go to match with the naming for "Analyzers" and "CodeFixers".
Good call!
I discovered this thread that talks about this a little bit further. It seems like the |
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Parser.cs
Outdated
Show resolved
Hide resolved
See the strategy employed in the Regex source generator and discussion in dotnet/runtime#66432 and dotnet/runtime#63512 for a way to not have to fully qualify everything with
|
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.cs
Outdated
Show resolved
Hide resolved
<Reference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" /> | ||
<Reference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a reference to Microsoft.CodeAnalysis.Analyzers
. It currently comes as a transitive package from Microsoft.CodeAnalysis.Common
. The direct dependency will ensure dotnet/roslyn-analyzers#6229 doesn't happen in future (I'm not sure why it doesn't happen currently).
Where's everyone barometer at with merging this once the build is green? I've addressed the major points of feedback, and there are some outstanding comments:
I want to make sure we can validate the steel thread with the generator landing in the SDK and supporting the |
I'm good with merging it, as long as you have follow up issues tracking the "TODO" pieces. We should make progress where we can. |
TODOs tracked in #45524. Let me know if i missed anything. |
Part of #45524.
This PR adds a new project to encapsulate the Minimal APIs generator:
EnableRequestDelegateGenerator
build propertyMicrosoft.AspNetCore.App
ref packThe goal of this PR is to get a barebones steel-thread working so we can validate a generator can be built, tested, shipped, and run from the SDK.