svn path=/trunk/mcs/; revision=104772
[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 // 
7 // (C) 2006 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 #if NET_2_0
29
30 using System;
31 using System.Collections.Generic;
32 using System.IO;
33 using System.Reflection;
34 using System.Security;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37
38 namespace Microsoft.Build.Tasks {
39         internal class AssemblyResolver {
40
41                 // name -> (version -> assemblypath)
42                 Dictionary <string, Dictionary <Version, string>> gac;
43                 TaskLoggingHelper log;
44
45                 public AssemblyResolver ()
46                 {
47                         gac = new Dictionary <string, Dictionary <Version, string>> ();
48
49                         GatherGacAssemblies ();
50                 }
51
52                 string GetGacPath ()
53                 {
54                         // NOTE: code from mcs/tools/gacutil/driver.cs
55                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
56
57                         if (gac == null)
58                                 return null;
59
60                         MethodInfo get_gac = gac.GetGetMethod (true);
61                         return (string) get_gac.Invoke (null, null);
62                 }
63
64                 void GatherGacAssemblies ()
65                 {
66                         string gac_path = GetGacPath ();
67                         if (gac_path == null)
68                                 throw new InvalidOperationException ("XBuild must be run on Mono runtime");
69                         if (!Directory.Exists (gac_path))
70                                 return; // in case mono isn't "installed".
71
72                         Version version;
73                         DirectoryInfo version_info, assembly_info;
74
75                         foreach (string assembly_name in Directory.GetDirectories (gac_path)) {
76                                 assembly_info = new DirectoryInfo (assembly_name);
77                                 foreach (string version_token in Directory.GetDirectories (assembly_name)) {
78                                         foreach (string file in Directory.GetFiles (version_token, "*.dll")) {
79                                                 version_info = new DirectoryInfo (version_token);
80                                                 version = new Version (version_info.Name.Split (
81                                                         new char [] {'_'}, StringSplitOptions.RemoveEmptyEntries) [0]);
82
83                                                 if (!gac.ContainsKey (assembly_info.Name))
84                                                         gac.Add (assembly_info.Name, new Dictionary <Version, string> ());
85                                                 gac [assembly_info.Name].Add (version, file);
86                                         }
87                                 }
88                         }
89                 }
90
91                 public string ResolveAssemblyReference (ITaskItem reference)
92                 {
93                         AssemblyName name = null;
94                         string resolved = null;
95
96                         name = new AssemblyName (reference.ItemSpec);
97
98                         if (reference.GetMetadata ("HintPath") != String.Empty) {
99
100                                 bool specificVersion;
101
102                                 if (reference.GetMetadata ("SpecificVersion") != String.Empty) {
103                                         specificVersion = Boolean.Parse (reference.GetMetadata ("SpecificVersion"));
104                                 } else {
105                                         specificVersion = IsStrongNamed (name);
106                                 }
107
108                                 resolved = ResolveHintPathReference (name, reference.GetMetadata ("HintPath"), specificVersion);
109                         }
110                         
111                         if (resolved == null)
112                                 resolved = ResolveGacReference (name, gac);
113
114                         return resolved;
115                 }
116
117                 string ResolveGacReference (AssemblyName name, Dictionary <string, Dictionary <Version, string>> dic)
118                 {
119                         // FIXME: deal with SpecificVersion=False
120
121                         if (!dic.ContainsKey (name.Name))
122                                 return null;
123
124                         if (name.Version != null) {
125                                 if (!dic [name.Name].ContainsKey (name.Version))
126                                         return null;
127                                 else
128                                         return dic [name.Name] [name.Version];
129                         }
130
131                         Version [] versions = new Version [dic [name.Name].Keys.Count];
132                         dic [name.Name].Keys.CopyTo (versions, 0);
133                         Array.Sort (versions, (IComparer <Version>) null);
134                         Version highest = versions [versions.Length - 1];
135                         return dic [name.Name] [highest];
136                 }
137
138                 string ResolveHintPathReference (AssemblyName name, string hintpath, bool specificVersion)
139                 {
140                         AssemblyName found;
141                         string ret = null;
142
143                         if (!File.Exists (hintpath))
144                                 log.LogMessage (MessageImportance.Low, "HintPath {0} does not exist.", hintpath);
145
146                         try {
147                                 found = AssemblyName.GetAssemblyName (hintpath);
148                                 if (AssemblyNamesCompatible (name, found, specificVersion))
149                                         ret = hintpath;
150                                 else
151                                         log.LogMessage (MessageImportance.Low, "Assembly names are not compatible.");
152                         } catch {
153                         }
154
155                         return ret;
156                 }
157
158                 static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion)
159                 {
160                         if (a.Name != b.Name)
161                                 return false;
162
163                         if (a.CultureInfo != null && a.CultureInfo != b.CultureInfo)
164                                 return false;
165
166                         if (specificVersion && a.Version != null && a.Version > b.Version)
167                                 return false;
168
169                         byte [] a_bytes = a.GetPublicKeyToken ();
170                         byte [] b_bytes = b.GetPublicKeyToken ();
171
172                         if (specificVersion) {
173                                 if (a_bytes == null || a_bytes.Length == 0)
174                                         return false;
175                                 if (b_bytes == null || b_bytes.Length == 0)
176                                         return false;
177
178                                 for (int i = 0; i < a_bytes.Length; i++)
179                                         if (a_bytes [i] != b_bytes [i])
180                                                 return false;
181                         }
182
183                         return true;
184                 }
185
186                 static bool IsStrongNamed (AssemblyName name)
187                 {
188                         return (name.Version != null && name.GetPublicKeyToken ().Length != 0);
189                 }
190
191                 public TaskLoggingHelper Log {
192                         set { log = value; }
193                 }
194         }
195 }
196
197 #endif