5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2006 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections.Generic;
35 using System.Reflection;
36 using System.Security;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
40 namespace Microsoft.Build.Tasks {
41 internal class AssemblyResolver {
43 // name -> (version -> assemblypath)
44 Dictionary<string, TargetFrameworkAssemblies> target_framework_cache;
45 Dictionary<string, Dictionary<Version, string>> gac;
46 TaskLoggingHelper log;
49 public AssemblyResolver ()
51 gac = new Dictionary<string, Dictionary<Version, string>> ();
52 target_framework_cache = new Dictionary <string, TargetFrameworkAssemblies> ();
54 GatherGacAssemblies ();
57 public StringWriter SearchLogger {
61 public void ResetSearchLogger ()
63 sw = new StringWriter ();
68 // NOTE: code from mcs/tools/gacutil/driver.cs
69 PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
74 MethodInfo get_gac = gac.GetGetMethod (true);
75 return (string) get_gac.Invoke (null, null);
78 void GatherGacAssemblies ()
80 string gac_path = GetGacPath ();
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".
87 DirectoryInfo version_info, assembly_info;
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]);
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);
104 if (assembliesByVersion.TryGetValue (version, out found_file) &&
105 File.GetLastWriteTime (file) <= File.GetLastWriteTime (found_file))
106 // Duplicate found, take the newer file
109 assembliesByVersion [version] = file;
115 public ResolvedReference FindInTargetFramework (ITaskItem reference, string framework_dir, bool specific_version)
117 AssemblyName key_aname = new AssemblyName (reference.ItemSpec);
118 TargetFrameworkAssemblies gac_asm;
119 if (!target_framework_cache.TryGetValue (framework_dir, out gac_asm)) {
121 gac_asm = target_framework_cache [framework_dir] = PopulateTargetFrameworkAssemblies (framework_dir);
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, false,
129 SearchPath.TargetFrameworkDirectory);
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);
136 SearchLogger.WriteLine ("Considered target framework dir {0}, assembly named '{1}' not found.",
137 framework_dir, key_aname.Name);
142 public ResolvedReference FindInDirectory (ITaskItem reference, string directory, string [] file_extensions)
144 if (reference.ItemSpec.IndexOf (',') < 0) {
146 string path = Path.Combine (directory, reference.ItemSpec);
147 if (GetAssemblyNameFromFile (path) != null)
148 return GetResolvedReference (reference, path, true, SearchPath.Directory);
150 foreach (string extn in file_extensions) {
151 string path_with_extn = path + extn;
152 if (GetAssemblyNameFromFile (path_with_extn) != null)
153 return GetResolvedReference (reference, path_with_extn, true, SearchPath.Directory);
157 // Probably an assembly name
158 AssemblyName key_aname = new AssemblyName (reference.ItemSpec);
159 foreach (string extn in file_extensions) {
160 foreach (string file in Directory.GetFiles (directory, "*" + extn)) {
161 AssemblyName found_aname = GetAssemblyNameFromFile (file);
162 if (found_aname == null)
163 // error already logged
166 //FIXME: Extract 'name' and look only for name.dll name.exe ?
167 if (AssemblyNamesCompatible (key_aname, found_aname, false))
168 return GetResolvedReference (reference, file, true, SearchPath.Directory);
170 SearchLogger.WriteLine ("Considered {0}, but assembly name wasn't compatible.", file);
177 TargetFrameworkAssemblies PopulateTargetFrameworkAssemblies (string directory)
179 TargetFrameworkAssemblies gac_asm = new TargetFrameworkAssemblies (directory);
180 foreach (string file in Directory.GetFiles (directory, "*.dll")) {
181 AssemblyName aname = GetAssemblyNameFromFile (file);
182 gac_asm.NameToAssemblyNameCache [aname.Name] =
183 new KeyValuePair<AssemblyName, string> (aname, file);
189 public ResolvedReference ResolveGacReference (ITaskItem reference, bool specific_version)
191 AssemblyName name = new AssemblyName (reference.ItemSpec);
192 if (!gac.ContainsKey (name.Name)) {
193 SearchLogger.WriteLine ("Considered {0}, but could not find in the GAC.",
198 if (name.Version != null) {
200 if (gac [name.Name].TryGetValue (name.Version, out ret))
201 return GetResolvedReference (reference, ret, false, SearchPath.Gac);
204 if (specific_version) {
205 SearchLogger.WriteLine ("Considered '{0}', but an assembly with the specific version not found.",
211 Version [] versions = new Version [gac [name.Name].Keys.Count];
212 gac [name.Name].Keys.CopyTo (versions, 0);
213 Array.Sort (versions, (IComparer <Version>) null);
214 Version highest = versions [versions.Length - 1];
215 return GetResolvedReference (reference, gac [name.Name] [highest], false, SearchPath.Gac);
218 public ResolvedReference ResolveHintPathReference (ITaskItem reference, bool specific_version)
220 AssemblyName name = new AssemblyName (reference.ItemSpec);
221 ResolvedReference resolved = null;
223 string hintpath = reference.GetMetadata ("HintPath");
224 if (String.IsNullOrEmpty (hintpath)) {
225 SearchLogger.WriteLine ("HintPath attribute not found");
229 if (!File.Exists (hintpath)) {
230 log.LogMessage (MessageImportance.Low, "HintPath {0} does not exist.", hintpath);
231 SearchLogger.WriteLine ("Considered {0}, but it does not exist.", hintpath);
235 AssemblyName found = GetAssemblyNameFromFile (hintpath);
237 log.LogMessage (MessageImportance.Low, "File at HintPath {0}, is either an invalid assembly or the file does not exist.", hintpath);
241 if (AssemblyNamesCompatible (name, found, specific_version)) {
242 resolved = GetResolvedReference (reference, hintpath, true, SearchPath.HintPath);
244 SearchLogger.WriteLine ("Considered {0}, but assembly name '{1}' did not match the " +
245 "expected '{2}' (SpecificVersion={3})", hintpath, found, name, specific_version);
246 log.LogMessage (MessageImportance.Low, "Assembly names are not compatible.");
252 public AssemblyName GetAssemblyNameFromFile (string filename)
254 AssemblyName aname = null;
255 filename = Path.GetFullPath (filename);
257 aname = AssemblyName.GetAssemblyName (filename);
258 } catch (FileNotFoundException) {
259 SearchLogger.WriteLine ("Considered '{0}' as a file, but the file does not exist",
261 } catch (BadImageFormatException) {
262 SearchLogger.WriteLine ("Considered '{0}' as a file, but it is an invalid assembly",
269 static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion)
271 if (a.Name != b.Name)
274 if (a.CultureInfo != null && !a.CultureInfo.Equals (b.CultureInfo))
277 if (specificVersion && a.Version != null && a.Version != b.Version)
280 byte [] a_bytes = a.GetPublicKeyToken ();
281 byte [] b_bytes = b.GetPublicKeyToken ();
283 if (specificVersion) {
284 if (a_bytes == null || a_bytes.Length == 0)
286 if (b_bytes == null || b_bytes.Length == 0)
289 for (int i = 0; i < a_bytes.Length; i++)
290 if (a_bytes [i] != b_bytes [i])
297 public bool IsStrongNamed (AssemblyName name)
299 return (name.Version != null &&
300 name.GetPublicKeyToken () != null &&
301 name.GetPublicKeyToken ().Length != 0);
304 // FIXME: to get default values of CopyLocal, compare with TargetFrameworkDirectories
306 // If metadata 'Private' is present then use that or use @default_value
307 // as the value for CopyLocal
308 internal ResolvedReference GetResolvedReference (ITaskItem reference, string filename,
309 bool default_value, SearchPath search_path)
311 string pvt = reference.GetMetadata ("Private");
313 bool copy_local = default_value;
314 if (!String.IsNullOrEmpty (pvt))
315 //FIXME: log a warning for invalid value
316 Boolean.TryParse (pvt, out copy_local);
318 return new ResolvedReference (filename, copy_local, search_path);
321 public TaskLoggingHelper Log {
326 class TargetFrameworkAssemblies {
329 // assembly (simple) name -> (AssemblyName, file path)
330 public Dictionary <string, KeyValuePair<AssemblyName, string>> NameToAssemblyNameCache;
332 public TargetFrameworkAssemblies (string path)
335 NameToAssemblyNameCache = new Dictionary<string, KeyValuePair<AssemblyName, string>> ();
342 TargetFrameworkDirectory,