Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 75a6572

Browse files
authored
Merge pull request #1220 from github/fixes/1217-devenv-config-not-updated
Enable assembly resolver to ensure that `Start Page` and other icons can be found
2 parents 37690f7 + 7d6e8de commit 75a6572

File tree

4 files changed

+241
-46
lines changed

4 files changed

+241
-46
lines changed
Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,124 @@
11
using System;
22
using System.IO;
3-
using System.Linq;
43
using System.Reflection;
54
using System.Globalization;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Runtime.InteropServices;
78
using Microsoft.VisualStudio;
89
using Microsoft.VisualStudio.Shell;
10+
using NLog;
911

1012
namespace GitHub.VisualStudio
1113
{
12-
// This is the Git service GUID, which fires early and is used by GitHubService.
13-
//[ProvideAutoLoad(Guids.GitSccProviderId)]
1414
// This fires before ShellInitialized and SolutionExists.
15-
//[ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string)]
15+
[ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string)]
1616
[Guid(Guids.guidAssemblyResolverPkgString)]
1717
public class AssemblyResolverPackage : Package
1818
{
19-
// list of assemblies that should be resolved by name only
20-
static readonly string[] ourAssemblies =
21-
{
22-
// resolver is required for these
23-
"GitHub.UI",
24-
"GitHub.VisualStudio.UI",
25-
26-
// these are signed by StrongNameSigner
27-
"Markdig",
28-
"Markdig.Wpf",
19+
// list of assemblies that should be considered when resolving
20+
IEnumerable<ProvideDependentAssemblyAttribute> dependentAssemblies;
21+
string packageFolder;
2922

30-
// these are included just in case
31-
"GitHub.UI.Reactive",
32-
"System.Windows.Interactivity"
33-
};
34-
35-
readonly string extensionDir;
23+
IDictionary<string, Assembly> resolvingAssemblies;
24+
IDictionary<string, Exception> resolvingExceptions;
3625

3726
public AssemblyResolverPackage()
3827
{
39-
extensionDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
40-
AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyFromExtensionDir;
28+
var asm = Assembly.GetExecutingAssembly();
29+
packageFolder = Path.GetDirectoryName(asm.Location);
30+
dependentAssemblies = asm.GetCustomAttributes<ProvideDependentAssemblyAttribute>();
31+
resolvingAssemblies = new Dictionary<string, Assembly>();
32+
resolvingExceptions = new Dictionary<string, Exception>();
33+
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyFromPackageFolder;
4134
}
4235

4336
protected override void Dispose(bool disposing)
4437
{
45-
AppDomain.CurrentDomain.AssemblyResolve -= LoadAssemblyFromExtensionDir;
38+
AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssemblyFromPackageFolder;
39+
40+
if (resolvingAssemblies.Count > 0 || resolvingExceptions.Count > 0)
41+
{
42+
// Avoid executing any logging code unless there is something to log.
43+
WriteToLog();
44+
}
45+
4646
base.Dispose(disposing);
4747
}
4848

49-
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
50-
Assembly LoadAssemblyFromExtensionDir(object sender, ResolveEventArgs e)
49+
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
50+
public static Assembly ResolveDependentAssembly(
51+
ProvideDependentAssemblyAttribute dependentAssembly, string packageFolder, AssemblyName resolveAssemblyName)
5152
{
52-
try
53+
if (dependentAssembly.AssemblyName == resolveAssemblyName.Name)
5354
{
54-
var name = new AssemblyName(e.Name).Name;
55-
var filename = Path.Combine(extensionDir, name + ".dll");
56-
if (!File.Exists(filename))
55+
var file = dependentAssembly.CodeBase.Replace("$PackageFolder$", packageFolder);
56+
if (File.Exists(file))
5757
{
58-
return null;
58+
var targetAssemblyName = AssemblyName.GetAssemblyName(file);
59+
60+
var codeBase = dependentAssembly as ProvideCodeBaseAttribute;
61+
if (codeBase != null)
62+
{
63+
if (resolveAssemblyName.FullName == targetAssemblyName.FullName)
64+
{
65+
return Assembly.LoadFrom(file);
66+
}
67+
}
68+
69+
var bindingRedirection = dependentAssembly as ProvideBindingRedirectionAttribute;
70+
if (bindingRedirection != null)
71+
{
72+
if (resolveAssemblyName.Version >= new Version(bindingRedirection.OldVersionLowerBound) &&
73+
resolveAssemblyName.Version <= new Version(bindingRedirection.OldVersionUpperBound))
74+
{
75+
resolveAssemblyName.Version = targetAssemblyName.Version;
76+
if (resolveAssemblyName.FullName == targetAssemblyName.FullName)
77+
{
78+
return Assembly.LoadFrom(file);
79+
}
80+
}
81+
}
5982
}
83+
}
6084

61-
var targetName = AssemblyName.GetAssemblyName(filename);
85+
return null;
86+
}
6287

63-
// Resolve any exact `FullName` matches.
64-
if (e.Name != targetName.FullName)
88+
Assembly ResolveAssemblyFromPackageFolder(object sender, ResolveEventArgs e)
89+
{
90+
try
91+
{
92+
var resolveAssemblyName = new AssemblyName(e.Name);
93+
foreach (var dependentAssembly in dependentAssemblies)
6594
{
66-
// Resolve any version of our assemblies.
67-
if (!ourAssemblies.Contains(name, StringComparer.OrdinalIgnoreCase))
95+
var resolvedAssembly = ResolveDependentAssembly(dependentAssembly, packageFolder, resolveAssemblyName);
96+
if (resolvedAssembly != null)
6897
{
69-
return null;
98+
return resolvingAssemblies[e.Name] = resolvedAssembly;
7099
}
71100
}
72-
73-
return Assembly.LoadFrom(filename);
74101
}
75102
catch (Exception ex)
76103
{
77-
var log = string.Format(CultureInfo.CurrentCulture,
78-
"Error occurred loading {0} from {1}.{2}{3}{4}",
79-
e.Name,
80-
Assembly.GetExecutingAssembly().Location,
81-
Environment.NewLine,
82-
ex,
83-
Environment.NewLine);
84-
VsOutputLogger.Write(log);
104+
resolvingExceptions[e.Name] = ex;
85105
}
86106

87107
return null;
88108
}
109+
110+
void WriteToLog()
111+
{
112+
var log = LogManager.GetCurrentClassLogger();
113+
foreach (var resolvedAssembly in resolvingAssemblies)
114+
{
115+
log.Info(CultureInfo.InvariantCulture, "Resolved '{0}' to '{1}'.", resolvedAssembly.Key, resolvedAssembly.Value.Location);
116+
}
117+
118+
foreach (var resolvingException in resolvingExceptions)
119+
{
120+
log.Error(CultureInfo.InvariantCulture, "Error occurred loading '{0}' from '{1}'.\n{2}", resolvingException.Key, packageFolder, resolvingException.Value);
121+
}
122+
}
89123
}
90124
}

src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<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">
22

33
<Symbols>
4+
<!-- We need to ensure that `GitHub.VisualStudio` has loaded before any of these resources are used. -->
5+
<!-- This should happen when `AssemblyResolverPackage` auto-loads on `UICONTEXT.NoSolution`. -->
46
<String Name="Resources" Value="/GitHub.VisualStudio;component/Resources/icons" />
7+
58
<Guid Name="guidImages" Value="{27841f47-070a-46d6-90be-a5cbbfc724ac}" />
69
<ID Name="logo" Value="1" />
710
<ID Name="arrow_left" Value="2" />
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using GitHub.VisualStudio;
7+
using Microsoft.VisualStudio.Shell;
8+
using System.Reflection;
9+
using System.IO;
10+
using Xunit;
11+
12+
namespace UnitTests.GitHub.VisualStudio
13+
{
14+
public class AssemblyResolverPackageTests
15+
{
16+
public class TheResolveDependentAssemblyMethod
17+
{
18+
[Fact]
19+
public void ProvideCodeBaseAttribute_MatchFullName()
20+
{
21+
var asm = Assembly.GetExecutingAssembly();
22+
var assemblyName = asm.GetName().Name;
23+
var codeBase = $"$PackageFolder$\\{assemblyName}.dll";
24+
var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase };
25+
var packageFolder = Path.GetDirectoryName(asm.Location);
26+
var resolveAssemblyName = new AssemblyName(asm.FullName);
27+
28+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName);
29+
30+
Assert.Equal(asm, resolvedAssembly);
31+
}
32+
33+
[Fact]
34+
public void ProvideCodeBaseAttribute_DontMatchDifferentVersion()
35+
{
36+
var asm = Assembly.GetExecutingAssembly();
37+
var assemblyName = asm.GetName().Name;
38+
var codeBase = $"$PackageFolder$\\{assemblyName}.dll";
39+
var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase };
40+
var packageFolder = Path.GetDirectoryName(asm.Location);
41+
var resolveAssemblyName = asm.GetName();
42+
resolveAssemblyName.Version = new Version("0.0.0.0");
43+
44+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName);
45+
46+
Assert.Null(resolvedAssembly);
47+
}
48+
49+
[Fact]
50+
public void ProvideCodeBaseAttribute_DontMatchMissingCodeBase()
51+
{
52+
var asm = Assembly.GetExecutingAssembly();
53+
var assemblyName = asm.GetName().Name;
54+
var codeBase = "__NothingToSeeHere___";
55+
var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = codeBase };
56+
var dependentAssemblies = new[] { provideCodeBase };
57+
var packageFolder = Path.GetDirectoryName(asm.Location);
58+
var resolveAssemblyName = new AssemblyName(asm.FullName);
59+
60+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName);
61+
62+
Assert.Null(resolvedAssembly);
63+
}
64+
65+
[Fact]
66+
public void ProvideCodeBaseAttribute_DontMatchPartialName()
67+
{
68+
var asm = Assembly.GetExecutingAssembly();
69+
var assemblyName = asm.GetName().Name;
70+
var provideCodeBase = new ProvideCodeBaseAttribute { AssemblyName = assemblyName, CodeBase = $"$PackageFolder$\\{assemblyName}.dll" };
71+
var dependentAssemblies = new[] { provideCodeBase };
72+
var packageFolder = Path.GetDirectoryName(asm.Location);
73+
var resolveAssemblyName = new AssemblyName(asm.GetName().Name);
74+
75+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideCodeBase, packageFolder, resolveAssemblyName);
76+
77+
Assert.Null(resolvedAssembly);
78+
}
79+
80+
[Fact]
81+
public void ProvideBindingRedirectionAttribute_MatchOldVersionLowerBound()
82+
{
83+
var asm = Assembly.GetExecutingAssembly();
84+
var assemblyName = asm.GetName().Name;
85+
var codeBase = $"$PackageFolder$\\{assemblyName}.dll";
86+
var oldVersionLowerBound = "0.0.0.0";
87+
var oldVersionUpperBound = oldVersionLowerBound;
88+
var provideBindingRedirection = new ProvideBindingRedirectionAttribute
89+
{
90+
AssemblyName = assemblyName,
91+
CodeBase = codeBase,
92+
OldVersionLowerBound = oldVersionLowerBound,
93+
OldVersionUpperBound = oldVersionUpperBound
94+
};
95+
var dependentAssemblies = new[] { provideBindingRedirection };
96+
var packageFolder = Path.GetDirectoryName(asm.Location);
97+
var resolveAssemblyName = asm.GetName();
98+
resolveAssemblyName.Version = new Version(oldVersionLowerBound);
99+
100+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName);
101+
102+
Assert.Equal(asm, resolvedAssembly);
103+
}
104+
105+
[Fact]
106+
public void ProvideBindingRedirectionAttribute_MatchOldVersionUpperBound()
107+
{
108+
var asm = Assembly.GetExecutingAssembly();
109+
var assemblyName = asm.GetName().Name;
110+
var codeBase = $"$PackageFolder$\\{assemblyName}.dll";
111+
var oldVersionUpperBound = "1.1.1.1";
112+
var oldVersionLowerBound = oldVersionUpperBound;
113+
var provideBindingRedirection = new ProvideBindingRedirectionAttribute
114+
{
115+
AssemblyName = assemblyName,
116+
CodeBase = codeBase,
117+
OldVersionLowerBound = oldVersionLowerBound,
118+
OldVersionUpperBound = oldVersionUpperBound
119+
};
120+
var dependentAssemblies = new[] { provideBindingRedirection };
121+
var packageFolder = Path.GetDirectoryName(asm.Location);
122+
var resolveAssemblyName = asm.GetName();
123+
resolveAssemblyName.Version = new Version(oldVersionLowerBound);
124+
125+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName);
126+
127+
Assert.Equal(asm, resolvedAssembly);
128+
}
129+
130+
[Fact]
131+
public void ProvideBindingRedirectionAttribute_DontMatchOutOfBounds()
132+
{
133+
var asm = Assembly.GetExecutingAssembly();
134+
var assemblyName = asm.GetName().Name;
135+
var codeBase = $"$PackageFolder$\\{assemblyName}.dll";
136+
var oldVersionLowerBound = "0.0.0.0";
137+
var oldVersionUpperBound = "1.1.1.1";
138+
var resolveVersion = new Version("2.2.2.2");
139+
var provideBindingRedirection = new ProvideBindingRedirectionAttribute
140+
{
141+
AssemblyName = assemblyName,
142+
CodeBase = codeBase,
143+
OldVersionLowerBound = oldVersionLowerBound,
144+
OldVersionUpperBound = oldVersionUpperBound
145+
};
146+
var dependentAssemblies = new[] { provideBindingRedirection };
147+
var packageFolder = Path.GetDirectoryName(asm.Location);
148+
var resolveAssemblyName = asm.GetName();
149+
resolveAssemblyName.Version = resolveVersion;
150+
151+
var resolvedAssembly = AssemblyResolverPackage.ResolveDependentAssembly(provideBindingRedirection, packageFolder, resolveAssemblyName);
152+
153+
Assert.Null(resolvedAssembly);
154+
}
155+
}
156+
}
157+
}

src/UnitTests/UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@
256256
<Compile Include="GitHub.UI\Converters.cs" />
257257
<Compile Include="GitHub.UI\TestAutomation\ResourceValueTests.cs" />
258258
<Compile Include="GitHub.UI\TwoFactorInputTests.cs" />
259+
<Compile Include="GitHub.VisualStudio\AssemblyResolverPackageTests.cs" />
259260
<Compile Include="GitHub.VisualStudio\Services\JsonConnectionCacheTests.cs" />
260261
<Compile Include="GitHub.VisualStudio\Services\ConnectionManagerTests.cs" />
261262
<Compile Include="GitHub.VisualStudio\Services\RepositoryPublishServiceTests.cs" />

0 commit comments

Comments
 (0)