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.
32 using System.Collections.Generic;
35 using System.Reflection;
36 using System.Security;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
41 namespace Microsoft.Build.Tasks {
42 internal class AssemblyResolver {
44 // name -> (version -> assemblypath)
45 static Dictionary<string, TargetFrameworkAssemblies> target_framework_cache;
46 static Dictionary<string, Dictionary<Version, string>> gac;
47 TaskLoggingHelper log;
48 List<string> search_log;
50 static LibraryPcFileCache cache;
52 public AssemblyResolver ()
55 gac = new Dictionary<string, Dictionary<Version, string>> ();
56 target_framework_cache = new Dictionary <string, TargetFrameworkAssemblies> ();
58 GatherGacAssemblies ();
62 public void ResetSearchLogger ()
64 if (search_log == null)
65 search_log = new List<string> ();
72 // NOTE: code from mcs/tools/gacutil/driver.cs
73 PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath", BindingFlags.Static | BindingFlags.NonPublic);
78 MethodInfo get_gac = gac.GetGetMethod (true);
79 return (string) get_gac.Invoke (null, null);
82 void GatherGacAssemblies ()
84 string gac_path = GetGacPath ();
86 throw new InvalidOperationException ("XBuild must be run on Mono runtime");
87 if (!Directory.Exists (gac_path))
88 return; // in case mono isn't "installed".
91 DirectoryInfo version_info, assembly_info;
93 foreach (string assembly_name in Directory.GetDirectories (gac_path)) {
94 assembly_info = new DirectoryInfo (assembly_name);
95 foreach (string version_token in Directory.GetDirectories (assembly_name)) {
96 foreach (string file in Directory.GetFiles (version_token, "*.dll")) {
97 version_info = new DirectoryInfo (version_token);
98 version = new Version (version_info.Name.Split (
99 new char [] {'_'}, StringSplitOptions.RemoveEmptyEntries) [0]);
101 Dictionary<Version, string> assembliesByVersion = new Dictionary <Version, string> ();
102 if (!gac.TryGetValue (assembly_info.Name, out assembliesByVersion)) {
103 assembliesByVersion = new Dictionary <Version, string> ();
104 gac.Add (assembly_info.Name, assembliesByVersion);
108 if (assembliesByVersion.TryGetValue (version, out found_file) &&
109 File.GetLastWriteTime (file) <= File.GetLastWriteTime (found_file))
110 // Duplicate found, take the newer file
113 assembliesByVersion [version] = file;
119 public ResolvedReference FindInTargetFramework (ITaskItem reference, string framework_dir, bool specific_version)
121 if (!Directory.Exists (framework_dir))
124 AssemblyName key_aname;
125 if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out key_aname))
128 TargetFrameworkAssemblies gac_asm;
129 if (!target_framework_cache.TryGetValue (framework_dir, out gac_asm)) {
131 gac_asm = target_framework_cache [framework_dir] = PopulateTargetFrameworkAssemblies (framework_dir);
134 KeyValuePair<AssemblyName, string> pair;
135 if (gac_asm.NameToAssemblyNameCache.TryGetValue (key_aname.Name, out pair)) {
136 if (AssemblyNamesCompatible (key_aname, pair.Key, specific_version)) {
137 // gac and tgt frmwk refs are not copied private
138 return GetResolvedReference (reference, pair.Value, pair.Key, false,
139 SearchPath.TargetFrameworkDirectory);
142 LogSearchMessage ("Considered target framework dir {0}, assembly name '{1}' did not " +
143 "match the expected '{2}' (SpecificVersion={3})",
144 framework_dir, pair.Key, key_aname, specific_version);
146 LogSearchMessage ("Considered target framework dir {0}, assembly named '{1}' not found.",
147 framework_dir, key_aname.Name);
152 // Look for %(Identity).{dll|exe|..}
153 // if specific_version==true
154 // resolve if assembly names match
156 // resolve the valid assembly
157 public ResolvedReference FindInDirectory (ITaskItem reference, string directory, string [] file_extensions, bool specific_version)
159 string filename = reference.ItemSpec;
160 int comma_pos = filename.IndexOf (',');
162 filename = filename.Substring (0, comma_pos);
165 string path = Path.GetFullPath (Path.Combine (directory, filename));
166 AssemblyName aname = null;
167 if (specific_version && !TryGetAssemblyNameFromFullName (reference.ItemSpec, out aname))
170 ResolvedReference resolved_ref = ResolveReferenceForPath (path, reference, aname, null, SearchPath.Directory, specific_version);
171 if (resolved_ref != null)
174 // try path + Include + {.dll|.exe|..}
175 foreach (string extn in file_extensions) {
176 resolved_ref = ResolveReferenceForPath (path + extn, reference, aname, null, SearchPath.Directory, specific_version);
177 if (resolved_ref != null)
184 // tries to resolve reference from the given file path, and compares assembly names
185 // if @specific_version == true, and logs accordingly
186 ResolvedReference ResolveReferenceForPath (string filename, ITaskItem reference, AssemblyName aname,
187 string error_message, SearchPath spath, bool specific_version)
189 AssemblyName found_aname;
190 if (!TryGetAssemblyNameFromFile (filename, out found_aname)) {
191 if (error_message != null)
192 log.LogMessage (MessageImportance.Low, error_message);
196 if (!specific_version || AssemblyNamesCompatible (aname, found_aname, specific_version)) {
197 // Check compatibility only if specific_version == true
198 return GetResolvedReference (reference, filename, found_aname, true, spath);
200 LogSearchMessage ("Considered '{0}', but assembly name '{1}' did not match the " +
201 "expected '{2}' (SpecificVersion={3})", filename, found_aname, aname, specific_version);
202 log.LogMessage (MessageImportance.Low, "Assembly names are not compatible.");
208 TargetFrameworkAssemblies PopulateTargetFrameworkAssemblies (string directory)
210 TargetFrameworkAssemblies gac_asm = new TargetFrameworkAssemblies (directory);
211 foreach (string file in Directory.GetFiles (directory, "*.dll")) {
213 if (TryGetAssemblyNameFromFile (file, out aname))
214 gac_asm.NameToAssemblyNameCache [aname.Name] =
215 new KeyValuePair<AssemblyName, string> (aname, file);
221 public ResolvedReference ResolveGacReference (ITaskItem reference, bool specific_version)
224 if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out name))
227 if (!gac.ContainsKey (name.Name)) {
228 LogSearchMessage ("Considered {0}, but could not find in the GAC.",
233 if (name.Version != null) {
235 if (gac [name.Name].TryGetValue (name.Version, out ret))
236 return GetResolvedReference (reference, ret, name, false, SearchPath.Gac);
239 if (specific_version) {
240 LogSearchMessage ("Considered '{0}', but an assembly with the specific version not found.",
246 Version [] versions = new Version [gac [name.Name].Keys.Count];
247 gac [name.Name].Keys.CopyTo (versions, 0);
248 Array.Sort (versions, (IComparer <Version>) null);
249 Version highest = versions [versions.Length - 1];
250 //FIXME: the aname being used here isn't correct, its version should
251 // actually match "highest"
252 return GetResolvedReference (reference, gac [name.Name] [highest], name, false, SearchPath.Gac);
255 public ResolvedReference ResolvePkgConfigReference (ITaskItem reference, bool specific_version)
257 PackageAssemblyInfo pkg = null;
259 pkg = PcCache.GetAssemblyLocation (reference.ItemSpec);
260 if (pkg == null && !specific_version) {
261 // if not specific version, then just match simple name
262 string name = reference.ItemSpec;
263 if (name.IndexOf (',') > 0)
264 name = name.Substring (0, name.IndexOf (','));
265 pkg = PcCache.ResolveAssemblyName (name).FirstOrDefault ();
269 LogSearchMessage ("Considered {0}, but could not find in any pkg-config files.",
275 if (!TryGetAssemblyNameFromFullName (pkg.FullName, out aname))
278 ResolvedReference rr = GetResolvedReference (reference, pkg.File, aname,
279 false, SearchPath.PkgConfig);
280 rr.FoundInSearchPathAsString = String.Format ("{{PkgConfig}} provided by package named {0}",
281 pkg.ParentPackage.Name);
286 // HintPath has a valid assembly
287 // if specific_version==true
288 // resolve if assembly names match
290 // resolve the valid assembly
291 public ResolvedReference ResolveHintPathReference (ITaskItem reference, bool specific_version)
293 string hintpath = reference.GetMetadata ("HintPath");
294 if (String.IsNullOrEmpty (hintpath)) {
295 LogSearchMessage ("HintPath attribute not found");
299 if (!File.Exists (hintpath)) {
300 log.LogMessage (MessageImportance.Low, "HintPath {0} does not exist.", hintpath);
301 LogSearchMessage ("Considered {0}, but it does not exist.", hintpath);
306 if (!TryGetAssemblyNameFromFullName (reference.ItemSpec, out aname))
309 return ResolveReferenceForPath (hintpath, reference, aname,
310 String.Format ("File at HintPath {0}, is either an invalid assembly or the file does not exist.", hintpath),
311 SearchPath.HintPath, specific_version);
314 static Dictionary<string, AssemblyName> assemblyNameCache = new Dictionary<string, AssemblyName> ();
315 public bool TryGetAssemblyNameFromFile (string filename, out AssemblyName aname)
317 filename = Path.GetFullPath (filename);
318 if (assemblyNameCache.TryGetValue (filename, out aname))
319 return aname != null;
323 aname = AssemblyName.GetAssemblyName (filename);
324 } catch (FileNotFoundException) {
325 LogSearchMessage ("Considered '{0}' as a file, but the file does not exist",
327 } catch (BadImageFormatException) {
328 LogSearchMessage ("Considered '{0}' as a file, but it is an invalid assembly",
332 assemblyNameCache [filename] = aname;
333 return aname != null;
336 bool TryGetAssemblyNameFromFullName (string full_name, out AssemblyName aname)
340 aname = new AssemblyName (full_name);
341 } catch (FileLoadException) {
342 LogSearchMessage ("Considered '{0}' as an assembly name, but it is invalid.", full_name);
345 return aname != null;
348 internal static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion)
350 return AssemblyNamesCompatible (a, b, specificVersion, true);
353 // if @specificVersion is true then match full name, else just the simple name
354 internal static bool AssemblyNamesCompatible (AssemblyName a, AssemblyName b, bool specificVersion,
357 if (String.Compare (a.Name, b.Name, ignoreCase) != 0)
360 if (!specificVersion)
361 // ..and simple names match
364 if (a.CultureInfo != null && !a.CultureInfo.Equals (b.CultureInfo))
367 if (a.Version != null && a.Version != b.Version)
370 byte [] a_bytes = a.GetPublicKeyToken ();
371 byte [] b_bytes = b.GetPublicKeyToken ();
373 bool a_is_empty = (a_bytes == null || a_bytes.Length == 0);
374 bool b_is_empty = (b_bytes == null || b_bytes.Length == 0);
376 if (a_is_empty || b_is_empty)
379 for (int i = 0; i < a_bytes.Length; i++)
380 if (a_bytes [i] != b_bytes [i])
386 public bool IsStrongNamed (AssemblyName name)
388 return (name.Version != null &&
389 name.GetPublicKeyToken () != null &&
390 name.GetPublicKeyToken ().Length != 0);
393 // FIXME: to get default values of CopyLocal, compare with TargetFrameworkDirectories
395 // If metadata 'Private' is present then use that or use @default_copy_local_value
396 // as the value for CopyLocal
397 internal ResolvedReference GetResolvedReference (ITaskItem reference, string filename,
398 AssemblyName aname, bool default_copy_local_value, SearchPath search_path)
400 string pvt = reference.GetMetadata ("Private");
402 bool copy_local = default_copy_local_value;
403 if (!String.IsNullOrEmpty (pvt))
404 //FIXME: log a warning for invalid value
405 Boolean.TryParse (pvt, out copy_local);
407 ITaskItem new_item = new TaskItem (reference);
408 new_item.ItemSpec = filename;
409 return new ResolvedReference (new_item, aname, copy_local, search_path, reference.ItemSpec);
412 public void LogSearchMessage (string msg, params object [] args)
414 search_log.Add (String.Format (msg, args));
417 public void LogSearchLoggerMessages (MessageImportance importance)
419 foreach (string msg in search_log)
420 log.LogMessage (importance, msg);
423 public TaskLoggingHelper Log {
426 PcFileCacheContext.Log = value;
430 static LibraryPcFileCache PcCache {
433 var context = new PcFileCacheContext ();
434 cache = new LibraryPcFileCache (context);
443 class TargetFrameworkAssemblies {
446 // assembly (simple) name -> (AssemblyName, file path)
447 public Dictionary <string, KeyValuePair<AssemblyName, string>> NameToAssemblyNameCache;
449 public TargetFrameworkAssemblies (string path)
452 NameToAssemblyNameCache = new Dictionary<string, KeyValuePair<AssemblyName, string>> (
453 StringComparer.OrdinalIgnoreCase);
457 class PcFileCacheContext : IPcFileCacheContext<LibraryPackageInfo>
459 public static TaskLoggingHelper Log;
461 // In the implementation of this method, the host application can extract
462 // information from the pc file and store it in the PackageInfo object
463 public void StoreCustomData (PcFile pcfile, LibraryPackageInfo pkg)
467 // Should return false if the provided package does not have required
469 public bool IsCustomDataComplete (string pcfile, LibraryPackageInfo pkg)
474 // Called to report errors
475 public void ReportError (string message, Exception ex)
477 Log.LogMessage (MessageImportance.Low, "Error loading pkg-config files: {0} : {1}",
478 message, ex.ToString ());
485 TargetFrameworkDirectory,