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;
81 Dictionary<string, string> conflictWarningsCache;
83 //FIXME: construct and use a graph of the dependencies, useful across projects
85 static ResolveAssemblyReference ()
87 default_assembly_extensions = new string [] { ".dll", ".exe" };
90 public ResolveAssemblyReference ()
92 assembly_resolver = new AssemblyResolver ();
95 //FIXME: make this reusable
96 public override bool Execute ()
98 if (assemblies == null && assemblyFiles == null)
102 LogTaskParameters ();
104 assembly_resolver.Log = Log;
105 tempResolvedFiles = new List<ITaskItem> ();
106 tempCopyLocalFiles = new Dictionary<string, ITaskItem> ();
107 tempSatelliteFiles = new Dictionary<string, ITaskItem> ();
108 tempRelatedFiles = new Dictionary<string, ITaskItem> ();
109 tempResolvedDepFiles = new Dictionary<string, ITaskItem> ();
111 primaryReferences = new List<PrimaryReference> ();
112 assemblyNameToResolvedRef = new Dictionary<string, ResolvedReference> ();
113 conflictWarningsCache = new Dictionary<string, string> ();
115 ResolveAssemblies ();
116 ResolveAssemblyFiles ();
117 resolvedFiles = tempResolvedFiles.ToArray ();
119 alreadyScannedAssemblyNames = new Dictionary<string, string> ();
121 // the first element is place holder for parent assembly's dir
122 dependency_search_paths = new List<string> () { String.Empty };
123 dependency_search_paths.AddRange (searchPaths);
125 // resolve dependencies
126 foreach (PrimaryReference pref in primaryReferences)
127 ResolveAssemblyFileDependencies (pref.TaskItem, pref.ParentCopyLocal);
129 copyLocalFiles = tempCopyLocalFiles.Values.ToArray ();
130 satelliteFiles = tempSatelliteFiles.Values.ToArray ();
131 relatedFiles = tempRelatedFiles.Values.ToArray ();
132 resolvedDependencyFiles = tempResolvedDepFiles.Values.ToArray ();
134 tempResolvedFiles.Clear ();
135 tempCopyLocalFiles.Clear ();
136 tempSatelliteFiles.Clear ();
137 tempRelatedFiles.Clear ();
138 tempResolvedDepFiles.Clear ();
139 alreadyScannedAssemblyNames.Clear ();
140 primaryReferences.Clear ();
141 assemblyNameToResolvedRef.Clear ();
142 conflictWarningsCache.Clear ();
143 dependency_search_paths = null;
148 void ResolveAssemblies ()
150 if (assemblies == null || assemblies.Length == 0)
153 foreach (ITaskItem item in assemblies) {
154 if (!String.IsNullOrEmpty (item.GetMetadata ("SubType"))) {
155 Log.LogWarning ("Reference '{0}' has non-empty SubType. Ignoring.", item.ItemSpec);
159 LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference {0}", item.ItemSpec);
160 ResolvedReference resolved_ref = ResolveReference (item, searchPaths, true);
161 if (resolved_ref == null) {
162 Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec);
163 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal);
165 if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null)
166 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
168 Log.LogMessage (MessageImportance.Low,
169 "\tReference {0} resolved to {1}. CopyLocal = {2}",
170 item.ItemSpec, resolved_ref.TaskItem,
171 resolved_ref.TaskItem.GetMetadata ("CopyLocal"));
173 Log.LogMessage (MessageImportance.Low,
174 "\tReference found at search path {0}",
175 resolved_ref.FoundInSearchPathAsString);
177 if (TryAddNewReference (tempResolvedFiles, resolved_ref) &&
178 !IsFromGacOrTargetFramework (resolved_ref) &&
179 resolved_ref.FoundInSearchPath != SearchPath.PkgConfig) {
180 primaryReferences.Add (new PrimaryReference (
181 resolved_ref.TaskItem,
182 resolved_ref.TaskItem.GetMetadata ("CopyLocal")));
188 // Use @search_paths to resolve the reference
189 ResolvedReference ResolveReference (ITaskItem item, IEnumerable<string> search_paths, bool set_copy_local)
191 ResolvedReference resolved = null;
192 bool specific_version;
194 assembly_resolver.ResetSearchLogger ();
196 if (!TryGetSpecificVersionValue (item, out specific_version))
199 foreach (string spath in search_paths) {
200 assembly_resolver.LogSearchMessage ("For searchpath {0}", spath);
202 if (String.Compare (spath, "{HintPathFromItem}") == 0) {
203 resolved = assembly_resolver.ResolveHintPathReference (item, specific_version);
204 } else if (String.Compare (spath, "{TargetFrameworkDirectory}") == 0) {
205 if (targetFrameworkDirectories == null)
207 foreach (string fpath in targetFrameworkDirectories) {
208 resolved = assembly_resolver.FindInTargetFramework (item,
209 fpath, specific_version);
210 if (resolved != null)
213 } else if (String.Compare (spath, "{GAC}") == 0) {
214 resolved = assembly_resolver.ResolveGacReference (item, specific_version);
215 } else if (String.Compare (spath, "{RawFileName}") == 0) {
216 //FIXME: identify assembly names, as extract the name, and try with that?
218 if (assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname))
219 resolved = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
220 SearchPath.RawFileName);
221 } else if (String.Compare (spath, "{CandidateAssemblyFiles}") == 0) {
222 assembly_resolver.LogSearchMessage (
223 "Warning: {{CandidateAssemblyFiles}} not supported currently");
224 } else if (String.Compare (spath, "{PkgConfig}") == 0) {
225 resolved = assembly_resolver.ResolvePkgConfigReference (item, specific_version);
227 resolved = assembly_resolver.FindInDirectory (
229 allowedAssemblyExtensions ?? default_assembly_extensions,
233 if (resolved != null)
237 if (resolved != null && set_copy_local)
238 SetCopyLocal (resolved.TaskItem, resolved.CopyLocal.ToString ());
243 bool TryGetSpecificVersionValue (ITaskItem item, out bool specific_version)
245 specific_version = true;
246 string value = item.GetMetadata ("SpecificVersion");
247 if (String.IsNullOrEmpty (value)) {
248 //AssemblyName name = new AssemblyName (item.ItemSpec);
249 // If SpecificVersion is not specified, then
250 // it is true if the Include is a strong name else false
251 //specific_version = assembly_resolver.IsStrongNamed (name);
253 // msbuild seems to just look for a ',' in the name :/
254 specific_version = item.ItemSpec.IndexOf (',') >= 0;
258 if (Boolean.TryParse (value, out specific_version))
261 Log.LogError ("Item '{0}' has attribute SpecificVersion with invalid value '{1}' " +
262 "which could not be converted to a boolean.", item.ItemSpec, value);
266 //FIXME: Consider CandidateAssemblyFiles also here
267 void ResolveAssemblyFiles ()
269 if (assemblyFiles == null)
272 foreach (ITaskItem item in assemblyFiles) {
273 assembly_resolver.ResetSearchLogger ();
275 if (!File.Exists (item.ItemSpec)) {
276 LogWithPrecedingNewLine (MessageImportance.Low,
277 "Primary Reference from AssemblyFiles {0}, file not found. Ignoring",
282 LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference from AssemblyFiles {0}", item.ItemSpec);
286 if (!assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname)) {
287 Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec);
288 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal);
292 if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null)
293 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
295 ResolvedReference rr = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true,
296 SearchPath.RawFileName);
297 copy_local = rr.CopyLocal.ToString ();
299 if (!TryAddNewReference (tempResolvedFiles, rr))
303 SetCopyLocal (rr.TaskItem, copy_local);
305 FindAndAddRelatedFiles (item.ItemSpec, copy_local);
306 FindAndAddSatellites (item.ItemSpec, copy_local);
308 if (FindDependencies && !IsFromGacOrTargetFramework (rr) &&
309 rr.FoundInSearchPath != SearchPath.PkgConfig)
310 primaryReferences.Add (new PrimaryReference (item, copy_local));
314 // Tries to resolve assemblies referenced by @item
315 // Skips gac references
317 void ResolveAssemblyFileDependencies (ITaskItem item, string parent_copy_local)
319 Queue<string> dependencies = new Queue<string> ();
320 dependencies.Enqueue (item.ItemSpec);
322 while (dependencies.Count > 0) {
323 string filename = Path.GetFullPath (dependencies.Dequeue ());
324 Assembly asm = Assembly.ReflectionOnlyLoadFrom (filename);
325 if (alreadyScannedAssemblyNames.ContainsKey (asm.FullName))
328 // set the 1st search path to this ref's base path
329 // Will be used for resolving the dependencies
330 dependency_search_paths [0] = Path.GetDirectoryName (filename);
332 foreach (AssemblyName aname in asm.GetReferencedAssemblies ()) {
333 if (alreadyScannedAssemblyNames.ContainsKey (aname.FullName))
336 ResolvedReference resolved_ref = ResolveDependencyByAssemblyName (
337 aname, asm.FullName, parent_copy_local);
339 if (resolved_ref != null && !IsFromGacOrTargetFramework (resolved_ref)
340 && resolved_ref.FoundInSearchPath != SearchPath.PkgConfig) {
341 tempResolvedDepFiles[resolved_ref.AssemblyName.FullName] = resolved_ref.TaskItem;
342 dependencies.Enqueue (resolved_ref.TaskItem.ItemSpec);
345 alreadyScannedAssemblyNames.Add (asm.FullName, String.Empty);
349 // Resolves by looking dependency_search_paths
350 // which is dir of parent reference file, and
352 ResolvedReference ResolveDependencyByAssemblyName (AssemblyName aname, string parent_asm_name,
353 string parent_copy_local)
355 // This will check for compatible assembly name/version
356 ResolvedReference resolved_ref;
357 if (TryGetResolvedReferenceByAssemblyName (aname, false, out resolved_ref))
360 LogWithPrecedingNewLine (MessageImportance.Low, "Dependency {0}", aname);
361 Log.LogMessage (MessageImportance.Low, "\tRequired by {0}", parent_asm_name);
363 ITaskItem item = new TaskItem (aname.FullName);
364 item.SetMetadata ("SpecificVersion", "false");
365 resolved_ref = ResolveReference (item, dependency_search_paths, false);
367 if (resolved_ref != null) {
368 if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null)
369 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
371 Log.LogMessage (MessageImportance.Low, "\tReference {0} resolved to {1}.",
372 aname, resolved_ref.TaskItem.ItemSpec);
374 Log.LogMessage (MessageImportance.Low,
375 "\tReference found at search path {0}",
376 resolved_ref.FoundInSearchPathAsString);
378 if (resolved_ref.FoundInSearchPath == SearchPath.Directory) {
379 // override CopyLocal with parent's val
380 SetCopyLocal (resolved_ref.TaskItem, parent_copy_local);
382 Log.LogMessage (MessageImportance.Low,
383 "\tThis is CopyLocal {0} as parent item has this value",
386 if (TryAddNewReference (tempResolvedFiles, resolved_ref)) {
387 FindAndAddRelatedFiles (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
388 FindAndAddSatellites (resolved_ref.TaskItem.ItemSpec, parent_copy_local);
392 Log.LogMessage (MessageImportance.Low,
393 "\tThis is CopyLocal false as it is in the gac," +
394 "target framework directory or provided by a package.");
396 TryAddNewReference (tempResolvedFiles, resolved_ref);
399 Log.LogWarning ("Reference '{0}' not resolved", aname);
400 assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal);
406 void FindAndAddRelatedFiles (string filename, string parent_copy_local)
408 if (!findRelatedFiles || allowedRelatedFileExtensions == null)
411 foreach (string ext in allowedRelatedFileExtensions) {
412 string rfile = filename + ext;
413 if (File.Exists (rfile)) {
414 ITaskItem item = new TaskItem (rfile);
415 SetCopyLocal (item, parent_copy_local);
417 tempRelatedFiles.AddUniqueFile (item);
422 void FindAndAddSatellites (string filename, string parent_copy_local)
427 string basepath = Path.GetDirectoryName (filename);
428 string resource = String.Format ("{0}{1}{2}",
429 Path.GetFileNameWithoutExtension (filename),
431 Path.GetExtension (filename));
433 string dir_sep = Path.DirectorySeparatorChar.ToString ();
434 foreach (string dir in Directory.GetDirectories (basepath)) {
435 string culture = Path.GetFileName (dir);
436 if (!CultureNamesTable.ContainsKey (culture))
439 string res_path = Path.Combine (dir, resource);
440 if (File.Exists (res_path)) {
441 ITaskItem item = new TaskItem (res_path);
442 SetCopyLocal (item, parent_copy_local);
443 item.SetMetadata ("DestinationSubdirectory", culture + dir_sep);
444 tempSatelliteFiles.AddUniqueFile (item);
449 // returns true is it was new
450 bool TryAddNewReference (List<ITaskItem> file_list, ResolvedReference key_ref)
452 ResolvedReference found_ref;
453 if (!TryGetResolvedReferenceByAssemblyName (key_ref.AssemblyName, key_ref.IsPrimary, out found_ref)) {
454 assemblyNameToResolvedRef [key_ref.AssemblyName.Name] = key_ref;
455 file_list.Add (key_ref.TaskItem);
462 void SetCopyLocal (ITaskItem item, string copy_local)
464 item.SetMetadata ("CopyLocal", copy_local);
466 // Assumed to be valid value
467 if (Boolean.Parse (copy_local))
468 tempCopyLocalFiles.AddUniqueFile (item);
471 bool TryGetResolvedReferenceByAssemblyName (AssemblyName key_aname, bool is_primary, out ResolvedReference found_ref)
474 // Match by just name
475 if (!assemblyNameToResolvedRef.TryGetValue (key_aname.Name, out found_ref))
479 // match for full name
480 if (AssemblyResolver.AssemblyNamesCompatible (key_aname, found_ref.AssemblyName, true, false))
481 // exact match, so its already there, dont add anything
484 // we have a name match, but version mismatch!
485 assembly_resolver.LogSearchMessage ("A conflict was detected between '{0}' and '{1}'",
486 key_aname.FullName, found_ref.AssemblyName.FullName);
488 if (is_primary == found_ref.IsPrimary) {
489 assembly_resolver.LogSearchMessage ("Unable to choose between the two. " +
490 "Choosing '{0}' arbitrarily.", found_ref.AssemblyName.FullName);
494 // since all dependencies are processed after
495 // all primary refererences, the one in the cache
496 // has to be a primary
497 // Prefer a primary reference over a dependency
499 assembly_resolver.LogSearchMessage ("Choosing '{0}' as it is a primary reference.",
500 found_ref.AssemblyName.FullName);
502 // If we can successfully use the primary reference, don't log a warning. It's too
504 //LogConflictWarning (found_ref.AssemblyName.FullName, key_aname.FullName);
509 void LogWithPrecedingNewLine (MessageImportance importance, string format, params object [] args)
511 Log.LogMessage (importance, String.Empty);
512 Log.LogMessage (importance, format, args);
515 // conflict b/w @main and @conflicting, picking @main
516 void LogConflictWarning (string main, string conflicting)
518 string key = main + ":" + conflicting;
519 if (!conflictWarningsCache.ContainsKey (key)) {
520 Log.LogWarning ("Found a conflict between : '{0}' and '{1}'. Using '{0}' reference.",
522 conflictWarningsCache [key] = key;
526 bool IsFromGacOrTargetFramework (ResolvedReference rr)
528 return rr.FoundInSearchPath == SearchPath.Gac ||
529 rr.FoundInSearchPath == SearchPath.TargetFrameworkDirectory;
532 void LogTaskParameters ()
534 Log.LogMessage (MessageImportance.Low, "TargetFrameworkDirectories:");
535 if (TargetFrameworkDirectories != null)
536 foreach (string dir in TargetFrameworkDirectories)
537 Log.LogMessage (MessageImportance.Low, "\t{0}", dir);
539 Log.LogMessage (MessageImportance.Low, "SearchPaths:");
540 if (SearchPaths != null)
541 foreach (string path in SearchPaths)
542 Log.LogMessage (MessageImportance.Low, "\t{0}", path);
545 public bool AutoUnify {
546 get { return autoUnify; }
547 set { autoUnify = value; }
550 public ITaskItem[] AssemblyFiles {
551 get { return assemblyFiles; }
552 set { assemblyFiles = value; }
555 public ITaskItem[] Assemblies {
556 get { return assemblies; }
557 set { assemblies = value; }
560 public string AppConfigFile {
561 get { return appConfigFile; }
562 set { appConfigFile = value; }
565 public string[] AllowedAssemblyExtensions {
566 get { return allowedAssemblyExtensions; }
567 set { allowedAssemblyExtensions = value; }
570 public string[] AllowedRelatedFileExtensions {
571 get { return allowedRelatedFileExtensions; }
572 set { allowedRelatedFileExtensions = value; }
575 public string[] CandidateAssemblyFiles {
576 get { return candidateAssemblyFiles; }
577 set { candidateAssemblyFiles = value; }
581 public ITaskItem[] CopyLocalFiles {
582 get { return copyLocalFiles; }
586 public ITaskItem[] FilesWritten {
587 get { return filesWritten; }
588 set { filesWritten = value; }
591 public bool FindDependencies {
592 get { return findDependencies; }
593 set { findDependencies = value; }
596 public bool FindRelatedFiles {
597 get { return findRelatedFiles; }
598 set { findRelatedFiles = value; }
601 public bool FindSatellites {
602 get { return findSatellites; }
603 set { findSatellites = value; }
606 public bool FindSerializationAssemblies {
607 get { return findSerializationAssemblies; }
608 set { findSerializationAssemblies = value; }
611 public string[] InstalledAssemblyTables {
612 get { return installedAssemblyTables; }
613 set { installedAssemblyTables = value; }
617 public ITaskItem[] RelatedFiles {
618 get { return relatedFiles; }
622 public ITaskItem[] ResolvedDependencyFiles {
623 get { return resolvedDependencyFiles; }
627 public ITaskItem[] ResolvedFiles {
628 get { return resolvedFiles; }
632 public ITaskItem[] SatelliteFiles {
633 get { return satelliteFiles; }
637 public ITaskItem[] ScatterFiles {
638 get { return scatterFiles; }
642 public string[] SearchPaths {
643 get { return searchPaths; }
644 set { searchPaths = value; }
648 public ITaskItem[] SerializationAssemblyFiles {
649 get { return serializationAssemblyFiles; }
653 get { return silent; }
654 set { silent = value; }
657 public string StateFile {
658 get { return stateFile; }
659 set { stateFile = value; }
663 public ITaskItem[] SuggestedRedirects {
664 get { return suggestedRedirects; }
668 public string TargetFrameworkMoniker { get; set; }
670 public string TargetFrameworkMonikerDisplayName { get; set; }
673 public string TargetFrameworkVersion { get; set; }
675 public string[] TargetFrameworkDirectories {
676 get { return targetFrameworkDirectories; }
677 set { targetFrameworkDirectories = value; }
680 public string TargetProcessorArchitecture {
681 get { return targetProcessorArchitecture; }
682 set { targetProcessorArchitecture = value; }
686 static Dictionary<string, string> cultureNamesTable;
687 static Dictionary<string, string> CultureNamesTable {
689 if (cultureNamesTable == null) {
690 cultureNamesTable = new Dictionary<string, string> ();
691 foreach (CultureInfo ci in CultureInfo.GetCultures (CultureTypes.AllCultures))
692 cultureNamesTable [ci.Name] = ci.Name;
695 return cultureNamesTable;
700 static class ResolveAssemblyReferenceHelper {
701 public static void AddUniqueFile (this Dictionary<string, ITaskItem> dic, ITaskItem item)
704 throw new ArgumentNullException ("dic");
706 throw new ArgumentNullException ("item");
708 string fullpath = Path.GetFullPath (item.ItemSpec);
709 if (!dic.ContainsKey (fullpath))
710 dic [fullpath] = item;
714 struct PrimaryReference {
715 public ITaskItem TaskItem;
716 public string ParentCopyLocal;
718 public PrimaryReference (ITaskItem item, string parent_copy_local)
721 ParentCopyLocal = parent_copy_local;