Cosmos/source/MPF/12.0/DesignTimeAssemblyResolution.cs
2017-04-03 23:55:10 -05:00

645 lines
28 KiB
C#

/********************************************************************************************
Copyright (c) Microsoft Corporation
All rights reserved.
Microsoft Public License:
This license governs use of the accompanying software. If you use the software, you
accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions
and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
royalty-free copyright license to reproduce its contribution, prepare derivative works of
its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions
and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
royalty-free license under its licensed patents to make, have made, use, sell, offer for
sale, import, and/or otherwise dispose of its contribution in the software or derivative
works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors'
name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are
infringed by the software, your patent license from such contributor to the software ends
automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent,
trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only
under this license by including a complete copy of this license with your distribution.
If you distribute any portion of the software in compiled or object code form, you may only
do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give
no express warranties, guarantees or conditions. You may have additional consumer rights
under your local laws which this license cannot change. To the extent permitted under your
local laws, the contributors exclude the implied warranties of merchantability, fitness for
a particular purpose and non-infringement.
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Build.Utilities;
using Microsoft.Build.Execution;
using System.IO;
using System.Globalization;
namespace Microsoft.VisualStudio.Project
{
public class DesignTimeAssemblyResolution
{
private const string OriginalItemSpec = "originalItemSpec";
private const string FoundAssemblyVersion = "Version";
private const string HighestVersionInRedistList = "HighestVersionInRedist";
private const string OutOfRangeDependencies = "OutOfRangeDependencies";
private RarInputs rarInputs;
public bool EnableLogging { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetFrameworkPaths")]
public virtual void Initialize(ProjectNode projectNode)
{
if (projectNode == null)
{
throw new ArgumentNullException("projectNode");
}
if (projectNode.CallMSBuild("GetFrameworkPaths") != MSBuildResult.Successful)
{
throw new InvalidOperationException("Build of GetFrameworkPaths failed.");
}
this.rarInputs = new RarInputs(projectNode.CurrentConfig);
}
public virtual VsResolvedAssemblyPath[] Resolve(IEnumerable<string> assemblies)
{
if (assemblies == null)
{
throw new ArgumentNullException("assemblies");
}
// Resolve references WITHOUT invoking MSBuild to avoid re-entrancy problems.
const bool projectDtar = true;
var rar = new Microsoft.Build.Tasks.ResolveAssemblyReference();
var engine = new MockEngine(EnableLogging);
rar.BuildEngine = engine;
// first set common properties/items then if projectDtar then set additional projectDtar properties
ITaskItem[] assemblyItems = assemblies.Select(assembly => new TaskItem(assembly)).ToArray();
rar.Assemblies = assemblyItems;
rar.SearchPaths = rarInputs.PdtarSearchPaths;
rar.TargetFrameworkDirectories = rarInputs.TargetFrameworkDirectories;
rar.AllowedAssemblyExtensions = rarInputs.AllowedAssemblyExtensions;
rar.TargetProcessorArchitecture = rarInputs.TargetProcessorArchitecture;
rar.TargetFrameworkVersion = rarInputs.TargetFrameworkVersion;
rar.TargetFrameworkMoniker = rarInputs.TargetFrameworkMoniker;
rar.TargetFrameworkMonikerDisplayName = rarInputs.TargetFrameworkMonikerDisplayName;
rar.TargetedRuntimeVersion = rarInputs.TargetedRuntimeVersion;
rar.FullFrameworkFolders = rarInputs.FullFrameworkFolders;
rar.LatestTargetFrameworkDirectories = rarInputs.LatestTargetFrameworkDirectories;
rar.FullTargetFrameworkSubsetNames = rarInputs.FullTargetFrameworkSubsetNames;
rar.FullFrameworkAssemblyTables = rarInputs.FullFrameworkAssemblyTables;
rar.IgnoreDefaultInstalledAssemblySubsetTables = rarInputs.IgnoreDefaultInstalledAssemblySubsetTables;
rar.ProfileName = rarInputs.ProfileName;
rar.Silent = !this.EnableLogging;
rar.FindDependencies = true;
rar.AutoUnify = false;
rar.FindSatellites = false;
rar.FindSerializationAssemblies = false;
rar.FindRelatedFiles = false;
// This set needs to be kept in sync with the set of project instance data that
// is populated into RarInputs
if (projectDtar)
{
// set project dtar specific properties
rar.CandidateAssemblyFiles = rarInputs.CandidateAssemblyFiles;
rar.StateFile = rarInputs.StateFile;
rar.InstalledAssemblySubsetTables = rarInputs.InstalledAssemblySubsetTables;
rar.TargetFrameworkSubsets = rarInputs.TargetFrameworkSubsets;
}
IEnumerable<VsResolvedAssemblyPath> results;
try
{
rar.Execute();
results = FilterResults(rar.ResolvedFiles).Select(pair => new VsResolvedAssemblyPath
{
bstrOrigAssemblySpec = pair.Key,
bstrResolvedAssemblyPath = pair.Value,
});
}
catch (Exception ex)
{
if (ErrorHandler.IsCriticalException(ex))
{
throw;
}
engine.RecordRARExecutionException(ex);
results = Enumerable.Empty<VsResolvedAssemblyPath>();
}
finally
{
if (this.EnableLogging)
{
WriteLogFile(engine, projectDtar, assemblies);
}
}
return results.ToArray();
}
private static IEnumerable<KeyValuePair<string, string>> FilterResults(IEnumerable<ITaskItem> resolvedFiles)
{
foreach (ITaskItem resolvedFile in resolvedFiles)
{
bool bAddResolvedAssemblyToResultList = true;
// excludeVersionWarningsFromResult
string foundAssemblyVersion = resolvedFile.GetMetadata(FoundAssemblyVersion);
string highestVersionInRedist = resolvedFile.GetMetadata(HighestVersionInRedistList);
Version asmVersion = null;
bool parsedAsmVersion = Version.TryParse(foundAssemblyVersion, out asmVersion);
Version redistVersion = null;
bool parsedRedistVersion = Version.TryParse(highestVersionInRedist, out redistVersion);
if ((parsedAsmVersion && parsedRedistVersion) && asmVersion > redistVersion)
{
// if the version of the assembly is greater than the highest version - for that assembly - found in
// the chained(possibly) redist lists; then the assembly does not belong to the target framework
bAddResolvedAssemblyToResultList = false;
}
// check outOfRangeDependencies
string outOfRangeDependencies = resolvedFile.GetMetadata(OutOfRangeDependencies);
if (!String.IsNullOrEmpty(outOfRangeDependencies))
{
// This metadata is a semi-colon delimited list of dependent assembly names which target
// a higher framework. If this metadata is NOT EMPTY then
// the current assembly does have dependencies which are greater than the current target framework
// so let's exclude this assembly
bAddResolvedAssemblyToResultList = false;
}
if (bAddResolvedAssemblyToResultList)
{
yield return new KeyValuePair<string, string>(resolvedFile.GetMetadata(OriginalItemSpec), resolvedFile.ItemSpec);
}
}
}
private static void WriteLogFile(MockEngine engine, bool projectDtar, IEnumerable<string> assemblies)
{
string logFilePrefix = projectDtar ? "P" : "G";
string logFilePath = Path.Combine(Path.GetTempPath(), logFilePrefix + @"Dtar" + (Guid.NewGuid()).ToString("N", CultureInfo.InvariantCulture) + ".log");
StringBuilder inputs = new StringBuilder();
Array.ForEach<string>(assemblies.ToArray(), assembly => { inputs.Append(assembly); inputs.Append(";"); inputs.Append("\n"); });
string logAssemblies = "Inputs: \n" + inputs.ToString() + "\n\n";
string finalLog = logAssemblies + engine.Log;
string[] finalLogLines = finalLog.Split(new char[] { '\n' });
File.WriteAllLines(logFilePath, finalLogLines);
}
/// <summary>
/// Engine required by RAR, primarily for collecting logs
/// </summary>
private class MockEngine : IBuildEngine
{
private int messages = 0;
private int warnings = 0;
private int errors = 0;
private StringBuilder log = new StringBuilder();
private readonly bool enableLog = false;
internal MockEngine(bool enableLog)
{
this.enableLog = enableLog;
}
public void RecordRARExecutionException(Exception ex)
{
if (!enableLog) return;
log.Append(String.Format(CultureInfo.InvariantCulture, "{0}", ex.ToString()));
}
public void LogErrorEvent(BuildErrorEventArgs eventArgs)
{
if (eventArgs == null)
{
throw new ArgumentNullException("eventArgs");
}
if (!enableLog) return;
if (eventArgs.File != null && eventArgs.File.Length > 0)
{
log.Append(String.Format(CultureInfo.InvariantCulture, "{0}({1},{2}): ", eventArgs.File, eventArgs.LineNumber, eventArgs.ColumnNumber));
}
log.Append("ERROR ");
log.Append(eventArgs.Code);
log.Append(": ");
++errors;
log.AppendLine(eventArgs.Message);
}
public void LogWarningEvent(BuildWarningEventArgs eventArgs)
{
if (eventArgs == null)
{
throw new ArgumentNullException("eventArgs");
}
if (!enableLog) return;
if (eventArgs.File != null && eventArgs.File.Length > 0)
{
log.Append(String.Format(CultureInfo.InvariantCulture, "{0}({1},{2}): ", eventArgs.File, eventArgs.LineNumber, eventArgs.ColumnNumber));
}
log.Append("WARNING ");
log.Append(eventArgs.Code);
log.Append(": ");
++warnings;
log.AppendLine(eventArgs.Message);
}
public void LogCustomEvent(CustomBuildEventArgs eventArgs)
{
if (eventArgs == null)
{
throw new ArgumentNullException("eventArgs");
}
if (!enableLog) return;
log.Append(eventArgs.Message);
log.Append("\n");
}
public void LogMessageEvent(BuildMessageEventArgs eventArgs)
{
if (eventArgs == null)
{
throw new ArgumentNullException("eventArgs");
}
log.Append(eventArgs.Message);
log.Append("\n");
++messages;
}
public bool ContinueOnError
{
get { return false; }
}
public string ProjectFileOfTaskNode
{
get { return String.Empty; }
}
public int LineNumberOfTaskNode
{
get { return 0; }
}
public int ColumnNumberOfTaskNode
{
get { return 0; }
}
internal string Log
{
get { return log.ToString(); }
}
public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Accesssor for RAR related properties in the projectInstance.
/// See ResolveAssemblyReferennce task msdn docs for member descriptions
/// </summary>
private class RarInputs
{
#region private fields
// RAR related property/item names etc
private const string TargetFrameworkDirectory = "TargetFrameworkDirectory";
private const string RegistrySearchPathFormat = "Registry:{0},{1},{2}{3}";
private const string FrameworkRegistryBase = "FrameworkRegistryBase";
private const string TargetFrameworkVersionName = "TargetFrameworkVersion";
private const string AssemblyFoldersSuffix = "AssemblyFoldersSuffix";
private const string AssemblyFoldersExConditions = "AssemblyFoldersExConditions";
private const string AllowedReferenceAssemblyFileExtensions = "AllowedReferenceAssemblyFileExtensions";
private const string ProcessorArchitecture = "ProcessorArchitecture";
private const string TargetFrameworkMonikerName = "TargetFrameworkMoniker";
private const string TargetFrameworkMonikerDisplayNameName = "TargetFrameworkMonikerDisplayName";
private const string TargetedRuntimeVersionName = "TargetedRuntimeVersion";
private const string FullFrameworkReferenceAssemblyPaths = "_FullFrameworkReferenceAssemblyPaths";
private const string TargetFrameworkProfile = "TargetFrameworkProfile";
private const string ProjectDesignTimeAssemblyResolutionSearchPaths = "ProjectDesignTimeAssemblyResolutionSearchPaths";
private const string Content = "Content";
private const string None = "None";
private const string RARResolvedReferencePath = "ReferencePath";
private const string IntermediateOutputPath = "IntermediateOutputPath";
private const string InstalledAssemblySubsetTablesName = "InstalledAssemblySubsetTables";
private const string IgnoreInstalledAssemblySubsetTables = "IgnoreInstalledAssemblySubsetTables";
private const string ReferenceInstalledAssemblySubsets = "_ReferenceInstalledAssemblySubsets";
private const string FullReferenceAssemblyNames = "FullReferenceAssemblyNames";
private const string LatestTargetFrameworkDirectoriesName = "LatestTargetFrameworkDirectories";
private const string FullFrameworkAssemblyTablesName = "FullFrameworkAssemblyTables";
private const string MSBuildProjectDirectory = "MSBuildProjectDirectory";
#endregion //private fields
public string[] TargetFrameworkDirectories { get; private set; }
public string[] AllowedAssemblyExtensions { get; private set; }
public string TargetProcessorArchitecture { get; private set; }
public string TargetFrameworkVersion { get; private set; }
public string TargetFrameworkMoniker { get; private set; }
public string TargetFrameworkMonikerDisplayName { get; private set; }
public string TargetedRuntimeVersion { get; private set; }
public string[] FullFrameworkFolders { get; private set; }
public string ProfileName { get; private set; }
public string[] PdtarSearchPaths { get; private set; }
public string[] CandidateAssemblyFiles { get; private set; }
public string StateFile { get; private set; }
public ITaskItem[] InstalledAssemblySubsetTables { get; private set; }
public bool IgnoreDefaultInstalledAssemblySubsetTables { get; private set; }
public string[] TargetFrameworkSubsets { get; private set; }
public string[] FullTargetFrameworkSubsetNames { get; private set; }
public ITaskItem[] FullFrameworkAssemblyTables { get; private set; }
public string[] LatestTargetFrameworkDirectories { get; private set; }
#region constructors
public RarInputs(ProjectInstance projectInstance)
{
// Run through all of the entries we want to extract from the project instance before we discard it to save memory
TargetFrameworkDirectories = GetTargetFrameworkDirectories(projectInstance);
AllowedAssemblyExtensions = GetAllowedAssemblyExtensions(projectInstance);
TargetProcessorArchitecture = GetTargetProcessorArchitecture(projectInstance);
TargetFrameworkVersion = GetTargetFrameworkVersion(projectInstance);
TargetFrameworkMoniker = GetTargetFrameworkMoniker(projectInstance);
TargetFrameworkMonikerDisplayName = GetTargetFrameworkMonikerDisplayName(projectInstance);
TargetedRuntimeVersion = GetTargetedRuntimeVersion(projectInstance);
FullFrameworkFolders = GetFullFrameworkFolders(projectInstance);
LatestTargetFrameworkDirectories = GetLatestTargetFrameworkDirectories(projectInstance);
FullTargetFrameworkSubsetNames = GetFullTargetFrameworkSubsetNames(projectInstance);
FullFrameworkAssemblyTables = GetFullFrameworkAssemblyTables(projectInstance);
IgnoreDefaultInstalledAssemblySubsetTables = GetIgnoreDefaultInstalledAssemblySubsetTables(projectInstance);
ProfileName = GetProfileName(projectInstance);
/*
* rar.CandidateAssemblyFiles = rarInputs.CandidateAssemblyFiles;
rar.StateFile = rarInputs.StateFile;
rar.InstalledAssemblySubsetTables = rarInputs.InstalledAssemblySubsetTables;
rar.TargetFrameworkSubsets = rarInputs.TargetFrameworkSubsets;
*/
// This set needs to be kept in sync with the set of project instance data that
// is passed into Rar
PdtarSearchPaths = GetPdtarSearchPaths(projectInstance);
CandidateAssemblyFiles = GetCandidateAssemblyFiles(projectInstance);
StateFile = GetStateFile(projectInstance);
InstalledAssemblySubsetTables = GetInstalledAssemblySubsetTables(projectInstance);
TargetFrameworkSubsets = GetTargetFrameworkSubsets(projectInstance);
}
#endregion // constructors
#region public properties
#region common properties/items
private string[] GetTargetFrameworkDirectories(ProjectInstance projectInstance)
{
if (TargetFrameworkDirectories == null)
{
string val = projectInstance.GetPropertyValue(TargetFrameworkDirectory).Trim();
TargetFrameworkDirectories = val.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => s.Length > 0)
.ToArray();
}
return TargetFrameworkDirectories;
}
private static string[] GetAllowedAssemblyExtensions(ProjectInstance projectInstance)
{
string[] allowedAssemblyExtensions;
string val = projectInstance.GetPropertyValue(AllowedReferenceAssemblyFileExtensions).Trim();
allowedAssemblyExtensions = val.Split(';').Select(s => s.Trim()).ToArray();
return allowedAssemblyExtensions;
}
private static string GetTargetProcessorArchitecture(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(ProcessorArchitecture).Trim();
return val;
}
private static string GetTargetFrameworkVersion(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(TargetFrameworkVersionName).Trim();
return val;
}
private static string GetTargetFrameworkMoniker(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(TargetFrameworkMonikerName).Trim();
return val;
}
private static string GetTargetFrameworkMonikerDisplayName(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(TargetFrameworkMonikerDisplayNameName).Trim();
return val;
}
private static string GetTargetedRuntimeVersion(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(TargetedRuntimeVersionName).Trim();
return val;
}
private static string[] GetFullFrameworkFolders(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(FullFrameworkReferenceAssemblyPaths).Trim();
string[] _fullFrameworkFolders = val.Split(';').Select(s => s.Trim()).ToArray();
return _fullFrameworkFolders;
}
private static string[] GetLatestTargetFrameworkDirectories(ProjectInstance projectInstance)
{
IEnumerable<ITaskItem> taskItems = projectInstance.GetItems(LatestTargetFrameworkDirectoriesName);
string[] latestTargetFrameworkDirectory = (taskItems.Select((Func<ITaskItem, string>)((item) => { return item.ItemSpec.Trim(); }))).ToArray();
return latestTargetFrameworkDirectory;
}
private static string GetProfileName(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(TargetFrameworkProfile).Trim();
return val;
}
#endregion //common properties/items
#region project dtar specific properties/items
private static string[] GetPdtarSearchPaths(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(ProjectDesignTimeAssemblyResolutionSearchPaths).Trim();
string[] _pdtarSearchPaths = val.Split(';').Select(s => s.Trim()).ToArray();
return _pdtarSearchPaths;
}
private static string[] GetCandidateAssemblyFiles(ProjectInstance projectInstance)
{
var candidateAssemblyFilesList = new List<ProjectItemInstance>();
candidateAssemblyFilesList.AddRange(projectInstance.GetItems(Content));
candidateAssemblyFilesList.AddRange(projectInstance.GetItems(None));
candidateAssemblyFilesList.AddRange(projectInstance.GetItems(RARResolvedReferencePath));
string[] candidateAssemblyFiles = candidateAssemblyFilesList.Select((Func<ProjectItemInstance, string>)((item) => { return item.GetMetadataValue("FullPath").Trim(); })).ToArray();
return candidateAssemblyFiles;
}
private static string GetStateFile(ProjectInstance projectInstance)
{
string intermediatePath = projectInstance.GetPropertyValue(IntermediateOutputPath).Trim();
intermediatePath = GetFullPathInProjectContext(projectInstance, intermediatePath);
string stateFile = Path.Combine(intermediatePath, "DesignTimeResolveAssemblyReferences.cache");
return stateFile;
}
private static ITaskItem[] GetInstalledAssemblySubsetTables(ProjectInstance projectInstance)
{
return projectInstance.GetItems(InstalledAssemblySubsetTablesName).ToArray();
}
private static bool GetIgnoreDefaultInstalledAssemblySubsetTables(ProjectInstance projectInstance)
{
bool ignoreDefaultInstalledAssemblySubsetTables = false;
string val = projectInstance.GetPropertyValue(IgnoreInstalledAssemblySubsetTables).Trim();
if (!String.IsNullOrEmpty(val))
{
if (val == Boolean.TrueString || val == Boolean.FalseString)
{
ignoreDefaultInstalledAssemblySubsetTables = Convert.ToBoolean(val, CultureInfo.InvariantCulture);
}
}
return ignoreDefaultInstalledAssemblySubsetTables;
}
private static string[] GetTargetFrameworkSubsets(ProjectInstance projectInstance)
{
IEnumerable<ITaskItem> taskItems = projectInstance.GetItems(ReferenceInstalledAssemblySubsets);
string[] targetFrameworkSubsets = (taskItems.Select((Func<ITaskItem, string>)((item) => { return item.ItemSpec.Trim(); }))).ToArray();
return targetFrameworkSubsets;
}
private static string[] GetFullTargetFrameworkSubsetNames(ProjectInstance projectInstance)
{
string val = projectInstance.GetPropertyValue(FullReferenceAssemblyNames).Trim();
string[] fullTargetFrameworkSubsetNames = val.Split(';').Select(s => s.Trim()).ToArray();
return fullTargetFrameworkSubsetNames;
}
private static ITaskItem[] GetFullFrameworkAssemblyTables(ProjectInstance projectInstance)
{
return projectInstance.GetItems(FullFrameworkAssemblyTablesName).ToArray();
}
#endregion //project dtar specific properties/items
#endregion // public properties
#region private methods
static string GetFullPathInProjectContext(ProjectInstance projectInstance, string path)
{
string fullPath = path;
if (!Path.IsPathRooted(path))
{
string projectDir = projectInstance.GetPropertyValue(MSBuildProjectDirectory).Trim();
fullPath = Path.Combine(projectDir, path);
fullPath = Path.GetFullPath(fullPath);
}
return fullPath;
}
#endregion // private methods
}
}
}