Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.2k
Enable assembly resolver to ensure that Start Page and other icons can be found#1220
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
6ce7fee7b0cc1ad542218ef22d3b48c38417674dbd13b553448ee1fb7d6e8deFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,90 +1,124 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Globalization; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.VisualStudio; | ||
| using Microsoft.VisualStudio.Shell; | ||
| using NLog; | ||
| namespace GitHub.VisualStudio | ||
| { | ||
| // This is the Git service GUID, which fires early and is used by GitHubService. | ||
| //[ProvideAutoLoad(Guids.GitSccProviderId)] | ||
| // This fires before ShellInitialized and SolutionExists. | ||
| //[ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string)] | ||
| [ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string)] | ||
| [Guid(Guids.guidAssemblyResolverPkgString)] | ||
| public class AssemblyResolverPackage : Package | ||
| { | ||
| // list of assemblies that should be resolved by name only | ||
| static readonly string[] ourAssemblies = | ||
| { | ||
| // resolver is required for these | ||
| "GitHub.UI", | ||
| "GitHub.VisualStudio.UI", | ||
| // these are signed by StrongNameSigner | ||
| "Markdig", | ||
| "Markdig.Wpf", | ||
| // list of assemblies that should be considered when resolving | ||
| IEnumerable<ProvideDependentAssemblyAttribute> dependentAssemblies; | ||
| string packageFolder; | ||
| // these are included just in case | ||
| "GitHub.UI.Reactive", | ||
| "System.Windows.Interactivity" | ||
| }; | ||
| readonly string extensionDir; | ||
| IDictionary<string, Assembly> resolvingAssemblies; | ||
| IDictionary<string, Exception> resolvingExceptions; | ||
| public AssemblyResolverPackage() | ||
| { | ||
| extensionDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||
| AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyFromExtensionDir; | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| packageFolder = Path.GetDirectoryName(asm.Location); | ||
| dependentAssemblies = asm.GetCustomAttributes<ProvideDependentAssemblyAttribute>(); | ||
| resolvingAssemblies = new Dictionary<string, Assembly>(); | ||
| resolvingExceptions = new Dictionary<string, Exception>(); | ||
| AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyFromPackageFolder; | ||
| } | ||
| protected override void Dispose(bool disposing) | ||
| { | ||
| AppDomain.CurrentDomain.AssemblyResolve -= LoadAssemblyFromExtensionDir; | ||
| AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssemblyFromPackageFolder; | ||
| if (resolvingAssemblies.Count > 0 || resolvingExceptions.Count > 0) | ||
| { | ||
| // Avoid executing any logging code unless there is something to log. | ||
| WriteToLog(); | ||
| } | ||
| base.Dispose(disposing); | ||
| } | ||
| [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] | ||
| Assembly LoadAssemblyFromExtensionDir(object sender, ResolveEventArgs e) | ||
| [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] | ||
| public static Assembly ResolveDependentAssembly( | ||
| ProvideDependentAssemblyAttribute dependentAssembly, string packageFolder, AssemblyName resolveAssemblyName) | ||
| { | ||
| try | ||
| if (dependentAssembly.AssemblyName == resolveAssemblyName.Name) | ||
| { | ||
| var name = new AssemblyName(e.Name).Name; | ||
| var filename = Path.Combine(extensionDir, name + ".dll"); | ||
| if (!File.Exists(filename)) | ||
| var file = dependentAssembly.CodeBase.Replace("$PackageFolder$", packageFolder); | ||
| if (File.Exists(file)) | ||
| { | ||
| return null; | ||
| var targetAssemblyName = AssemblyName.GetAssemblyName(file); | ||
| var codeBase = dependentAssembly as ProvideCodeBaseAttribute; | ||
| if (codeBase != null) | ||
| { | ||
| if (resolveAssemblyName.FullName == targetAssemblyName.FullName) | ||
| { | ||
| return Assembly.LoadFrom(file); | ||
| } | ||
| } | ||
| var bindingRedirection = dependentAssembly as ProvideBindingRedirectionAttribute; | ||
| if (bindingRedirection != null) | ||
| { | ||
| if (resolveAssemblyName.Version >= new Version(bindingRedirection.OldVersionLowerBound) && | ||
| resolveAssemblyName.Version <= new Version(bindingRedirection.OldVersionUpperBound)) | ||
| { | ||
| resolveAssemblyName.Version = targetAssemblyName.Version; | ||
| if (resolveAssemblyName.FullName == targetAssemblyName.FullName) | ||
| { | ||
| return Assembly.LoadFrom(file); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| var targetName = AssemblyName.GetAssemblyName(filename); | ||
| return null; | ||
| } | ||
| // Resolve any exact `FullName` matches. | ||
| if (e.Name != targetName.FullName) | ||
| Assembly ResolveAssemblyFromPackageFolder(object sender, ResolveEventArgs e) | ||
| { | ||
| try | ||
| { | ||
| var resolveAssemblyName = new AssemblyName(e.Name); | ||
| foreach (var dependentAssembly in dependentAssemblies) | ||
| { | ||
| // Resolve any version of our assemblies. | ||
| if (!ourAssemblies.Contains(name, StringComparer.OrdinalIgnoreCase)) | ||
| var resolvedAssembly = ResolveDependentAssembly(dependentAssembly, packageFolder, resolveAssemblyName); | ||
| if (resolvedAssembly != null) | ||
| { | ||
| return null; | ||
| return resolvingAssemblies[e.Name] = resolvedAssembly; | ||
| } | ||
| } | ||
| return Assembly.LoadFrom(filename); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var log = string.Format(CultureInfo.CurrentCulture, | ||
| "Error occurred loading {0} from {1}.{2}{3}{4}", | ||
| e.Name, | ||
| Assembly.GetExecutingAssembly().Location, | ||
| Environment.NewLine, | ||
| ex, | ||
| Environment.NewLine); | ||
| VsOutputLogger.Write(log); | ||
| resolvingExceptions[e.Name] = ex; | ||
| } | ||
| return null; | ||
| } | ||
| void WriteToLog() | ||
| { | ||
| var log = LogManager.GetCurrentClassLogger(); | ||
| foreach (var resolvedAssembly in resolvingAssemblies) | ||
| { | ||
| log.Info(CultureInfo.InvariantCulture, "Resolved '{0}' to '{1}'.", resolvedAssembly.Key, resolvedAssembly.Value.Location); | ||
| } | ||
| foreach (var resolvingException in resolvingExceptions) | ||
| { | ||
| log.Error(CultureInfo.InvariantCulture, "Error occurred loading '{0}' from '{1}'.\n{2}", resolvingException.Key, packageFolder, resolvingException.Value); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| <ImageManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/VisualStudio/ImageManifestSchema/2014"> | ||
| <Symbols> | ||
| <!-- We need to ensure that `GitHub.VisualStudio` has loaded before any of these resources are used. --> | ||
| <!-- This should happen when `AssemblyResolverPackage` auto-loads on `UICONTEXT.NoSolution`. --> | ||
| <String Name="Resources" Value="/GitHub.VisualStudio;component/Resources/icons" /> | ||
CollaboratorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems the culprit was actually here. It tries to find an assembly by its simple name before that assembly has been loaded. I think when the There is a possible workaround for this: I did a spike to see if this might work for us, but it would involve moving a bunch of resources into | ||
| <Guid Name="guidImages" Value="{27841f47-070a-46d6-90be-a5cbbfc724ac}" /> | ||
| <ID Name="logo" Value="1" /> | ||
| <ID Name="arrow_left" Value="2" /> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using GitHub.VisualStudio; | ||
| using Microsoft.VisualStudio.Shell; | ||
| using System.Reflection; | ||
| using System.IO; | ||
| using Xunit; | ||
| namespace UnitTests.GitHub.VisualStudio | ||
| { | ||
| public class AssemblyResolverPackageTests | ||
| { | ||
| public class TheResolveDependentAssemblyMethod | ||
| { | ||
| [Fact] | ||
| public void ProvideCodeBaseAttribute_MatchFullName() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = $"$PackageFolder$\\{assemblyName}.dll"; | ||
| var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = new AssemblyName(asm.FullName); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName); | ||
| Assert.Equal(asm, resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideCodeBaseAttribute_DontMatchDifferentVersion() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = $"$PackageFolder$\\{assemblyName}.dll"; | ||
| var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = asm.GetName(); | ||
| resolveAssemblyName.Version = new Version("0.0.0.0"); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName); | ||
| Assert.Null(resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideCodeBaseAttribute_DontMatchMissingCodeBase() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = "__NothingToSeeHere___"; | ||
| var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase }; | ||
| var dependentAssemblies = new[] { provideCodeBase }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = new AssemblyName(asm.FullName); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName); | ||
| Assert.Null(resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideCodeBaseAttribute_DontMatchPartialName() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = $"$PackageFolder$\\{assemblyName}.dll" }; | ||
| var dependentAssemblies = new[] { provideCodeBase }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = new AssemblyName(asm.GetName().Name); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName); | ||
| Assert.Null(resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideBindingRedirectionAttribute_MatchOldVersionLowerBound() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = $"$PackageFolder$\\{assemblyName}.dll"; | ||
| var oldVersionLowerBound = "0.0.0.0"; | ||
| var oldVersionUpperBound = oldVersionLowerBound; | ||
| var provideBindingRedirection = new ProvideBindingRedirectionAttribute | ||
| { | ||
| AssemblyName = assemblyName, | ||
| CodeBase = codeBase, | ||
| OldVersionLowerBound = oldVersionLowerBound, | ||
| OldVersionUpperBound = oldVersionUpperBound | ||
| }; | ||
| var dependentAssemblies = new[] { provideBindingRedirection }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = asm.GetName(); | ||
| resolveAssemblyName.Version = new Version(oldVersionLowerBound); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName); | ||
| Assert.Equal(asm, resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideBindingRedirectionAttribute_MatchOldVersionUpperBound() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = $"$PackageFolder$\\{assemblyName}.dll"; | ||
| var oldVersionUpperBound = "1.1.1.1"; | ||
| var oldVersionLowerBound = oldVersionUpperBound; | ||
| var provideBindingRedirection = new ProvideBindingRedirectionAttribute | ||
| { | ||
| AssemblyName = assemblyName, | ||
| CodeBase = codeBase, | ||
| OldVersionLowerBound = oldVersionLowerBound, | ||
| OldVersionUpperBound = oldVersionUpperBound | ||
| }; | ||
| var dependentAssemblies = new[] { provideBindingRedirection }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = asm.GetName(); | ||
| resolveAssemblyName.Version = new Version(oldVersionLowerBound); | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName); | ||
| Assert.Equal(asm, resolvedAssembly); | ||
| } | ||
| [Fact] | ||
| public void ProvideBindingRedirectionAttribute_DontMatchOutOfBounds() | ||
| { | ||
| var asm = Assembly.GetExecutingAssembly(); | ||
| var assemblyName = asm.GetName().Name; | ||
| var codeBase = $"$PackageFolder$\\{assemblyName}.dll"; | ||
| var oldVersionLowerBound = "0.0.0.0"; | ||
| var oldVersionUpperBound = "1.1.1.1"; | ||
| var resolveVersion = new Version("2.2.2.2"); | ||
| var provideBindingRedirection = new ProvideBindingRedirectionAttribute | ||
| { | ||
| AssemblyName = assemblyName, | ||
| CodeBase = codeBase, | ||
| OldVersionLowerBound = oldVersionLowerBound, | ||
| OldVersionUpperBound = oldVersionUpperBound | ||
| }; | ||
| var dependentAssemblies = new[] { provideBindingRedirection }; | ||
| var packageFolder = Path.GetDirectoryName(asm.Location); | ||
| var resolveAssemblyName = asm.GetName(); | ||
| resolveAssemblyName.Version = resolveVersion; | ||
| var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName); | ||
| Assert.Null(resolvedAssembly); | ||
| } | ||
| } | ||
| } | ||
| } |
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 log the resolved assemblies and resolve errors when VS is shutdown rather than when they occur?
Uh oh!
There was an error while loading. Please reload this page.
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 a few reasons:
Trace.WriteLine).I've tried to make it as predictable and light weight as possible. :-)