X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMicrosoft.Build.Tasks%2FMicrosoft.Build.Tasks%2FResolveAssemblyReference.cs;h=f1d20ab11f997140fffd714a06db897f42d67c26;hb=6bdfc2f092d0a27f1b33f2d1cbe27a2955a4e970;hp=c8eb51dc3cb0f904c2dddc2f44b12c8c8f36af93;hpb=af90548a08ef5effc93b083b7eec44daa178b141;p=mono.git diff --git a/mcs/class/Microsoft.Build.Tasks/Microsoft.Build.Tasks/ResolveAssemblyReference.cs b/mcs/class/Microsoft.Build.Tasks/Microsoft.Build.Tasks/ResolveAssemblyReference.cs index c8eb51dc3cb..f1d20ab11f9 100644 --- a/mcs/class/Microsoft.Build.Tasks/Microsoft.Build.Tasks/ResolveAssemblyReference.cs +++ b/mcs/class/Microsoft.Build.Tasks/Microsoft.Build.Tasks/ResolveAssemblyReference.cs @@ -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 @@ -29,7 +31,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; +using System.Reflection; using System.Security; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -42,6 +47,7 @@ namespace Microsoft.Build.Tasks { ITaskItem[] assemblies; string appConfigFile; string[] allowedAssemblyExtensions; + string[] allowedRelatedFileExtensions; string[] candidateAssemblyFiles; ITaskItem[] copyLocalFiles; ITaskItem[] filesWritten; @@ -62,30 +68,461 @@ namespace Microsoft.Build.Tasks { ITaskItem[] suggestedRedirects; string[] targetFrameworkDirectories; string targetProcessorArchitecture; + static string [] default_assembly_extensions; + + AssemblyResolver assembly_resolver; + List dependency_search_paths; + Dictionary assemblyNameToResolvedRef; + Dictionary tempSatelliteFiles, tempRelatedFiles, + tempResolvedDepFiles, tempCopyLocalFiles; + List tempResolvedFiles; + List primaryReferences; + Dictionary alreadyScannedAssemblyNames; + Dictionary conflictWarningsCache; + + //FIXME: construct and use a graph of the dependencies, useful across projects + + static ResolveAssemblyReference () + { + default_assembly_extensions = new string [] { ".dll", ".exe" }; + } public ResolveAssemblyReference () { + assembly_resolver = new AssemblyResolver (); } - // FIXME: very primitive version that references .dll or uses - // HintPath if it's there - + //FIXME: make this reusable public override bool Execute () { - List tempResolvedFiles = new List (); - + if (assemblies == null && assemblyFiles == null) + // nothing to resolve + return true; + + assembly_resolver.Log = Log; + tempResolvedFiles = new List (); + tempCopyLocalFiles = new Dictionary (); + tempSatelliteFiles = new Dictionary (); + tempRelatedFiles = new Dictionary (); + tempResolvedDepFiles = new Dictionary (); + + primaryReferences = new List (); + assemblyNameToResolvedRef = new Dictionary (); + conflictWarningsCache = new Dictionary (); + + ResolveAssemblies (); + ResolveAssemblyFiles (); + + alreadyScannedAssemblyNames = new Dictionary (); + + // the first element is place holder for parent assembly's dir + dependency_search_paths = new List () { String.Empty }; + dependency_search_paths.AddRange (searchPaths); + + // resolve dependencies + foreach (PrimaryReference pref in primaryReferences) + ResolveAssemblyFileDependencies (pref.TaskItem, pref.ParentCopyLocal); + + resolvedFiles = tempResolvedFiles.ToArray (); + copyLocalFiles = tempCopyLocalFiles.Values.ToArray (); + satelliteFiles = tempSatelliteFiles.Values.ToArray (); + relatedFiles = tempRelatedFiles.Values.ToArray (); + resolvedDependencyFiles = tempResolvedDepFiles.Values.ToArray (); + + tempResolvedFiles.Clear (); + tempCopyLocalFiles.Clear (); + tempSatelliteFiles.Clear (); + tempRelatedFiles.Clear (); + tempResolvedDepFiles.Clear (); + alreadyScannedAssemblyNames.Clear (); + primaryReferences.Clear (); + assemblyNameToResolvedRef.Clear (); + conflictWarningsCache.Clear (); + dependency_search_paths = null; + + return true; + } + + void ResolveAssemblies () + { + if (assemblies == null || assemblies.Length == 0) + return; + foreach (ITaskItem item in assemblies) { - if (item.GetMetadata ("HintPath") != String.Empty) - tempResolvedFiles.Add (new TaskItem (item.GetMetadata ("HintPath"))); - else - tempResolvedFiles.Add (new TaskItem (String.Format ("{0}.dll",item.ItemSpec))); + if (!String.IsNullOrEmpty (item.GetMetadata ("SubType"))) { + Log.LogWarning ("Reference '{0}' has non-empty SubType. Ignoring.", item.ItemSpec); + continue; + } + + LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference {0}", item.ItemSpec); + ResolvedReference resolved_ref = ResolveReference (item, searchPaths, true); + if (resolved_ref == null) { + Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec); + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal); + } else { + if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null) + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low); + + Log.LogMessage (MessageImportance.Low, + "\tReference {0} resolved to {1}. CopyLocal = {2}", + item.ItemSpec, resolved_ref.TaskItem, + resolved_ref.TaskItem.GetMetadata ("CopyLocal")); + + Log.LogMessage (MessageImportance.Low, + "\tReference found at search path {0}", + resolved_ref.FoundInSearchPathAsString); + + if (TryAddNewReference (tempResolvedFiles, resolved_ref) && + !IsFromGacOrTargetFramework (resolved_ref) && + resolved_ref.FoundInSearchPath != SearchPath.PkgConfig) { + primaryReferences.Add (new PrimaryReference ( + resolved_ref.TaskItem, + resolved_ref.TaskItem.GetMetadata ("CopyLocal"))); + } + } } - - resolvedFiles = tempResolvedFiles.ToArray (); - + } + + // Use @search_paths to resolve the reference + ResolvedReference ResolveReference (ITaskItem item, IEnumerable search_paths, bool set_copy_local) + { + ResolvedReference resolved = null; + bool specific_version; + + assembly_resolver.ResetSearchLogger (); + + if (!TryGetSpecificVersionValue (item, out specific_version)) + return null; + + foreach (string spath in search_paths) { + assembly_resolver.LogSearchMessage ("For searchpath {0}", spath); + + if (String.Compare (spath, "{HintPathFromItem}") == 0) { + resolved = assembly_resolver.ResolveHintPathReference (item, specific_version); + } else if (String.Compare (spath, "{TargetFrameworkDirectory}") == 0) { + if (targetFrameworkDirectories == null) + continue; + foreach (string fpath in targetFrameworkDirectories) { + resolved = assembly_resolver.FindInTargetFramework (item, + fpath, specific_version); + if (resolved != null) + break; + } + } else if (String.Compare (spath, "{GAC}") == 0) { + resolved = assembly_resolver.ResolveGacReference (item, specific_version); + } else if (String.Compare (spath, "{RawFileName}") == 0) { + //FIXME: identify assembly names, as extract the name, and try with that? + AssemblyName aname; + if (assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname)) + resolved = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true, + SearchPath.RawFileName); + } else if (String.Compare (spath, "{CandidateAssemblyFiles}") == 0) { + assembly_resolver.LogSearchMessage ( + "Warning: {{CandidateAssemblyFiles}} not supported currently"); + } else if (String.Compare (spath, "{PkgConfig}") == 0) { + resolved = assembly_resolver.ResolvePkgConfigReference (item, specific_version); + } else { + resolved = assembly_resolver.FindInDirectory ( + item, spath, + allowedAssemblyExtensions ?? default_assembly_extensions, + specific_version); + } + + if (resolved != null) + break; + } + + if (resolved != null && set_copy_local) + SetCopyLocal (resolved.TaskItem, resolved.CopyLocal.ToString ()); + + return resolved; + } + + bool TryGetSpecificVersionValue (ITaskItem item, out bool specific_version) + { + specific_version = true; + string value = item.GetMetadata ("SpecificVersion"); + if (String.IsNullOrEmpty (value)) { + //AssemblyName name = new AssemblyName (item.ItemSpec); + // If SpecificVersion is not specified, then + // it is true if the Include is a strong name else false + //specific_version = assembly_resolver.IsStrongNamed (name); + + // msbuild seems to just look for a ',' in the name :/ + specific_version = item.ItemSpec.IndexOf (',') >= 0; + return true; + } + + if (Boolean.TryParse (value, out specific_version)) + return true; + + Log.LogError ("Item '{0}' has attribute SpecificVersion with invalid value '{1}' " + + "which could not be converted to a boolean.", item.ItemSpec, value); + return false; + } + + //FIXME: Consider CandidateAssemblyFiles also here + void ResolveAssemblyFiles () + { + if (assemblyFiles == null) + return; + + foreach (ITaskItem item in assemblyFiles) { + assembly_resolver.ResetSearchLogger (); + + if (!File.Exists (item.ItemSpec)) { + LogWithPrecedingNewLine (MessageImportance.Low, + "Primary Reference from AssemblyFiles {0}, file not found. Ignoring", + item.ItemSpec); + continue; + } + + LogWithPrecedingNewLine (MessageImportance.Low, "Primary Reference from AssemblyFiles {0}", item.ItemSpec); + string copy_local; + + AssemblyName aname; + if (!assembly_resolver.TryGetAssemblyNameFromFile (item.ItemSpec, out aname)) { + Log.LogWarning ("Reference '{0}' not resolved", item.ItemSpec); + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal); + continue; + } + + if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null) + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low); + + ResolvedReference rr = assembly_resolver.GetResolvedReference (item, item.ItemSpec, aname, true, + SearchPath.RawFileName); + copy_local = rr.CopyLocal.ToString (); + + if (!TryAddNewReference (tempResolvedFiles, rr)) + // already resolved + continue; + + SetCopyLocal (rr.TaskItem, copy_local); + + FindAndAddRelatedFiles (item.ItemSpec, copy_local); + FindAndAddSatellites (item.ItemSpec, copy_local); + + if (FindDependencies && !IsFromGacOrTargetFramework (rr) && + rr.FoundInSearchPath != SearchPath.PkgConfig) + primaryReferences.Add (new PrimaryReference (item, copy_local)); + } + } + + // Tries to resolve assemblies referenced by @item + // Skips gac references + // @item : filename + void ResolveAssemblyFileDependencies (ITaskItem item, string parent_copy_local) + { + Queue dependencies = new Queue (); + dependencies.Enqueue (item.ItemSpec); + + while (dependencies.Count > 0) { + string filename = Path.GetFullPath (dependencies.Dequeue ()); + Assembly asm = Assembly.ReflectionOnlyLoadFrom (filename); + if (alreadyScannedAssemblyNames.ContainsKey (asm.FullName)) + continue; + + // set the 1st search path to this ref's base path + // Will be used for resolving the dependencies + dependency_search_paths [0] = Path.GetDirectoryName (filename); + + foreach (AssemblyName aname in asm.GetReferencedAssemblies ()) { + if (alreadyScannedAssemblyNames.ContainsKey (aname.FullName)) + continue; + + ResolvedReference resolved_ref = ResolveDependencyByAssemblyName ( + aname, asm.FullName, parent_copy_local); + + if (resolved_ref != null && !IsFromGacOrTargetFramework (resolved_ref) + && resolved_ref.FoundInSearchPath != SearchPath.PkgConfig) + dependencies.Enqueue (resolved_ref.TaskItem.ItemSpec); + } + alreadyScannedAssemblyNames.Add (asm.FullName, String.Empty); + } + } + + // Resolves by looking dependency_search_paths + // which is dir of parent reference file, and + // SearchPaths + ResolvedReference ResolveDependencyByAssemblyName (AssemblyName aname, string parent_asm_name, + string parent_copy_local) + { + // This will check for compatible assembly name/version + ResolvedReference resolved_ref; + if (TryGetResolvedReferenceByAssemblyName (aname, false, out resolved_ref)) + return resolved_ref; + + LogWithPrecedingNewLine (MessageImportance.Low, "Dependency {0}", aname); + Log.LogMessage (MessageImportance.Low, "\tRequired by {0}", parent_asm_name); + + ITaskItem item = new TaskItem (aname.FullName); + item.SetMetadata ("SpecificVersion", "false"); + resolved_ref = ResolveReference (item, dependency_search_paths, false); + + if (resolved_ref != null) { + if (Environment.GetEnvironmentVariable ("XBUILD_LOG_REFERENCE_RESOLVER") != null) + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low); + + Log.LogMessage (MessageImportance.Low, "\tReference {0} resolved to {1}.", + aname, resolved_ref.TaskItem.ItemSpec); + + Log.LogMessage (MessageImportance.Low, + "\tReference found at search path {0}", + resolved_ref.FoundInSearchPathAsString); + + if (resolved_ref.FoundInSearchPath == SearchPath.Directory) { + // override CopyLocal with parent's val + SetCopyLocal (resolved_ref.TaskItem, parent_copy_local); + + Log.LogMessage (MessageImportance.Low, + "\tThis is CopyLocal {0} as parent item has this value", + parent_copy_local); + + if (TryAddNewReference (tempResolvedFiles, resolved_ref)) { + FindAndAddRelatedFiles (resolved_ref.TaskItem.ItemSpec, parent_copy_local); + FindAndAddSatellites (resolved_ref.TaskItem.ItemSpec, parent_copy_local); + } + } else { + //gac or tgtfmwk + Log.LogMessage (MessageImportance.Low, + "\tThis is CopyLocal false as it is in the gac," + + "target framework directory or provided by a package."); + + TryAddNewReference (tempResolvedFiles, resolved_ref); + } + } else { + Log.LogWarning ("Reference '{0}' not resolved", aname); + assembly_resolver.LogSearchLoggerMessages (MessageImportance.Normal); + } + + return resolved_ref; + } + + void FindAndAddRelatedFiles (string filename, string parent_copy_local) + { + if (!findRelatedFiles || allowedRelatedFileExtensions == null) + return; + + foreach (string ext in allowedRelatedFileExtensions) { + string rfile = filename + ext; + if (File.Exists (rfile)) { + ITaskItem item = new TaskItem (rfile); + SetCopyLocal (item, parent_copy_local); + + tempRelatedFiles.AddUniqueFile (item); + } + } + } + + void FindAndAddSatellites (string filename, string parent_copy_local) + { + if (!FindSatellites) + return; + + string basepath = Path.GetDirectoryName (filename); + string resource = String.Format ("{0}{1}{2}", + Path.GetFileNameWithoutExtension (filename), + ".resources", + Path.GetExtension (filename)); + + string dir_sep = Path.DirectorySeparatorChar.ToString (); + foreach (string dir in Directory.GetDirectories (basepath)) { + string culture = Path.GetFileName (dir); + if (!CultureNamesTable.ContainsKey (culture)) + continue; + + string res_path = Path.Combine (dir, resource); + if (File.Exists (res_path)) { + ITaskItem item = new TaskItem (res_path); + SetCopyLocal (item, parent_copy_local); + item.SetMetadata ("DestinationSubdirectory", culture + dir_sep); + tempSatelliteFiles.AddUniqueFile (item); + } + } + } + + // returns true is it was new + bool TryAddNewReference (List file_list, ResolvedReference key_ref) + { + ResolvedReference found_ref; + if (!TryGetResolvedReferenceByAssemblyName (key_ref.AssemblyName, key_ref.IsPrimary, out found_ref)) { + assemblyNameToResolvedRef [key_ref.AssemblyName.Name] = key_ref; + file_list.Add (key_ref.TaskItem); + + return true; + } + return false; + } + + void SetCopyLocal (ITaskItem item, string copy_local) + { + item.SetMetadata ("CopyLocal", copy_local); + + // Assumed to be valid value + if (Boolean.Parse (copy_local)) + tempCopyLocalFiles.AddUniqueFile (item); + } + + bool TryGetResolvedReferenceByAssemblyName (AssemblyName key_aname, bool is_primary, out ResolvedReference found_ref) + { + found_ref = null; + // Match by just name + if (!assemblyNameToResolvedRef.TryGetValue (key_aname.Name, out found_ref)) + // not there + return false; + + // match for full name + if (AssemblyResolver.AssemblyNamesCompatible (key_aname, found_ref.AssemblyName, true, false)) + // exact match, so its already there, dont add anything + return true; + + // we have a name match, but version mismatch! + assembly_resolver.LogSearchMessage ("A conflict was detected between '{0}' and '{1}'", + key_aname.FullName, found_ref.AssemblyName.FullName); + + if (is_primary == found_ref.IsPrimary) { + assembly_resolver.LogSearchMessage ("Unable to choose between the two. " + + "Choosing '{0}' arbitrarily.", found_ref.AssemblyName.FullName); + return true; + } + + // since all dependencies are processed after + // all primary refererences, the one in the cache + // has to be a primary + // Prefer a primary reference over a dependency + + assembly_resolver.LogSearchMessage ("Choosing '{0}' as it is a primary reference.", + found_ref.AssemblyName.FullName); + + LogConflictWarning (found_ref.AssemblyName.FullName, key_aname.FullName); + return true; } - + + void LogWithPrecedingNewLine (MessageImportance importance, string format, params object [] args) + { + Log.LogMessage (importance, String.Empty); + Log.LogMessage (importance, format, args); + } + + // conflict b/w @main and @conflicting, picking @main + void LogConflictWarning (string main, string conflicting) + { + string key = main + ":" + conflicting; + if (!conflictWarningsCache.ContainsKey (key)) { + Log.LogWarning ("Found a conflict between : '{0}' and '{1}'. Using '{0}' reference.", + main, conflicting); + conflictWarningsCache [key] = key; + } + } + + bool IsFromGacOrTargetFramework (ResolvedReference rr) + { + return rr.FoundInSearchPath == SearchPath.Gac || + rr.FoundInSearchPath == SearchPath.TargetFrameworkDirectory; + } + public bool AutoUnify { get { return autoUnify; } set { autoUnify = value; } @@ -110,6 +547,11 @@ namespace Microsoft.Build.Tasks { get { return allowedAssemblyExtensions; } set { allowedAssemblyExtensions = value; } } + + public string[] AllowedRelatedFileExtensions { + get { return allowedRelatedFileExtensions; } + set { allowedRelatedFileExtensions = value; } + } public string[] CandidateAssemblyFiles { get { return candidateAssemblyFiles; } @@ -212,7 +654,46 @@ namespace Microsoft.Build.Tasks { get { return targetProcessorArchitecture; } set { targetProcessorArchitecture = value; } } + + static Dictionary cultureNamesTable; + static Dictionary CultureNamesTable { + get { + if (cultureNamesTable == null) { + cultureNamesTable = new Dictionary (); + foreach (CultureInfo ci in CultureInfo.GetCultures (CultureTypes.AllCultures)) + cultureNamesTable [ci.Name] = ci.Name; + } + + return cultureNamesTable; + } + } } + + static class ResolveAssemblyReferenceHelper { + public static void AddUniqueFile (this Dictionary dic, ITaskItem item) + { + if (dic == null) + throw new ArgumentNullException ("dic"); + if (item == null) + throw new ArgumentNullException ("item"); + + string fullpath = Path.GetFullPath (item.ItemSpec); + if (!dic.ContainsKey (fullpath)) + dic [fullpath] = item; + } + } + + struct PrimaryReference { + public ITaskItem TaskItem; + public string ParentCopyLocal; + + public PrimaryReference (ITaskItem item, string parent_copy_local) + { + TaskItem = item; + ParentCopyLocal = parent_copy_local; + } + } + } -#endif \ No newline at end of file +#endif