2 // ResolveAssemblyReference.cs: Searches for assembly files.
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;
34 using System.Globalization;
37 using System.Reflection;
38 using System.Security;
39 using Microsoft.Build.Framework;
40 using Microsoft.Build.Utilities;
42 namespace Microsoft.Build.Tasks {
43 public class ResolveAssemblyReference : TaskExtension {
46 ITaskItem[] assemblyFiles;
47 ITaskItem[] assemblies;
49 string[] allowedAssemblyExtensions;
50 string[] allowedRelatedFileExtensions;
51 string[] candidateAssemblyFiles;
52 ITaskItem[] copyLocalFiles;
53 ITaskItem[] filesWritten;
54 bool findDependencies;
55 bool findRelatedFiles;
57 bool findSerializationAssemblies;
58 string[] installedAssemblyTables;
59 ITaskItem[] relatedFiles;
60 ITaskItem[] resolvedDependencyFiles;
61 ITaskItem[] resolvedFiles;
62 ITaskItem[] satelliteFiles;
63 ITaskItem[] scatterFiles;
65 ITaskItem[] serializationAssemblyFiles;
68 ITaskItem[] suggestedRedirects;
69 string[] targetFrameworkDirectories;
70 string targetProcessorArchitecture;
71 static string [] default_assembly_extensions;
73 AssemblyResolver assembly_resolver;
74 List<string> dependency_search_paths;
75 Dictionary<string, ResolvedReference> assemblyNameToResolvedRef;
76 Dictionary<string, ITaskItem> tempSatelliteFiles, tempRelatedFiles,
77 tempResolvedDepFiles, tempCopyLocalFiles;
78 List<ITaskItem> tempResolvedFiles;
79 List<PrimaryReference> primaryReferences;
80 Dictionary<string, string> alreadyScannedAssemblyNames;
82 //FIXME: construct and use a graph of the dependencies, useful across projects
84 static ResolveAssemblyReference ()
86 default_assembly_extensions = new string [] { ".dll", ".exe" };
89 public ResolveAssemblyReference ()
91 assembly_resolver = new AssemblyResolver ();
94 //FIXME: make this reusable
95 public override bool Execute ()
97 assembly_resolver.Log = Log;
98 tempResolvedFiles = new List<ITaskItem> ();
99 tempCopyLocalFiles = new Dictionary<string, ITaskItem> ();
100 tempSatelliteFiles = new Dictionary<string, ITaskItem> ();
101 tempRelatedFiles = new Dictionary<string, ITaskItem> ();
102 tempResolvedDepFiles = new Dictionary<string, ITaskItem> ();
104 primaryReferences = new List<PrimaryReference> ();
105 assemblyNameToResolvedRef = new Dictionary<string, ResolvedReference> ();
107 foreach (ITaskItem item in assemblies) {
108 if (!String.IsNullOrEmpty (item.GetMetadata ("SubType"))) {
109 Log.LogWarning ("Reference '{0}' has non-empty SubType. Ignoring.", item.ItemSpec);
113 Log.LogMessage (MessageImportance.Low, "Primary Reference {0}", item.ItemSpec);
114 ResolvedReference resolved_ref = ResolveReference (item, searchPaths);
115 if (resolved_ref == null) {
116 Log.LogWarning ("\tReference '{0}' not resolved", item.ItemSpec);
117 Log.LogMessage ("{0}", assembly_resolver.SearchLogger.ToString ());
119 Log.LogMessage (MessageImportance.Low,
120 "\tReference {0} resolved to {1}. CopyLocal = {2}",
121 item.ItemSpec, resolved_ref.TaskItem,
122 resolved_ref.TaskItem.GetMetadata ("CopyLocal"));
124 Log.LogMessage (MessageImportance.Low,
125 "\tReference found at search path {0}",
126 resolved_ref.FoundInSearchPathAsString);
128 if (TryAddNewReference (tempResolvedFiles, resolved_ref) &&
129 !IsFromGacOrTargetFramework (resolved_ref)) {
130 primaryReferences.Add (new PrimaryReference (
131 resolved_ref.TaskItem,
132 resolved_ref.TaskItem.GetMetadata ("CopyLocal")));
137 ResolveAssemblyFiles ();
139 alreadyScannedAssemblyNames = new Dictionary<string, string> ();
141 // the first element is place holder for parent assembly's dir
142 dependency_search_paths = new List<string> () { String.Empty };
143 dependency_search_paths.AddRange (searchPaths);
145 // resolve dependencies
146 foreach (PrimaryReference pref in primaryReferences)
147 ResolveAssemblyFileDependencies (pref.TaskItem, pref.ParentCopyLocal);
149 resolvedFiles = tempResolvedFiles.ToArray ();
150 copyLocalFiles = tempCopyLocalFiles.Values.ToArray ();
151 satelliteFiles = tempSatelliteFiles.Values.ToArray ();
152 relatedFiles = tempRelatedFiles.Values.ToArray ();
153 resolvedDependencyFiles = tempResolvedDepFiles.Values.ToArray ();
155 tempResolvedFiles.Clear ();
156 tempCopyLocalFiles.Clear ();
157 tempSatelliteFiles.Clear ();
158 tempRelatedFiles.Clear ();
159 tempResolvedDepFiles.Clear ();
160 alreadyScannedAssemblyNames.Clear ();
161 primaryReferences.Clear ();
162 assemblyNameToResolvedRef.Clear ();
163 dependency_search_paths = null;
168 // Use @search_paths to resolve the reference
169 ResolvedReference ResolveReference (ITaskItem item, IEnumerable<string> search_paths)
171 ResolvedReference resolved = null;
172 bool specific_version;
174 assembly_resolver.ResetSearchLogger ();
176 if (!TryGetSpecificVersionValue (item, out specific_version))
179 foreach (string spath in search_paths) {
180 assembly_resolver.SearchLogger.WriteLine ("For searchpath {0}", spath);
182 if (String.Compare (spath, "{HintPathFromItem}") == 0) {
183 resolved = assembly_resolver.ResolveHintPathReference (item, specific_version);
184 } else if (String.Compare (spath, "{TargetFrameworkDirectory}") == 0) {
185 foreach (string fpath in targetFrameworkDirectories) {
186 resolved = assembly_resolver.FindInTargetFramework (item,
187 fpath, specific_version);
188 if (resolved != null)
191 } else if (String.Compare (spath, "{GAC}") == 0) {
192 resolved = assembly_resolver.ResolveGacReference (item, specific_version);
193 } else if (String.Compare (spath, "{RawFileName}") == 0) {
194 //FIXME: identify assembly names, as extract the name, and try with that?
195 AssemblyName aname = assembly_resolver.GetAssemblyNameFromFile (item.ItemSpec);
197 resolved = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
198 SearchPath.RawFileName);
199 } else if (String.Compare (spath, "{CandidateAssemblyFiles}") == 0) {
200 assembly_resolver.SearchLogger.WriteLine (
201 "Warning: {CandidateAssemblyFiles} not supported currently");
202 } else if (String.Compare (spath, "{PkgConfig}") == 0) {
203 resolved = assembly_resolver.ResolvePkgConfigReference (item, specific_version);
205 resolved = assembly_resolver.FindInDirectory (
207 allowedAssemblyExtensions ?? default_assembly_extensions);
210 if (resolved != null)
214 if (resolved != null)
215 SetCopyLocal (resolved.TaskItem, resolved.CopyLocal.ToString ());
220 bool TryGetSpecificVersionValue (ITaskItem item, out bool specific_version)
222 specific_version = true;
223 string value = item.GetMetadata ("SpecificVersion");
224 if (String.IsNullOrEmpty (value)) {
225 AssemblyName name = new AssemblyName (item.ItemSpec);
226 // If SpecificVersion is not specified, then
227 // it is true if the Include is a strong name else false
228 specific_version = assembly_resolver.IsStrongNamed (name);
232 if (Boolean.TryParse (value, out specific_version))
235 Log.LogError ("Item '{0}' has attribute SpecificVersion with invalid value '{1}' " +
236 "which could not be converted to a boolean.", item.ItemSpec, value);
240 //FIXME: Consider CandidateAssemblyFiles also here
241 void ResolveAssemblyFiles ()
243 foreach (ITaskItem item in assemblyFiles) {
244 assembly_resolver.ResetSearchLogger ();
246 if (!File.Exists (item.ItemSpec)) {
247 Log.LogMessage (MessageImportance.Low,
248 "Primary Reference from AssemblyFiles {0}, file not found. Ignoring",
253 Log.LogMessage (MessageImportance.Low, "Primary Reference from AssemblyFiles {0}", item.ItemSpec);
256 AssemblyName aname = assembly_resolver.GetAssemblyNameFromFile (item.ItemSpec);
258 Log.LogWarning ("\tReference '{0}' not resolved", item.ItemSpec);
259 Log.LogMessage ("{0}", assembly_resolver.SearchLogger.ToString ());
263 ResolvedReference rr = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
264 SearchPath.RawFileName);
265 copy_local = rr.CopyLocal.ToString ();
267 if (!TryAddNewReference (tempResolvedFiles, rr))
271 SetCopyLocal (rr.TaskItem, copy_local);
273 FindAndAddRelatedFiles (item.ItemSpec, copy_local);
274 FindAndAddSatellites (item.ItemSpec, copy_local);
276 if (FindDependencies && !IsFromGacOrTargetFramework (rr))
277 primaryReferences.Add (new PrimaryReference (item, copy_local));
281 // Tries to resolve assemblies referenced by @item
282 // Skips gac references
284 void ResolveAssemblyFileDependencies (ITaskItem item, string parent_copy_local)
286 Queue<string> dependencies = new Queue<string> ();
287 dependencies.Enqueue (item.ItemSpec);
289 while (dependencies.Count > 0) {
290 string filename = Path.GetFullPath (dependencies.Dequeue ());
291 Assembly asm = Assembly.ReflectionOnlyLoadFrom (filename);
292 if (alreadyScannedAssemblyNames.ContainsKey (asm.FullName))
295 // set the 1st search path to this ref's base path
296 // Will be used for resolving the dependencies
297 dependency_search_paths [0] = Path.GetDirectoryName (filename);
299 foreach (AssemblyName aname in asm.GetReferencedAssemblies ()) {
300 if (alreadyScannedAssemblyNames.ContainsKey (aname.FullName))
303 ResolvedReference resolved_ref = ResolveDependencyByAssemblyName (
304 aname, asm.FullName, parent_copy_local);
306 if (resolved_ref != null && !IsFromGacOrTargetFramework (resolved_ref))
307 dependencies.Enqueue (resolved_ref.TaskItem.ItemSpec);
309 alreadyScannedAssemblyNames.Add (asm.FullName, String.Empty);
313 // Resolves by looking dependency_search_paths
314 // which is dir of parent reference file, and
316 ResolvedReference ResolveDependencyByAssemblyName (AssemblyName aname, string parent_asm_name,
317 string parent_copy_local)
319 // This will check for compatible assembly name/version
320 ResolvedReference resolved_ref;
321 if (TryGetResolvedReferenceByAssemblyName (aname, false, out resolved_ref))
324 Log.LogMessage (MessageImportance.Low, "Dependency {0}", aname);
325 Log.LogMessage (MessageImportance.Low, "\tRequired by {0}", parent_asm_name);
327 ITaskItem item = new TaskItem (aname.FullName);
328 item.SetMetadata ("SpecificVersion", "false");
329 resolved_ref = ResolveReference (item, dependency_search_paths);
331 string copy_local = "false";
332 if (resolved_ref != null) {
333 Log.LogMessage (MessageImportance.Low, "\tReference {0} resolved to {1}.",
334 aname, resolved_ref.TaskItem.ItemSpec);
336 Log.LogMessage (MessageImportance.Low,
337 "\tReference found at search path {0}",
338 resolved_ref.FoundInSearchPathAsString);
340 if (resolved_ref.FoundInSearchPath == SearchPath.Directory) {
341 // override CopyLocal with parent's val
342 resolved_ref.TaskItem.SetMetadata ("CopyLocal", parent_copy_local);
344 Log.LogMessage (MessageImportance.Low,
345 "\tThis is CopyLocal {0} as parent item has this value",
348 if (TryAddNewReference (tempResolvedFiles, resolved_ref)) {
349 FindAndAddRelatedFiles (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
350 FindAndAddSatellites (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
354 Log.LogMessage (MessageImportance.Low,
355 "\tThis is CopyLocal {0} as it is in the gac," +
356 "target framework directory or provided by a package.",
359 TryAddNewReference (tempResolvedFiles, resolved_ref);
362 Log.LogWarning ("\tReference '{0}' not resolved", aname);
363 Log.LogMessage ("{0}", assembly_resolver.SearchLogger.ToString ());
369 void FindAndAddRelatedFiles (string filename, string parent_copy_local)
371 if (!findRelatedFiles || allowedRelatedFileExtensions == null)
374 foreach (string ext in allowedRelatedFileExtensions) {
375 string rfile = filename + ext;
376 if (File.Exists (rfile)) {
377 ITaskItem item = new TaskItem (rfile);
378 SetCopyLocal (item, parent_copy_local);
380 tempRelatedFiles.AddUniqueFile (item);
385 void FindAndAddSatellites (string filename, string parent_copy_local)
390 string basepath = Path.GetDirectoryName (filename);
391 string resource = String.Format ("{0}{1}{2}",
392 Path.GetFileNameWithoutExtension (filename),
394 Path.GetExtension (filename));
396 string dir_sep = Path.DirectorySeparatorChar.ToString ();
397 foreach (string dir in Directory.GetDirectories (basepath)) {
398 string culture = Path.GetFileName (dir);
399 if (!CultureNamesTable.ContainsKey (culture))
402 string res_path = Path.Combine (dir, resource);
403 if (File.Exists (res_path)) {
404 ITaskItem item = new TaskItem (res_path);
405 SetCopyLocal (item, parent_copy_local);
406 item.SetMetadata ("DestinationSubdirectory", culture + dir_sep);
407 tempSatelliteFiles.AddUniqueFile (item);
412 // returns true is it was new
413 bool TryAddNewReference (List<ITaskItem> file_list, ResolvedReference key_ref)
415 ResolvedReference found_ref;
416 if (!TryGetResolvedReferenceByAssemblyName (key_ref.AssemblyName, key_ref.IsPrimary, out found_ref)) {
417 assemblyNameToResolvedRef [key_ref.AssemblyName.Name] = key_ref;
418 file_list.Add (key_ref.TaskItem);
425 void SetCopyLocal (ITaskItem item, string copy_local)
427 item.SetMetadata ("CopyLocal", copy_local);
429 // Assumed to be valid value
430 if (Boolean.Parse (copy_local))
431 tempCopyLocalFiles.AddUniqueFile (item);
434 bool TryGetResolvedReferenceByAssemblyName (AssemblyName key_aname, bool is_primary, out ResolvedReference found_ref)
437 // Match by just name
438 if (!assemblyNameToResolvedRef.TryGetValue (key_aname.Name, out found_ref))
442 // match for full name
443 if (AssemblyResolver.AssemblyNamesCompatible (key_aname, found_ref.AssemblyName, true))
444 // exact match, so its already there, dont add anything
447 // we have a name match, but version mismatch!
448 assembly_resolver.SearchLogger.WriteLine ("A conflict was detected between '{0}' and '{1}'",
449 key_aname.FullName, found_ref.AssemblyName.FullName);
451 if (is_primary == found_ref.IsPrimary) {
452 assembly_resolver.SearchLogger.WriteLine ("Unable to choose between the two. " +
453 "Choosing '{0}' arbitrarily.", found_ref.AssemblyName.FullName);
457 // since all dependencies are processed after
458 // all primary refererences, the one in the cache
459 // has to be a primary
460 // Prefer a primary reference over a dependency
462 assembly_resolver.SearchLogger.WriteLine ("Choosing '{0}' as it is a primary reference.",
463 found_ref.AssemblyName.FullName);
465 Log.LogWarning ("Found a conflict between : '{0}' and '{1}'. Using '{0}' reference.",
466 found_ref.AssemblyName.FullName,
472 bool IsCopyLocal (ITaskItem item)
474 return Boolean.Parse (item.GetMetadata ("CopyLocal"));
477 bool IsFromTargetFramework (string filename)
479 foreach (string fpath in targetFrameworkDirectories)
480 if (filename.StartsWith (fpath))
486 bool IsFromGacOrTargetFramework (ResolvedReference rr)
488 return rr.FoundInSearchPath == SearchPath.Gac ||
489 rr.FoundInSearchPath == SearchPath.TargetFrameworkDirectory;
492 public bool AutoUnify {
493 get { return autoUnify; }
494 set { autoUnify = value; }
497 public ITaskItem[] AssemblyFiles {
498 get { return assemblyFiles; }
499 set { assemblyFiles = value; }
502 public ITaskItem[] Assemblies {
503 get { return assemblies; }
504 set { assemblies = value; }
507 public string AppConfigFile {
508 get { return appConfigFile; }
509 set { appConfigFile = value; }
512 public string[] AllowedAssemblyExtensions {
513 get { return allowedAssemblyExtensions; }
514 set { allowedAssemblyExtensions = value; }
517 public string[] AllowedRelatedFileExtensions {
518 get { return allowedRelatedFileExtensions; }
519 set { allowedRelatedFileExtensions = value; }
522 public string[] CandidateAssemblyFiles {
523 get { return candidateAssemblyFiles; }
524 set { candidateAssemblyFiles = value; }
528 public ITaskItem[] CopyLocalFiles {
529 get { return copyLocalFiles; }
533 public ITaskItem[] FilesWritten {
534 get { return filesWritten; }
535 set { filesWritten = value; }
538 public bool FindDependencies {
539 get { return findDependencies; }
540 set { findDependencies = value; }
543 public bool FindRelatedFiles {
544 get { return findRelatedFiles; }
545 set { findRelatedFiles = value; }
548 public bool FindSatellites {
549 get { return findSatellites; }
550 set { findSatellites = value; }
553 public bool FindSerializationAssemblies {
554 get { return findSerializationAssemblies; }
555 set { findSerializationAssemblies = value; }
558 public string[] InstalledAssemblyTables {
559 get { return installedAssemblyTables; }
560 set { installedAssemblyTables = value; }
564 public ITaskItem[] RelatedFiles {
565 get { return relatedFiles; }
569 public ITaskItem[] ResolvedDependencyFiles {
570 get { return resolvedDependencyFiles; }
574 public ITaskItem[] ResolvedFiles {
575 get { return resolvedFiles; }
579 public ITaskItem[] SatelliteFiles {
580 get { return satelliteFiles; }
584 public ITaskItem[] ScatterFiles {
585 get { return scatterFiles; }
589 public string[] SearchPaths {
590 get { return searchPaths; }
591 set { searchPaths = value; }
595 public ITaskItem[] SerializationAssemblyFiles {
596 get { return serializationAssemblyFiles; }
600 get { return silent; }
601 set { silent = value; }
604 public string StateFile {
605 get { return stateFile; }
606 set { stateFile = value; }
610 public ITaskItem[] SuggestedRedirects {
611 get { return suggestedRedirects; }
614 public string[] TargetFrameworkDirectories {
615 get { return targetFrameworkDirectories; }
616 set { targetFrameworkDirectories = value; }
619 public string TargetProcessorArchitecture {
620 get { return targetProcessorArchitecture; }
621 set { targetProcessorArchitecture = value; }
624 static Dictionary<string, string> cultureNamesTable;
625 static Dictionary<string, string> CultureNamesTable {
627 if (cultureNamesTable == null) {
628 cultureNamesTable = new Dictionary<string, string> ();
629 foreach (CultureInfo ci in CultureInfo.GetCultures (CultureTypes.AllCultures))
630 cultureNamesTable [ci.Name] = ci.Name;
633 return cultureNamesTable;
638 static class ResolveAssemblyReferenceHelper {
639 public static void AddUniqueFile (this Dictionary<string, ITaskItem> dic, ITaskItem item)
642 throw new ArgumentNullException ("dic");
644 throw new ArgumentNullException ("item");
646 string fullpath = Path.GetFullPath (item.ItemSpec);
647 if (!dic.ContainsKey (fullpath))
648 dic [fullpath] = item;
652 struct PrimaryReference {
653 public ITaskItem TaskItem;
654 public string ParentCopyLocal;
656 public PrimaryReference (ITaskItem item, string parent_copy_local)
659 ParentCopyLocal = parent_copy_local;