Fix bug #517974.
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / AssemblyResolver.cs
1 //
2 // AssemblyResolver.cs
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Ankit Jain (jankit@novell.com)
7 // 
8 // (C) 2006 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30 #if NET_2_0
31
32 using System;
33 using System.Collections.Generic;
34 using System.IO;
35 using System.Reflection;
36 using System.Security;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
39
40 namespace Microsoft.Build.Tasks {
41         internal class AssemblyResolver {
42
43                 // name -> (version -> assemblypath)
44                 Dictionary<string, TargetFrameworkAssemblies> target_framework_cache;
45                 Dictionary<string, Dictionary<Version, string>> gac;
46                 TaskLoggingHelper log;
47                 StringWriter sw;
48
49                 public AssemblyResolver ()
50                 {
51                         gac = new Dictionary<string, Dictionary<Version, string>> ();
52                         target_framework_cache = new Dictionary <string, TargetFrameworkAssemblies> ();
53
54                         GatherGacAssemblies ();
55                 }
56
57                 public StringWriter SearchLogger {
58                         get { return sw; }
59                 }
60
61                 public void ResetSearchLogger ()
62                 {
63                         sw = new StringWriter ();
64                 }
65
66                 string GetGacPath ()
67                 {
68                         // NOTE: code from mcs/tools/gacutil/driver.cs
69                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
70
71                         if (gac == null)
72                                 return null;
73
74                         MethodInfo get_gac = gac.GetGetMethod (true);
75                         return (string) get_gac.Invoke (null, null);
76                 }
77
78                 void GatherGacAssemblies ()
79                 {
80                         string gac_path = GetGacPath ();
81                         if (gac_path == null)
82                                 throw new InvalidOperationException ("XBuild must be run on Mono runtime");
83                         if (!Directory.Exists (gac_path))
84                                 return; // in case mono isn't "installed".
85
86                         Version version;
87                         DirectoryInfo version_info, assembly_info;
88
89                         foreach (string assembly_name in Directory.GetDirectories (gac_path)) {
90                                 assembly_info = new DirectoryInfo (assembly_name);
91                                 foreach (string version_token in Directory.GetDirectories (assembly_name)) {
92                                         foreach (string file in Directory.GetFiles (version_token, "*.dll")) {
93                                                 version_info = new DirectoryInfo (version_token);
94                                                 version = new Version (version_info.Name.Split (
95                                                         new char [] {'_'}, StringSplitOptions.RemoveEmptyEntries) [0]);
96
97                                                 Dictionary<Version, string> assembliesByVersion = new Dictionary <Version, string> ();
98                                                 if (!gac.TryGetValue (assembly_info.Name, out assembliesByVersion)) {
99                                                         assembliesByVersion = new Dictionary <Version, string> ();
100                                                         gac.Add (assembly_info.Name, assembliesByVersion);
101                                                 }
102
103                                                 string found_file;
104                                                 if (assembliesByVersion.TryGetValue (version, out found_file) &&
105                                                         File.GetLastWriteTime (file) <= File.GetLastWriteTime (found_file))
106                                                                 // Duplicate found, take the newer file
107                                                                 continue;
108
109                                                 assembliesByVersion [version] = file;
110                                         }
111                                 }
112                         }
113                 }
114
115                 public ResolvedReference FindInTargetFramework (ITaskItem reference, string framework_dir, bool specific_version)
116                 {
117                         AssemblyName key_aname = new AssemblyName (reference.ItemSpec);
118                         TargetFrameworkAssemblies gac_asm;
119                         if (!target_framework_cache.TryGetValue (framework_dir, out gac_asm)) {
120                                 // fill gac_asm
121                                 gac_asm = target_framework_cache [framework_dir] = PopulateTargetFrameworkAssemblies (framework_dir);
122                         }
123
124                         KeyValuePair<AssemblyName, string> pair;
125                         if (gac_asm.NameToAssemblyNameCache.TryGetValue (key_aname.Name, out pair)) {
126                                 if (AssemblyNamesCompatible (key_aname, pair.Key, specific_version)) {
127                                         // gac and tgt frmwk refs are not copied private
128                                         return GetResolvedReference (reference, pair.Value, pair.Key, false,
129                                                         SearchPath.TargetFrameworkDirectory);
130                                 }
131
132                                 SearchLogger.WriteLine ("Considered target framework dir {0}, assembly name '{1}' did not " +
133                                                 "match the expected '{2}' (SpecificVersion={3})",
134                                                 framework_dir, pair.Key, key_aname, specific_version);
135                         } else {
136                                 SearchLogger.WriteLine ("Considered target framework dir {0}, assembly named '{1}' not found.",
137                                                 framework_dir, key_aname.Name);
138                         }
139                         return null;
140                 }
141
142                 public ResolvedReference FindInDirectory (ITaskItem reference, string directory, string [] file_extensions)
143                 {
144                         if (reference.ItemSpec.IndexOf (',') < 0) {
145                                 // Try as a filename
146                                 string path = Path.Combine (directory, reference.ItemSpec);
147                                 AssemblyName aname = GetAssemblyNameFromFile (path);
148                                 if (aname != null)
149                                         return GetResolvedReference (reference, path, aname, true, SearchPath.Directory);
150
151                                 foreach (string extn in file_extensions) {
152                                         string path_with_extn = path + extn;
153                                         aname = GetAssemblyNameFromFile (path_with_extn);
154                                         if (aname != null)
155                                                 return GetResolvedReference (reference, path_with_extn, aname, true,
156                                                                 SearchPath.Directory);
157                                 }
158                         }
159
160                         // Probably an assembly name
161                         AssemblyName key_aname = new AssemblyName (reference.ItemSpec);
162                         foreach (string extn in file_extensions) {
163                                 foreach (string file in Directory.GetFiles (directory, "*" + extn)) {
164                                         AssemblyName found_aname = GetAssemblyNameFromFile (file);
165                                         if (found_aname == null)
166                                                 // error already logged
167                                                 continue;
168
169                                         //FIXME: Extract 'name' and look only for name.dll name.exe ?
170                                         if (AssemblyNamesCompatible (key_aname, found_aname, false))
171                                                 return GetResolvedReference (reference, file, found_aname, true,
172                                                                 SearchPath.Directory);
173
174                                         SearchLogger.WriteLine ("Considered {0}, but assembly name wasn't compatible.", file);
175                                 }
176                         }
177
178                         return null;
179                 }
180
181                 TargetFrameworkAssemblies PopulateTargetFrameworkAssemblies (string directory)
182                 {
183                         TargetFrameworkAssemblies gac_asm = new TargetFrameworkAssemblies (directory);
184                         foreach (string file in Directory.GetFiles (directory, "*.dll")) {
185                                 AssemblyName aname = GetAssemblyNameFromFile (file);
186                                 gac_asm.NameToAssemblyNameCache [aname.Name] =
187                                         new KeyValuePair<AssemblyName, string> (aname, file);
188                         }
189
190                         return gac_asm;
191                 }
192
193                 public ResolvedReference ResolveGacReference (ITaskItem reference, bool specific_version)
194                 {
195                         AssemblyName name = new AssemblyName (reference.ItemSpec);
196                         if (!gac.ContainsKey (name.Name)) {
197                                 SearchLogger.WriteLine ("Considered {0}, but could not find in the GAC.",
198                                                 reference.ItemSpec);
199                                 return null;
200                         }
201
202                         if (name.Version != null) {
203                                 string ret;
204                                 if (gac [name.Name].TryGetValue (name.Version, out ret))
205                                         return GetResolvedReference (reference, ret, name, false, SearchPath.Gac);
206
207                                 // not found
208                                 if (specific_version) {
209                                         SearchLogger.WriteLine ("Considered '{0}', but an assembly with the specific version not found.",
210                                                         reference.ItemSpec);
211                                         return null;
212                                 }
213                         }
214
215                         Version [] versions = new Version [gac [name.Name].Keys.Count];
216                         gac [name.Name].Keys.CopyTo (versions, 0);
217                         Array.Sort (versions, (IComparer <Version>) null);
218                         Version highest = versions [versions.Length - 1];
219                         //FIXME: the aname being used here isn't correct, its version should
220                         //       actually match "highest"
221                         return GetResolvedReference (reference, gac [name.Name] [highest], name, false, SearchPath.Gac);
222                 }
223
224                 public ResolvedReference ResolveHintPathReference (ITaskItem reference, bool specific_version)
225                 {
226                         AssemblyName name = new AssemblyName (reference.ItemSpec);
227                         ResolvedReference resolved = null;
228
229                         string hintpath = reference.GetMetadata ("HintPath");
230                         if (String.IsNullOrEmpty (hintpath)) {
231                                 SearchLogger.WriteLine ("HintPath attribute not found");
232                                 return null;
233                         }
234
235                         if (!File.Exists (hintpath)) {
236                                 log.LogMessage (MessageImportance.Low, "HintPath {0} does not exist.", hintpath);
237                                 SearchLogger.WriteLine ("Considered {0}, but it does not exist.", hintpath);
238                                 return null;
239                         }
240
241                         AssemblyName found = GetAssemblyNameFromFile (hintpath);
242                         if (found == null) {
243                                 log.LogMessage (MessageImportance.Low, "File at HintPath {0}, is either an invalid assembly or the file does not exist.", hintpath);
244                                 return null;
245                         }
246
247                         if (AssemblyNamesCompatible (name, found, specific_version)) {
248                                 resolved = GetResolvedReference (reference, hintpath, found, true, SearchPath.HintPath);
249                         } else {
250                                 SearchLogger.WriteLine ("Considered {0}, but assembly name '{1}' did not match the " +
251                                                 "expected '{2}' (SpecificVersion={3})", hintpath, found, name, specific_version);
252                                 log.LogMessage (MessageImportance.Low, "Assembly names are not compatible.");
253                         }
254
255                         return resolved;
256                 }
257
258                 public AssemblyName GetAssemblyNameFromFile (string filename)
259                 {
260                         AssemblyName aname = null;
261                         filename = Path.GetFullPath (filename);
262                         try {
263                                 aname = AssemblyName.GetAssemblyName (filename);
264                         } catch (FileNotFoundException) {
265                                 SearchLogger.WriteLine ("Considered '{0}' as a file, but the file does not exist",
266                                                 filename);
267                         } catch (BadImageFormatException) {
268                                 SearchLogger.WriteLine ("Considered '{0}' as a file, but it is an invalid assembly",
269                                                 filename);
270                         }
271
272                         return aname;
273                 }
274
275                 internal static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion)
276                 {
277                         if (a.Name != b.Name)
278                                 return false;
279
280                         if (a.CultureInfo != null && !a.CultureInfo.Equals (b.CultureInfo))
281                                 return false;
282
283                         if (specificVersion && a.Version != null && a.Version != b.Version)
284                                 return false;
285
286                         byte [] a_bytes = a.GetPublicKeyToken ();
287                         byte [] b_bytes = b.GetPublicKeyToken ();
288
289                         if (specificVersion) {
290                                 bool a_is_empty = (a_bytes == null || a_bytes.Length == 0);
291                                 bool b_is_empty = (b_bytes == null || b_bytes.Length == 0);
292
293                                 if (a_is_empty && b_is_empty)
294                                         return true;
295
296                                 if (a_is_empty || b_is_empty)
297                                         return false;
298
299                                 for (int i = 0; i < a_bytes.Length; i++)
300                                         if (a_bytes [i] != b_bytes [i])
301                                                 return false;
302                         }
303
304                         return true;
305                 }
306
307                 public bool IsStrongNamed (AssemblyName name)
308                 {
309                         return (name.Version != null &&
310                                         name.GetPublicKeyToken () != null &&
311                                         name.GetPublicKeyToken ().Length != 0);
312                 }
313
314                 // FIXME: to get default values of CopyLocal, compare with TargetFrameworkDirectories
315
316                 // If metadata 'Private' is present then use that or use @default_value
317                 // as the value for CopyLocal
318                 internal ResolvedReference GetResolvedReference (ITaskItem reference, string filename,
319                                 AssemblyName aname, bool default_value, SearchPath search_path)
320                 {
321                         string pvt = reference.GetMetadata ("Private");
322
323                         bool copy_local = default_value;
324                         if (!String.IsNullOrEmpty (pvt))
325                                 //FIXME: log a warning for invalid value
326                                 Boolean.TryParse (pvt, out copy_local);
327
328                         return new ResolvedReference (filename, aname, copy_local, search_path);
329                 }
330
331                 public TaskLoggingHelper Log {
332                         set { log = value; }
333                 }
334         }
335
336         class TargetFrameworkAssemblies {
337                 public string Path;
338
339                 // assembly (simple) name -> (AssemblyName, file path)
340                 public Dictionary <string, KeyValuePair<AssemblyName, string>> NameToAssemblyNameCache;
341
342                 public TargetFrameworkAssemblies (string path)
343                 {
344                         this.Path = path;
345                         NameToAssemblyNameCache = new Dictionary<string, KeyValuePair<AssemblyName, string>> ();
346                 }
347         }
348
349         enum SearchPath
350         {
351                 Gac,
352                 TargetFrameworkDirectory,
353                 CandidateAssemblies,
354                 HintPath,
355                 Directory,
356                 RawFileName
357         }
358 }
359
360
361
362 #endif