-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add analyzer redirecting API #74820
base: main
Are you sure you want to change the base?
Add analyzer redirecting API #74820
Conversation
jjonescz
commented
Aug 20, 2024
•
edited
Loading
edited
- Part of Address analyzer and generator torn state with Visual Studio sdk#42087
- SDK counterpart: Add analyzer redirecting VSIX sdk#42861
- Design: Add analyzer redirecting proposal sdk#42437 (markdown)
IAnalyzerAssemblyResolver
public API{ | ||
redirectedPath = currentlyRedirectedPath; | ||
} | ||
else if (redirectedPath != currentlyRedirectedPath) |
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.
Given this is a path lets use string.Equals
with an explicit comparer.
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.
==
is equivalent to ordinal comparison; so did you mean to use ignore-case ordinal comparison, or just be more explicit?
} | ||
else if (redirectedPath != currentlyRedirectedPath) | ||
{ | ||
throw new InvalidOperationException($"Multiple redirectors disagree on the path to redirect '{fullPath}' to ('{redirectedPath}' vs '{currentlyRedirectedPath}')."); |
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.
The catch
below means this exception never manifests correct? It's just a Watson report mechanism?
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.
Yes, we don't want to continue if there's a mismatch, but we probably shouldn't crash either.
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
namespace Microsoft.CodeAnalysis.Diagnostics.Redirecting; |
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 did we put this into a separate namespace?
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 that we can use RestrictedInternalsVisibleTo to make this (and only this) namespace visible to the SDK counterpart.
namespace Microsoft.CodeAnalysis.Diagnostics.Redirecting; | ||
|
||
/// <summary> | ||
/// Any MEF component implementing this interface will be used to redirect analyzer 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.
It feels a bit off to be mentioning MEF at the compiler layer. True this is shared code but still feels a bit off. Maybe we should move this to the remarks section?
Also .... are there any other cases in the compiler layer where interfaces are MEF imported in workspaces? Basically an analogous example to this? I'm considering if we should go this way or possibly make workspaces define a new interface that is specifically for MEF importing then uses an adapter to bridge to this.
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.
are there any other cases in the compiler layer where interfaces are MEF imported in workspaces?
IAnalyzerAssemblyResolver is exactly like that (it's what I based IAnalyzerAssemblyRedirector on). I don't know about any others.
|
||
static string getAssemblyLocation(string fullPath, IAnalyzerAssemblyLoader assemblyLoader) | ||
{ | ||
// We use reflection because AnalyzerAssemblyLoader can come from a different assembly (it's source-shared). |
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.
What is the case where AnalyzerFileReference
doesn't have access to IAnalyzerAssemblyLoader
members that are internal
?
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.
AnalyzerFileReference
doesn't have access to AnalyzerAssemblyLoader.RedirectAssemblyPathExternally
because there are two AnalyzerAssemblyLoader
s:
- one in Microsoft.CodeAnalysis, that one is fine and AnalyzerFileReference can access it,
- one in Microsoft.CodeAnalysis.Workspaces (it's a different type, only the source code is shared), this is the one actually passed here as the parameter
assemblyLoader
, but AnalyzerFileReference cannot access it
@@ -77,6 +78,8 @@ internal abstract partial class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader | |||
/// <see cref="Assembly"/> winning.</remarks> | |||
private readonly ImmutableArray<IAnalyzerAssemblyResolver> _externalResolvers; | |||
|
|||
private readonly ImmutableArray<IAnalyzerAssemblyRedirector> _externalRedirectors; |
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.
Have a layering question. The high level view of this change is that when Visual Studio is loading analyzers / generators it is able to redirect from paths in the .NET SDK to paths inside of Visual Studio. Basically just change the path that we are loading from.
Why are we doing this work in the compiler vs. say the workspaces layer? That seems like the more natural fit to my eye. Is there a reason that it needs to be done here instead?
Note: appologies cause I think we talked about this before but it's been paged out of my brain.
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.
Note that this file is actually source-shared between Compiler and Workspaces.
But yes, we could implement this somewhere else in the IDE layers, outside these shared components. I've tried that at first, but it was difficult (more so as I'm unfamiliar with the IDE codebase).
The current implementation changes AnalyzerFileReference.FullPath to the redirected one. And AnalyzerFileReference.FullPath is then used by everything else, so we are basically done.
If this was done in IDE, we would keep the FullPath pointing to the original path and then we would need to change the IDE to know about redirecting, e.g.,
- the assembly loaders,
- Solution Explorer analyzer UI,
- some data serialization / checksums also needed changing IIRC,
- maybe more?
(And doing this in the compiler doesn't seem bad? Theoretically, the same redirecting could be done by the command line compiler if we ever wanted that for some reason.)
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 it's reasonable for the IDE to know about redirecting as they are the ones that actually do it. It also seems like it should be reflected in the IDE that they are loading the VS copy of analyzers vs. it being hidden by the compiler.
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.
Hm, I can move this into ProjectSystemProject.GetMappedAnalyzerPaths. That's what CodeStyle analyzer redirecting uses as well. I thought @chsienki said we don't want to use that API, but now I think the reason was only that Razor needs to do more complex assembly resolution which we don't need here.
I'm not sure if moving there will change anything functionally except layering, but I will try to do that to see what happens.
I think it's reasonable for the IDE to know about redirecting as they are the ones that actually do it. It also seems like it should be reflected in the IDE that they are loading the VS copy of analyzers vs. it being hidden by the compiler.
You mean it should be reflected in the UI somehow? Technically I think that's orthogonal to where this lives - even if it's in the compiler layer, it can expose some APIs which the IDE could read to see which analyzers were redirected.
Btw, IAnalyzerAssemblyResolver also lives in the compiler layer even though it's also meant to be used just by IDEs I think.
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.
Tried locally moving the functionality into GetMappedAnalyzerPaths and it seems to work fine. I can push the changes tomorrow when I polish them a bit.
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.
@jjonescz Would we be against updating the Resolver interface to support Razor's use case? I think moving CodeStyle and Razor redirection to this interface would be preferable.
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.
Would we be against updating the Resolver interface to support Razor's use case?
IAnalyzerAssemblyResolver supports Razor's use case since it was added for Razor. Although it's true that Razor redirection currently lives in both the resolver interface and GetMappedAnalyzerPaths, not sure why, perhaps backcompat.
I think moving CodeStyle and Razor redirection to this interface would be preferable.
I guess CodeStyle could be moved to use the Resolver interface, not sure, but also seems unrelated to this PR.
Note that this PR adds another interface - IAnalyzerAssemblyRedirector - which is a bit different than the Resolver one - it comes from external VSIX via MEF, that's why I made it a separate interface.
The logic of both interfaces (Redirector and Resolver) could be similar - and indeed it was before this thread where Jared asked me to move the new one (Redirector) out of compiler layer. The Resolver still lives in the compiler layer - and it needs to because it intercepts assembly loading inside AssemblyLoadContext which currently is shared between compiler and IDE so technically lives in the compiler layer. Whereas Redirector only redirects paths, so it can live in IDE layer.
@RikkiGibson @jaredpar for reviews |
namespace Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting; | ||
|
||
/// <summary> | ||
/// Any MEF component implementing this interface will be used to redirect analyzer 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.
Can we add a bit here that drives home this happens before shadow copy? Essentially this redirects the path that we pass to the compiler and the compiler will then do as it normally does with the path?
@dotnet/roslyn-ide, @jasonmalinowski PTAL |