11using System ;
2+ using System . IO ;
23using System . Linq ;
34using System . Reflection ;
45using System . Diagnostics ;
5- using GitHub . Helpers ;
6+ using System . Collections . Generic ;
67using GitHub . Logging ;
78using Microsoft . VisualStudio . Shell ;
89using Microsoft . VisualStudio . Shell . Interop ;
10+ using Microsoft . VisualStudio . Shell . Settings ;
11+ using Microsoft . VisualStudio . Settings ;
912using Serilog ;
1013
1114namespace GitHub . VisualStudio . Helpers
1215{
1316 /// <summary>
14- // When running in the Exp instance, ensure there is only one active binding path.
15- // This is necessary when the regular ( AllUsers) extension is also installed .
17+ /// This a workaround for extensions that define a ProvideBindingPath attribute and
18+ /// install for AllUsers.
1619 /// </summary>
1720 /// <remarks>
21+ /// Extensions that are installed for AllUsers, will also be installed for all
22+ /// instances of Visual Studio - including the experimental (Exp) instance which
23+ /// is used in development. This isn't a problem so long as all features that
24+ /// exist in the AllUsers extension, also exist in the extension that is being
25+ /// developed.
26+ ///
27+ /// When an extension uses the ProvideBindingPath attribute, the binding path for
28+ /// the AllUsers extension gets installed at the same time as the one in development.
29+ /// This doesn't matter when an assembly is strong named and is loaded using its
30+ /// full name (including version number). When an assembly is loaded using its
31+ /// simple name, assemblies from the AllUsers extension can end up loaded at the
32+ /// same time as the extension being developed. This can happen when an assembly
33+ /// is loaded from XAML or an .imagemanifest.
34+ ///
35+ /// This is a workaround for that issue. The <see cref="FindRedundantBindingPaths(List{string}, string)" />
36+ /// method will check to see if a reference assembly could be loaded from an alternative
37+ /// binding path. It will return any alternative paths that is finds.
1838 /// See https://github.com/github/VisualStudio/issues/1995
1939 /// </remarks>
20- class BindingPathHelper
40+ public class BindingPathHelper
2141 {
2242 static readonly ILogger log = LogManager . ForContext < BindingPathHelper > ( ) ;
2343
@@ -26,8 +46,8 @@ internal static void CheckBindingPaths(Assembly assembly, IServiceProvider servi
2646 log . Information ( "Looking for assembly on wrong binding path" ) ;
2747
2848 ThreadHelper . CheckAccess ( ) ;
29- var bindingPaths = BindingPathUtilities . FindBindingPaths ( serviceProvider ) ;
30- var bindingPath = BindingPathUtilities . FindRedundantBindingPaths ( bindingPaths , assembly . Location )
49+ var bindingPaths = FindBindingPaths ( serviceProvider ) ;
50+ var bindingPath = FindRedundantBindingPaths ( bindingPaths , assembly . Location )
3151 . FirstOrDefault ( ) ;
3252 if ( bindingPath == null )
3353 {
@@ -49,5 +69,41 @@ internal static void CheckBindingPaths(Assembly assembly, IServiceProvider servi
4969 Process . Start ( "https://github.com/github/VisualStudio/issues/2006" ) ;
5070 }
5171 }
72+
73+ /// <summary>
74+ /// Find any alternative binding path that might have been installed by an AllUsers extension.
75+ /// </summary>
76+ /// <param name="bindingPaths">A list of binding paths to search</param>
77+ /// <param name="assemblyLocation">A reference assembly that has been loaded from the correct path.</param>
78+ /// <returns>A list of redundant binding paths.</returns>
79+ public static IList < string > FindRedundantBindingPaths ( IEnumerable < string > bindingPaths , string assemblyLocation )
80+ {
81+ var fileName = Path . GetFileName ( assemblyLocation ) ;
82+ return bindingPaths
83+ . Select ( p => ( path : p , file : Path . Combine ( p , fileName ) ) )
84+ . Where ( pf => File . Exists ( pf . file ) )
85+ . Where ( pf => ! pf . file . Equals ( assemblyLocation , StringComparison . OrdinalIgnoreCase ) )
86+ . Select ( pf => pf . path )
87+ . ToList ( ) ;
88+ }
89+
90+ /// <summary>
91+ /// Find Visual Studio's list of binding paths.
92+ /// </summary>
93+ /// <returns>A list of binding paths.</returns>
94+ public static IEnumerable < string > FindBindingPaths ( IServiceProvider serviceProvider )
95+ {
96+ const string bindingPaths = "BindingPaths" ;
97+ var manager = new ShellSettingsManager ( serviceProvider ) ;
98+ var store = manager . GetReadOnlySettingsStore ( SettingsScope . Configuration ) ;
99+ foreach ( var guid in store . GetSubCollectionNames ( bindingPaths ) )
100+ {
101+ var guidPath = Path . Combine ( bindingPaths , guid ) ;
102+ foreach ( var path in store . GetPropertyNames ( guidPath ) )
103+ {
104+ yield return path ;
105+ }
106+ }
107+ }
52108 }
53109}
0 commit comments