[xbuild] Rename method to make it more consistent.
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / AssemblyResolver.cs
index 6ecbe4a28f21cc49a478987bd097d7a75e1b1dc6..b5d8672c580b062d17cea6dfafab545605ea6a5d 100644 (file)
@@ -3,8 +3,10 @@
 //
 // Author:
 //   Marek Sieradzki (marek.sieradzki@gmail.com)
+//   Ankit Jain (jankit@novell.com)
 // 
 // (C) 2006 Marek Sieradzki
+// Copyright 2009 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Reflection;
 using System.Security;
 using Microsoft.Build.Framework;
 using Microsoft.Build.Utilities;
+using Mono.PkgConfig;
 
 namespace Microsoft.Build.Tasks {
        internal class AssemblyResolver {
 
                // name -> (version -> assemblypath)
-               Dictionary <string, Dictionary <Version, string>> gac;
+               Dictionary<string, TargetFrameworkAssemblies> target_framework_cache;
+               Dictionary<string, Dictionary<Version, string>> gac;
+               TaskLoggingHelper log;
+               StringWriter sw;
+               List<string> search_log;
+
+               static LibraryPcFileCache cache;
 
-               Dictionary <string, Dictionary <Version, string>> hint_path_assemblies;
-               Dictionary <string, object> hint_paths;
-       
                public AssemblyResolver ()
                {
-                       gac = new Dictionary <string, Dictionary <Version, string>> ();
-                       hint_path_assemblies = new Dictionary <string, Dictionary <Version, string>> ();
-                       hint_paths = new Dictionary <string, object> ();
+                       gac = new Dictionary<string, Dictionary<Version, string>> ();
+                       target_framework_cache = new Dictionary <string, TargetFrameworkAssemblies> ();
+
                        GatherGacAssemblies ();
                }
 
+               public void ResetSearchLogger ()
+               {
+                       if (search_log == null)
+                               search_log = new List<string> ();
+                       else
+                               search_log.Clear ();
+               }
+
                string GetGacPath ()
                {
                        // NOTE: code from mcs/tools/gacutil/driver.cs
@@ -69,6 +84,8 @@ namespace Microsoft.Build.Tasks {
                        string gac_path = GetGacPath ();
                        if (gac_path == null)
                                throw new InvalidOperationException ("XBuild must be run on Mono runtime");
+                       if (!Directory.Exists (gac_path))
+                               return; // in case mono isn't "installed".
 
                        Version version;
                        DirectoryInfo version_info, assembly_info;
@@ -81,92 +98,398 @@ namespace Microsoft.Build.Tasks {
                                                version = new Version (version_info.Name.Split (
                                                        new char [] {'_'}, StringSplitOptions.RemoveEmptyEntries) [0]);
 
-                                               if (!gac.ContainsKey (assembly_info.Name))
-                                                       gac.Add (assembly_info.Name, new Dictionary <Version, string> ());
-                                               gac [assembly_info.Name].Add (version, file);
+                                               Dictionary<Version, string> assembliesByVersion = new Dictionary <Version, string> ();
+                                               if (!gac.TryGetValue (assembly_info.Name, out assembliesByVersion)) {
+                                                       assembliesByVersion = new Dictionary <Version, string> ();
+                                                       gac.Add (assembly_info.Name, assembliesByVersion);
+                                               }
+
+                                               string found_file;
+                                               if (assembliesByVersion.TryGetValue (version, out found_file) &&
+                                                       File.GetLastWriteTime (file) <= File.GetLastWriteTime (found_file))
+                                                               // Duplicate found, take the newer file
+                                                               continue;
+
+                                               assembliesByVersion [version] = file;
                                        }
                                }
                        }
                }
 
-               void GatherHintPathAssemblies (string hintPath)
+               public ResolvedReference FindInTargetFramework (ITaskItem reference, string framework_dir, bool specific_version)
                {
-                       if (hint_paths.ContainsKey (hintPath))
-                               return;
+                       if (!Directory.Exists (framework_dir))
+                               return null;
+                       
+                       AssemblyName key_aname;
+                       if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out key_aname))
+                               return null;
 
-                       Assembly a;
-                       AssemblyName name;
+                       TargetFrameworkAssemblies gac_asm;
+                       if (!target_framework_cache.TryGetValue (framework_dir, out gac_asm)) {
+                               // fill gac_asm
+                               gac_asm = target_framework_cache [framework_dir] = PopulateTargetFrameworkAssemblies (framework_dir);
+                       }
 
-                       try {
-                               foreach (string assembly_name in Directory.GetFiles (Path.GetDirectoryName (hintPath))) {
-                                       try {
-                                               a = Assembly.ReflectionOnlyLoadFrom (assembly_name);
-                                               name = new AssemblyName (a.FullName);
-       
-                                               if (!hint_path_assemblies.ContainsKey (name.Name))
-                                                       hint_path_assemblies [name.Name] = new Dictionary <Version, string> ();
-                                               hint_path_assemblies [name.Name] [name.Version] = assembly_name;
-                                               hint_paths [hintPath] = null;
-                                       } catch {
-                                       }
+                       KeyValuePair<AssemblyName, string> pair;
+                       if (gac_asm.NameToAssemblyNameCache.TryGetValue (key_aname.Name, out pair)) {
+                               if (AssemblyNamesCompatible (key_aname, pair.Key, specific_version)) {
+                                       // gac and tgt frmwk refs are not copied private
+                                       return GetResolvedReference (reference, pair.Value, pair.Key, false,
+                                                       SearchPath.TargetFrameworkDirectory);
                                }
-                       } catch {
+
+                               LogSearchMessage ("Considered target framework dir {0}, assembly name '{1}' did not " +
+                                               "match the expected '{2}' (SpecificVersion={3})",
+                                               framework_dir, pair.Key, key_aname, specific_version);
+                       } else {
+                               LogSearchMessage ("Considered target framework dir {0}, assembly named '{1}' not found.",
+                                               framework_dir, key_aname.Name);
                        }
+                       return null;
                }
 
-               public string ResolveAssemblyReference (ITaskItem reference)
+               // Look for %(Identity).{dll|exe|..}
+               // if specific_version==true
+               //      resolve if assembly names match
+               // else
+               //      resolve the valid assembly
+               public ResolvedReference FindInDirectory (ITaskItem reference, string directory, string [] file_extensions, bool specific_version)
                {
-                       AssemblyName name = null;
-                       string resolved = null;
+                       string filename = reference.ItemSpec;
+                       int comma_pos = filename.IndexOf (',');
+                       if (comma_pos >= 0)
+                               filename = filename.Substring (0, comma_pos);
 
-                       try {
-                               name = new AssemblyName (reference.ItemSpec);
-                       } catch {
+                       // Try as a filename
+                       string path = Path.GetFullPath (Path.Combine (directory, filename));
+                       AssemblyName aname = null;
+                       if (specific_version && !TryGetAssemblyNameFromFullName (reference.ItemSpec, out aname))
                                return null;
-                       }
 
-                       if (reference.GetMetadata ("HintPath") != String.Empty)
-                               resolved = ResolveHintPathReference (name, reference.GetMetadata ("HintPath"));
-                       
-                       if (resolved == null)
-                               resolved = ResolveGacReference (name);
+                       ResolvedReference resolved_ref = ResolveReferenceForPath (path, reference, aname, null, SearchPath.Directory, specific_version);
+                       if (resolved_ref != null)
+                               return resolved_ref;
+
+                       // try path + Include + {.dll|.exe|..}
+                       foreach (string extn in file_extensions) {
+                               resolved_ref = ResolveReferenceForPath (path + extn, reference, aname, null, SearchPath.Directory, specific_version);
+                               if (resolved_ref != null)
+                                       return resolved_ref;
+                       }
 
-                       return resolved;
+                       return null;
                }
 
-               string ResolveGacReference (AssemblyName name)
+               // tries to resolve reference from the given file path, and compares assembly names
+               // if @specific_version == true, and logs accordingly
+               ResolvedReference ResolveReferenceForPath (string filename, ITaskItem reference, AssemblyName aname,
+                                       string error_message, SearchPath spath, bool specific_version)
                {
-                       return ResolveGenericReference (name, gac);
+                       AssemblyName found_aname;
+                       if (!TryGetAssemblyNameFromFile (filename, out found_aname)) {
+                               if (error_message != null)
+                                       log.LogMessage (MessageImportance.Low, error_message);
+                               return null;
+                       }
+
+                       if (!specific_version || AssemblyNamesCompatible (aname, found_aname, specific_version)) {
+                               // Check compatibility only if specific_version == true
+                               return GetResolvedReference (reference, filename, found_aname, true, spath);
+                       } else {
+                               LogSearchMessage ("Considered '{0}', but assembly name '{1}' did not match the " +
+                                               "expected '{2}' (SpecificVersion={3})", filename, found_aname, aname, specific_version);
+                               log.LogMessage (MessageImportance.Low, "Assembly names are not compatible.");
+                       }
+
+                       return null;
                }
 
-               string ResolveHintPathReference (AssemblyName name, string hintpath)
+               TargetFrameworkAssemblies PopulateTargetFrameworkAssemblies (string directory)
                {
-                       if (hintpath != String.Empty)
-                               GatherHintPathAssemblies (hintpath);
-                       return ResolveGenericReference (name, hint_path_assemblies);
+                       TargetFrameworkAssemblies gac_asm = new TargetFrameworkAssemblies (directory);
+                       foreach (string file in Directory.GetFiles (directory, "*.dll")) {
+                               AssemblyName aname;
+                               if (TryGetAssemblyNameFromFile (file, out aname))
+                                       gac_asm.NameToAssemblyNameCache [aname.Name] =
+                                               new KeyValuePair<AssemblyName, string> (aname, file);
+                       }
+
+                       return gac_asm;
                }
 
-               string ResolveGenericReference (AssemblyName name, Dictionary <string, Dictionary <Version, string>> dic)
+               public ResolvedReference ResolveGacReference (ITaskItem reference, bool specific_version)
                {
-                       // FIXME: deal with SpecificVersion=False
+                       AssemblyName name;
+                       if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out name))
+                               return null;
 
-                       if (!dic.ContainsKey (name.Name))
+                       if (!gac.ContainsKey (name.Name)) {
+                               LogSearchMessage ("Considered {0}, but could not find in the GAC.",
+                                               reference.ItemSpec);
                                return null;
+                       }
 
                        if (name.Version != null) {
-                               if (!dic [name.Name].ContainsKey (name.Version))
+                               string ret;
+                               if (gac [name.Name].TryGetValue (name.Version, out ret))
+                                       return GetResolvedReference (reference, ret, name, false, SearchPath.Gac);
+
+                               // not found
+                               if (specific_version) {
+                                       LogSearchMessage ("Considered '{0}', but an assembly with the specific version not found.",
+                                                       reference.ItemSpec);
                                        return null;
-                               else
-                                       return dic [name.Name] [name.Version];
+                               }
                        }
 
-                       Version [] versions = new Version [dic [name.Name].Keys.Count];
-                       dic [name.Name].Keys.CopyTo (versions, 0);
+                       Version [] versions = new Version [gac [name.Name].Keys.Count];
+                       gac [name.Name].Keys.CopyTo (versions, 0);
                        Array.Sort (versions, (IComparer <Version>) null);
                        Version highest = versions [versions.Length - 1];
-                       return dic [name.Name] [highest];
+                       //FIXME: the aname being used here isn't correct, its version should
+                       //       actually match "highest"
+                       return GetResolvedReference (reference, gac [name.Name] [highest], name, false, SearchPath.Gac);
+               }
+
+               public ResolvedReference ResolvePkgConfigReference (ITaskItem reference, bool specific_version)
+               {
+                       PackageAssemblyInfo pkg = null;
+
+                       if (specific_version) {
+                               pkg = PcCache.GetAssemblyLocation (reference.ItemSpec);
+                       } else {
+                               // if not specific version, then just match simple name
+                               string name = reference.ItemSpec;
+                               if (name.IndexOf (',') > 0)
+                                       name = name.Substring (0, name.IndexOf (','));
+                               pkg = PcCache.ResolveAssemblyName (name).FirstOrDefault ();
+                       }
+
+                       if (pkg == null) {
+                               LogSearchMessage ("Considered {0}, but could not find in any pkg-config files.",
+                                               reference.ItemSpec);
+                               return null;
+                       }
+
+                       AssemblyName aname;
+                       if (!TryGetAssemblyNameFromFullName (pkg.FullName, out aname))
+                               return null;
+
+                       ResolvedReference rr = GetResolvedReference (reference, pkg.File, aname,
+                                               false, SearchPath.PkgConfig);
+                       rr.FoundInSearchPathAsString = String.Format ("{{PkgConfig}} provided by package named {0}",
+                                                       pkg.ParentPackage.Name);
+
+                       return rr;
+               }
+
+               // HintPath has a valid assembly
+               // if specific_version==true
+               //      resolve if assembly names match
+               // else
+               //      resolve the valid assembly
+               public ResolvedReference ResolveHintPathReference (ITaskItem reference, bool specific_version)
+               {
+                       string hintpath = reference.GetMetadata ("HintPath");
+                       if (String.IsNullOrEmpty (hintpath)) {
+                               LogSearchMessage ("HintPath attribute not found");
+                               return null;
+                       }
+
+                       if (!File.Exists (hintpath)) {
+                               log.LogMessage (MessageImportance.Low, "HintPath {0} does not exist.", hintpath);
+                               LogSearchMessage ("Considered {0}, but it does not exist.", hintpath);
+                               return null;
+                       }
+
+                       AssemblyName aname;
+                       if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out aname))
+                               return null;
+
+                       return ResolveReferenceForPath (hintpath, reference, aname,
+                                               String.Format ("File at HintPath {0}, is either an invalid assembly or the file does not exist.", hintpath),
+                                               SearchPath.HintPath, specific_version);
+               }
+
+               public bool TryGetAssemblyNameFromFile (string filename, out AssemblyName aname)
+               {
+                       aname = null;
+                       filename = Path.GetFullPath (filename);
+                       try {
+                               aname = AssemblyName.GetAssemblyName (filename);
+                       } catch (FileNotFoundException) {
+                               LogSearchMessage ("Considered '{0}' as a file, but the file does not exist",
+                                               filename);
+                       } catch (BadImageFormatException) {
+                               LogSearchMessage ("Considered '{0}' as a file, but it is an invalid assembly",
+                                               filename);
+                       }
+
+                       return aname != null;
+               }
+
+               bool TryGetAssemblyNameFromFullName (string full_name, out AssemblyName aname)
+               {
+                       aname = null;
+                       try {
+                               aname = new AssemblyName (full_name);
+                       } catch (FileLoadException) {
+                               LogSearchMessage ("Considered '{0}' as an assembly name, but it is invalid.", full_name);
+                       }
+
+                       return aname != null;
+               }
+
+               internal static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion)
+               {
+                       return AssemblyNamesCompatible (a, b, specificVersion, true);
+               }
+
+               // if @specificVersion is true then match full name, else just the simple name
+               internal static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion,
+                               bool ignoreCase)
+               {
+                       if (String.Compare (a.Name, b.Name, ignoreCase) != 0)
+                               return false;
+
+                       if (!specificVersion)
+                               // ..and simple names match
+                               return true;
+
+                       if (a.CultureInfo != null && !a.CultureInfo.Equals (b.CultureInfo))
+                               return false;
+
+                       if (a.Version != null && a.Version != b.Version)
+                               return false;
+
+                       byte [] a_bytes = a.GetPublicKeyToken ();
+                       byte [] b_bytes = b.GetPublicKeyToken ();
+
+                       bool a_is_empty = (a_bytes == null || a_bytes.Length == 0);
+                       bool b_is_empty = (b_bytes == null || b_bytes.Length == 0);
+
+                       if (a_is_empty && b_is_empty)
+                               return true;
+
+                       if (a_is_empty || b_is_empty)
+                               return false;
+
+                       for (int i = 0; i < a_bytes.Length; i++)
+                               if (a_bytes [i] != b_bytes [i])
+                                       return false;
+
+                       return true;
+               }
+
+               public bool IsStrongNamed (AssemblyName name)
+               {
+                       return (name.Version != null &&
+                                       name.GetPublicKeyToken () != null &&
+                                       name.GetPublicKeyToken ().Length != 0);
+               }
+
+               // FIXME: to get default values of CopyLocal, compare with TargetFrameworkDirectories
+
+               // If metadata 'Private' is present then use that or use @default_copy_local_value
+               // as the value for CopyLocal
+               internal ResolvedReference GetResolvedReference (ITaskItem reference, string filename,
+                               AssemblyName aname, bool default_copy_local_value, SearchPath search_path)
+               {
+                       string pvt = reference.GetMetadata ("Private");
+
+                       bool copy_local = default_copy_local_value;
+                       if (!String.IsNullOrEmpty (pvt))
+                               //FIXME: log a warning for invalid value
+                               Boolean.TryParse (pvt, out copy_local);
+
+                       ITaskItem new_item = new TaskItem (reference);
+                       new_item.ItemSpec = filename;
+                       return new ResolvedReference (new_item, aname, copy_local, search_path, reference.ItemSpec);
+               }
+
+               public void LogSearchMessage (string msg, params object [] args)
+               {
+                       search_log.Add (String.Format (msg, args));
+               }
+
+               public void LogSearchLoggerMessages (MessageImportance importance)
+               {
+                       foreach (string msg in search_log)
+                               log.LogMessage (importance, msg);
+               }
+
+               public TaskLoggingHelper Log {
+                       set {
+                               log = value;
+                               PcFileCacheContext.Log = value;
+                       }
+               }
+
+               static LibraryPcFileCache PcCache  {
+                       get {
+                               if (cache == null) {
+                                       var context = new PcFileCacheContext ();
+                                       cache = new LibraryPcFileCache (context);
+                                       cache.Update ();
+                               }
+
+                               return cache;
+                       }
+               }
+       }
+
+       class TargetFrameworkAssemblies {
+               public string Path;
+
+               // assembly (simple) name -> (AssemblyName, file path)
+               public Dictionary <string, KeyValuePair<AssemblyName, string>> NameToAssemblyNameCache;
+
+               public TargetFrameworkAssemblies (string path)
+               {
+                       this.Path = path;
+                       NameToAssemblyNameCache = new Dictionary<string, KeyValuePair<AssemblyName, string>> (
+                                       StringComparer.InvariantCultureIgnoreCase);
                }
        }
+
+       class PcFileCacheContext : IPcFileCacheContext<LibraryPackageInfo>
+       {
+               public static TaskLoggingHelper Log;
+
+               // In the implementation of this method, the host application can extract
+               // information from the pc file and store it in the PackageInfo object
+               public void StoreCustomData (PcFile pcfile, LibraryPackageInfo pkg)
+               {
+               }
+
+               // Should return false if the provided package does not have required
+               // custom data
+               public bool IsCustomDataComplete (string pcfile, LibraryPackageInfo pkg)
+               {
+                       return true;
+               }
+
+               // Called to report errors
+               public void ReportError (string message, Exception ex)
+               {
+                       Log.LogMessage (MessageImportance.Low, "Error loading pkg-config files: {0} : {1}",
+                                       message, ex.ToString ());
+               }
+       }
+
+       enum SearchPath
+       {
+               Gac,
+               TargetFrameworkDirectory,
+               CandidateAssemblies,
+               HintPath,
+               Directory,
+               RawFileName,
+               PkgConfig
+       }
 }
 
+
+
 #endif