1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the MIT License. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . IO ;
7
+ using System . Linq ;
8
+ using System . Reflection ;
9
+ using Microsoft . Azure . WebJobs ;
10
+ using Microsoft . Azure . WebJobs . Host ;
11
+ using Microsoft . Azure . WebJobs . Hosting ;
12
+ using Microsoft . CodeAnalysis ;
13
+ using Microsoft . Extensions . DependencyInjection ;
14
+ using Microsoft . Extensions . Hosting ;
15
+
16
+ namespace Microsoft . Azure . Functions . Analyzers
17
+ {
18
+ // Only support extensions and WebJobs core.
19
+ // Although extensions may refer to other dlls.
20
+ public class AssemblyCache
21
+ {
22
+ // Map from assembly identities to full paths
23
+ public static AssemblyCache Instance = new AssemblyCache ( ) ;
24
+
25
+ bool _registered ;
26
+
27
+ // Assembly Display Name --> Path
28
+ Dictionary < string , string > _assemblyNameToPathMap = new Dictionary < string , string > ( ) ;
29
+
30
+ // Assembly Display Name --> loaded Assembly object
31
+ Dictionary < string , Assembly > _assemblyNameToObjMap = new Dictionary < string , Assembly > ( ) ;
32
+
33
+ const string WebJobsAssemblyName = "Microsoft.Azure.WebJobs" ;
34
+ const string WebJobsHostAssemblyName = "Microsoft.Azure.WebJobs.Host" ;
35
+
36
+ JobHostMetadataProvider _tooling ;
37
+
38
+ internal JobHostMetadataProvider Tooling => _tooling ;
39
+ private int _projectCount ;
40
+
41
+ // $$$ This can get invoked multiple times concurrently
42
+ // This will get called on every compilation.
43
+ // So return early on subsequent initializations.
44
+ internal void Build ( Compilation compilation )
45
+ {
46
+ Register ( ) ;
47
+
48
+ int count ;
49
+ lock ( this )
50
+ {
51
+ // If project references have changed, then reanalyze to pick up new dependencies.
52
+ var refs = compilation . References . OfType < PortableExecutableReference > ( ) . ToArray ( ) ;
53
+ count = refs . Length ;
54
+ if ( ( count == _projectCount ) && ( _tooling != null ) )
55
+ {
56
+ return ; // already initialized.
57
+ }
58
+
59
+ // Even for netStandard/.core projects, this will still be a flattened list of the full transitive closure of dependencies.
60
+ foreach ( var asm in compilation . References . OfType < PortableExecutableReference > ( ) )
61
+ {
62
+ var dispName = asm . Display ; // For .net core, the displayname can be the full path
63
+ var path = asm . FilePath ;
64
+
65
+ _assemblyNameToPathMap [ dispName ] = path ;
66
+ }
67
+
68
+ // Builtins
69
+ _assemblyNameToObjMap [ "mscorlib" ] = typeof ( object ) . Assembly ;
70
+ _assemblyNameToObjMap [ WebJobsAssemblyName ] = typeof ( Microsoft . Azure . WebJobs . FunctionNameAttribute ) . Assembly ;
71
+ _assemblyNameToObjMap [ WebJobsHostAssemblyName ] = typeof ( Microsoft . Azure . WebJobs . JobHost ) . Assembly ;
72
+
73
+ // JSON.Net?
74
+ }
75
+
76
+ // Produce tooling object
77
+ var webjobsStartups = new List < Type > ( ) ;
78
+ foreach ( var path in _assemblyNameToPathMap . Values )
79
+ {
80
+ // We don't want to load and reflect over every dll.
81
+ // By convention, restrict based on filenames.
82
+ var filename = Path . GetFileName ( path ) ;
83
+ // TODO: Can this search be narrowed, e.g. "webjobs.extensions"?
84
+ if ( ! filename . ToLowerInvariant ( ) . Contains ( "extension" ) )
85
+ {
86
+ continue ;
87
+ }
88
+ if ( path . Contains ( @"\ref\" ) ) // Skip reference assemblies.
89
+ {
90
+ continue ;
91
+ }
92
+
93
+ Assembly assembly ;
94
+ try
95
+ {
96
+ // See GetNuGetPackagesPath for details
97
+ // Script runtime is already setup with assembly resolution hooks, so use LoadFrom
98
+ assembly = Assembly . LoadFrom ( path ) ;
99
+
100
+ string asmName = new AssemblyName ( assembly . FullName ) . Name ;
101
+ _assemblyNameToObjMap [ asmName ] = assembly ;
102
+
103
+ var test = assembly . GetCustomAttributes < WebJobsStartupAttribute > ( ) . Select ( a => a . WebJobsStartupType ) ;
104
+ if ( test . Count ( ) > 0 )
105
+ {
106
+ webjobsStartups . AddRange ( test ) ;
107
+ }
108
+ }
109
+ catch ( Exception e )
110
+ {
111
+ // Could be a reference assembly.
112
+ continue ;
113
+ }
114
+ }
115
+
116
+ var host = new HostBuilder ( )
117
+ . ConfigureWebJobs ( b =>
118
+ {
119
+ b . AddAzureStorageCoreServices ( )
120
+ . UseExternalStartup ( new CompilationWebJobsStartupTypeLocator ( _assemblyNameToObjMap . Values . ToArray ( ) ) ) ;
121
+ } )
122
+ . Build ( ) ;
123
+ var tooling = ( JobHostMetadataProvider ) host . Services . GetRequiredService < IJobHostMetadataProvider > ( ) ;
124
+
125
+ lock ( this )
126
+ {
127
+ this . _projectCount = count ;
128
+ this . _tooling = tooling ;
129
+ }
130
+ }
131
+
132
+ public bool TryMapAssembly ( IAssemblySymbol asm , out Assembly asmRef )
133
+ {
134
+ // Top-level map only supports mscorlib, webjobs, or extensions
135
+ var asmName = asm . Identity . Name ;
136
+
137
+ Assembly asm2 ;
138
+ if ( _assemblyNameToObjMap . TryGetValue ( asmName , out asm2 ) )
139
+ {
140
+ asmRef = asm2 ;
141
+ return true ;
142
+ }
143
+
144
+ // Is this an extension? Must have a reference to WebJobs
145
+ bool isWebJobsAssembly = false ;
146
+ foreach ( var module in asm . Modules )
147
+ {
148
+ foreach ( var asmReference in module . ReferencedAssemblies )
149
+ {
150
+ if ( asmReference . Name == WebJobsAssemblyName )
151
+ {
152
+ isWebJobsAssembly = true ;
153
+ goto Done ;
154
+ }
155
+ }
156
+ }
157
+ Done :
158
+ if ( ! isWebJobsAssembly )
159
+ {
160
+ asmRef = null ;
161
+ return false ;
162
+ }
163
+
164
+ foreach ( var kv in _assemblyNameToPathMap )
165
+ {
166
+ var path = kv . Value ;
167
+ var shortName = Path . GetFileNameWithoutExtension ( path ) ;
168
+
169
+ if ( string . Equals ( asmName , shortName , StringComparison . OrdinalIgnoreCase ) )
170
+ {
171
+ var asm3 = Assembly . LoadFile ( path ) ;
172
+ _assemblyNameToObjMap [ asmName ] = asm3 ;
173
+
174
+ asmRef = asm3 ;
175
+ return true ;
176
+ }
177
+ }
178
+
179
+ throw new NotImplementedException ( ) ;
180
+ }
181
+
182
+ public void Register ( )
183
+ {
184
+ if ( _registered )
185
+ {
186
+ return ;
187
+ }
188
+ AppDomain . CurrentDomain . AssemblyResolve += CurrentDomain_AssemblyResolve ;
189
+ _registered = true ;
190
+ }
191
+
192
+ private Assembly CurrentDomain_AssemblyResolve ( object sender , ResolveEventArgs args )
193
+ {
194
+ var an = new AssemblyName ( args . Name ) ;
195
+ var context = args . RequestingAssembly ;
196
+
197
+ Assembly asm2 ;
198
+ if ( _assemblyNameToObjMap . TryGetValue ( an . Name , out asm2 ) )
199
+ {
200
+ return asm2 ;
201
+ }
202
+
203
+ asm2 = LoadFromProjectReference ( an ) ;
204
+ if ( asm2 != null )
205
+ {
206
+ _assemblyNameToObjMap [ an . Name ] = asm2 ;
207
+ }
208
+
209
+ return asm2 ;
210
+ }
211
+
212
+ private Assembly LoadFromProjectReference ( AssemblyName an )
213
+ {
214
+ foreach ( var kv in _assemblyNameToPathMap )
215
+ {
216
+ var path = kv . Key ;
217
+ if ( path . Contains ( @"\ref\" ) ) // Skip reference assemblies.
218
+ {
219
+ continue ;
220
+ }
221
+
222
+ var filename = Path . GetFileNameWithoutExtension ( path ) ;
223
+
224
+ // Simplifying assumption: assume dll name matches assembly name.
225
+ // Use this as a filter to limit the number of file-touches.
226
+ if ( string . Equals ( filename , an . Name , StringComparison . OrdinalIgnoreCase ) )
227
+ {
228
+ var an2 = AssemblyName . GetAssemblyName ( path ) ;
229
+
230
+ if ( string . Equals ( an2 . FullName , an . FullName , StringComparison . OrdinalIgnoreCase ) )
231
+ {
232
+ var a = Assembly . LoadFrom ( path ) ;
233
+ return a ;
234
+ }
235
+ }
236
+ }
237
+ return null ;
238
+ }
239
+ }
240
+ }
0 commit comments