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

Add analyzer redirecting API #74820

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

jjonescz
Copy link
Member

@jjonescz jjonescz commented Aug 20, 2024

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Analyzers untriaged Issues and PRs which have not yet been triaged by a lead labels Aug 20, 2024
@dotnet-policy-service dotnet-policy-service bot added the Needs API Review Needs to be reviewed by the API review council label Aug 20, 2024
@jjonescz jjonescz removed the VSCode label Aug 27, 2024
@jjonescz jjonescz marked this pull request as ready for review August 29, 2024 16:32
@jjonescz jjonescz requested review from a team as code owners August 29, 2024 16:32
@jjonescz jjonescz changed the title Add IAnalyzerAssemblyResolver public API Add analyzer redirecting public API Aug 30, 2024
@jjonescz jjonescz changed the title Add analyzer redirecting public API Add analyzer redirecting API Sep 11, 2024
{
redirectedPath = currentlyRedirectedPath;
}
else if (redirectedPath != currentlyRedirectedPath)
Copy link
Member

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.

Copy link
Member Author

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}').");
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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?

Copy link
Member Author

@jjonescz jjonescz Oct 22, 2024

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.
Copy link
Member

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.

Copy link
Member Author

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).
Copy link
Member

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?

Copy link
Member Author

@jjonescz jjonescz Oct 22, 2024

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 AnalyzerAssemblyLoaders:

  • 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;
Copy link
Member

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.

Copy link
Member Author

@jjonescz jjonescz Jan 10, 2025

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.)

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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 RikkiGibson self-assigned this Jan 13, 2025
@333fred
Copy link
Member

333fred commented Jan 21, 2025

@RikkiGibson @jaredpar for reviews

namespace Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting;

/// <summary>
/// Any MEF component implementing this interface will be used to redirect analyzer assemblies.
Copy link
Member

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?

@jaredpar
Copy link
Member

@dotnet/roslyn-ide, @jasonmalinowski PTAL

@jasonmalinowski jasonmalinowski self-requested a review January 24, 2025 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Analyzers Needs API Review Needs to be reviewed by the API review council untriaged Issues and PRs which have not yet been triaged by a lead VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants