//
// 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.Globalization;
using System.IO;
+using System.Linq;
+using System.Reflection;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
ITaskItem[] assemblies;
string appConfigFile;
string[] allowedAssemblyExtensions;
+ string[] allowedRelatedFileExtensions;
string[] candidateAssemblyFiles;
ITaskItem[] copyLocalFiles;
ITaskItem[] filesWritten;
ITaskItem[] suggestedRedirects;
string[] targetFrameworkDirectories;
string targetProcessorArchitecture;
+ static string [] default_assembly_extensions;
+
+ AssemblyResolver assembly_resolver;
+ List<string> dependency_search_paths;
+ Dictionary<string, ResolvedReference> assemblyNameToResolvedRef;
+ Dictionary<string, ITaskItem> tempSatelliteFiles, tempRelatedFiles,
+ tempResolvedDepFiles, tempCopyLocalFiles;
+ List<ITaskItem> tempResolvedFiles;
+ List<PrimaryReference> primaryReferences;
+ Dictionary<string, string> alreadyScannedAssemblyNames;
+ Dictionary<string, string> 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 <reference>.dll or uses
- // HintPath if it's there
-
+ //FIXME: make this reusable
public override bool Execute ()
{
- List <ITaskItem> tempResolvedFiles = new List <ITaskItem> ();
-
+ if (assemblies == null && assemblyFiles == null)
+ // nothing to resolve
+ return true;
+
+ LogTaskParameters ();
+
+ assembly_resolver.Log = Log;
+ tempResolvedFiles = new List<ITaskItem> ();
+ tempCopyLocalFiles = new Dictionary<string, ITaskItem> ();
+ tempSatelliteFiles = new Dictionary<string, ITaskItem> ();
+ tempRelatedFiles = new Dictionary<string, ITaskItem> ();
+ tempResolvedDepFiles = new Dictionary<string, ITaskItem> ();
+
+ primaryReferences = new List<PrimaryReference> ();
+ assemblyNameToResolvedRef = new Dictionary<string, ResolvedReference> ();
+ conflictWarningsCache = new Dictionary<string, string> ();
+
+ ResolveAssemblies ();
+ ResolveAssemblyFiles ();
+ resolvedFiles = tempResolvedFiles.ToArray ();
+
+ alreadyScannedAssemblyNames = new Dictionary<string, string> ();
+
+ // the first element is place holder for parent assembly's dir
+ dependency_search_paths = new List<string> () { String.Empty };
+ dependency_search_paths.AddRange (searchPaths);
+
+ // resolve dependencies
+ foreach (PrimaryReference pref in primaryReferences)
+ ResolveAssemblyFileDependencies (pref.TaskItem, pref.ParentCopyLocal);
+
+ 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 {
+ 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);
+
+ assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
+
+ 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<string> 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;
+ }
+
+ 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<string> dependencies = new Queue<string> ();
+ 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 (IncludeDependencies (resolved_ref, aname.FullName)) {
+ tempResolvedDepFiles[resolved_ref.AssemblyName.FullName] = resolved_ref.TaskItem;
+ 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) {
+ 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);
+
+ assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
+
+ 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.LogMessage (MessageImportance.Low, "Could not resolve the assembly \"{0}\".", aname);
+ assembly_resolver.LogSearchLoggerMessages (MessageImportance.Low);
+ }
+
+ return resolved_ref;
+ }
+
+ void FindAndAddRelatedFiles (string filename, string parent_copy_local)
+ {
+ if (!findRelatedFiles || allowedRelatedFileExtensions == null)
+ return;
+
+ foreach (string ext in allowedRelatedFileExtensions) {
+ string rfile = Path.ChangeExtension (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<ITaskItem> 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);
+
+ // If we can successfully use the primary reference, don't log a warning. It's too
+ // verbose.
+ //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;
+ }
+
+ bool IncludeDependencies (ResolvedReference rr, string aname)
+ {
+ if (rr == null)
+ return false;
+ if (aname.Equals ("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"))
+ return true;
+ return !IsFromGacOrTargetFramework (rr) && rr.FoundInSearchPath != SearchPath.PkgConfig;
+ }
+
+ void LogTaskParameters ()
+ {
+ Log.LogMessage (MessageImportance.Low, "TargetFrameworkDirectories:");
+ if (TargetFrameworkDirectories != null)
+ foreach (string dir in TargetFrameworkDirectories)
+ Log.LogMessage (MessageImportance.Low, "\t{0}", dir);
+
+ Log.LogMessage (MessageImportance.Low, "SearchPaths:");
+ if (SearchPaths != null)
+ foreach (string path in SearchPaths)
+ Log.LogMessage (MessageImportance.Low, "\t{0}", path);
+ }
+
public bool AutoUnify {
get { return autoUnify; }
set { autoUnify = value; }
get { return allowedAssemblyExtensions; }
set { allowedAssemblyExtensions = value; }
}
+
+ public string[] AllowedRelatedFileExtensions {
+ get { return allowedRelatedFileExtensions; }
+ set { allowedRelatedFileExtensions = value; }
+ }
public string[] CandidateAssemblyFiles {
get { return candidateAssemblyFiles; }
public ITaskItem[] SuggestedRedirects {
get { return suggestedRedirects; }
}
-
+
+#if NET_4_0
+ public string TargetFrameworkMoniker { get; set; }
+
+ public string TargetFrameworkMonikerDisplayName { get; set; }
+#endif
+
+ public string TargetFrameworkVersion { get; set; }
+
public string[] TargetFrameworkDirectories {
get { return targetFrameworkDirectories; }
set { targetFrameworkDirectories = value; }
get { return targetProcessorArchitecture; }
set { targetProcessorArchitecture = value; }
}
+
+
+ static Dictionary<string, string> cultureNamesTable;
+ static Dictionary<string, string> CultureNamesTable {
+ get {
+ if (cultureNamesTable == null) {
+ cultureNamesTable = new Dictionary<string, string> ();
+ foreach (CultureInfo ci in CultureInfo.GetCultures (CultureTypes.AllCultures))
+ cultureNamesTable [ci.Name] = ci.Name;
+ }
+
+ return cultureNamesTable;
+ }
+ }
}
+
+ static class ResolveAssemblyReferenceHelper {
+ public static void AddUniqueFile (this Dictionary<string, ITaskItem> 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