|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Composition;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Design;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
[ExportWorkspaceServiceFactory(typeof(IFrameworkAssemblyPathResolver), ServiceLayer.Host), Shared]
internal sealed class VisualStudioFrameworkAssemblyPathResolverFactory : IWorkspaceServiceFactory
{
private readonly IThreadingContext _threadingContext;
private readonly IServiceProvider _serviceProvider;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VisualStudioFrameworkAssemblyPathResolverFactory(IThreadingContext threadingContext, SVsServiceProvider serviceProvider)
{
_threadingContext = threadingContext;
_serviceProvider = serviceProvider;
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
=> new Service(_threadingContext, workspaceServices.Workspace as VisualStudioWorkspace, _serviceProvider);
private sealed class Service(IThreadingContext threadingContext, VisualStudioWorkspace? workspace, IServiceProvider serviceProvider) : IFrameworkAssemblyPathResolver
{
private readonly IThreadingContext _threadingContext = threadingContext;
private readonly VisualStudioWorkspace? _workspace = workspace;
private readonly IServiceProvider _serviceProvider = serviceProvider;
public string? ResolveAssemblyPath(
ProjectId projectId,
string assemblyName,
string? fullyQualifiedTypeName)
{
_threadingContext.ThrowIfNotOnUIThread();
var assembly = ResolveAssembly(projectId, assemblyName);
if (assembly != null)
{
// Codebase specifies where the assembly is on disk. However, it's in
// full URI format (i.e. file://c:/...). This will allow us to get the
// actual local in the normal path format.
if (Uri.TryCreate(assembly.CodeBase, UriKind.RelativeOrAbsolute, out var uri) &&
CanResolveType(assembly, fullyQualifiedTypeName))
{
return uri.LocalPath;
}
}
return null;
}
private static bool CanResolveType(Assembly assembly, string? fullyQualifiedTypeName)
{
if (fullyQualifiedTypeName == null)
{
// nothing to resolve.
return true;
}
// We only get a type name without generic indicators. So try to few different
// generic versions of the type name in case any of those hit. it's highly
// unlikely we'd find something with more than 4 generic parameters, so only try
// up that point.
for (var i = 0; i < 5; i++)
{
var name = i == 0
? fullyQualifiedTypeName
: fullyQualifiedTypeName + "`" + i;
try
{
var type = assembly.GetType(name, throwOnError: false);
if (type != null)
{
return true;
}
}
catch (FileNotFoundException)
{
}
catch (FileLoadException)
{
}
catch (BadImageFormatException)
{
}
}
return false;
}
private Assembly? ResolveAssembly(ProjectId projectId, string assemblyName)
{
_threadingContext.ThrowIfNotOnUIThread();
if (_workspace == null)
{
return null;
}
var hierarchy = _workspace.GetHierarchy(projectId);
if (hierarchy == null ||
!hierarchy.TryGetProperty((__VSHPROPID)__VSHPROPID4.VSHPROPID_TargetFrameworkMoniker, out string? targetMoniker) ||
targetMoniker == null)
{
return null;
}
try
{
// Below we use the DesignTimeAssemblyResolver functionality of VS to
// determine if we can resolve the specified assembly name in the context
// of this project. However, this service does not do the right thing
// in UWP apps. Specifically, it *will* resolve the assembly to a
// reference assembly, even though that's never what we want. In order
// to deal with that, we put in this little check where we do not allow
// reference assembly resolution if the projects TargetFrameworkMoniker
// is ".NETCore, Version=5.0" or greater.
var frameworkName = new FrameworkName(targetMoniker);
if (StringComparer.OrdinalIgnoreCase.Equals(frameworkName.Identifier, ".NETCore") &&
frameworkName.Version >= new Version(major: 5, minor: 0))
{
return null;
}
}
catch (ArgumentException)
{
// Something wrong with our TFM. We don't have enough information to
// properly resolve this assembly name.
return null;
}
try
{
var frameworkProvider = new VsTargetFrameworkProvider(
(IVsFrameworkMultiTargeting)_serviceProvider.GetService(typeof(SVsFrameworkMultiTargeting)),
targetMoniker,
(IVsSmartOpenScope)_serviceProvider.GetService(typeof(SVsSmartOpenScope)));
return frameworkProvider.GetReflectionAssembly(new AssemblyName(assemblyName));
}
catch (InvalidOperationException)
{
// VsTargetFrameworkProvider throws InvalidOperationException in the
// some cases (like when targeting packs are missing). In that case
// we can't resolve this path.
return null;
}
}
}
}
|