anx.framework/Visual Studio/MPF11/Dev11/Src/CSharp/AssemblyReferenceNode.cs
Konstantin Koch a8588a30a5 Update ContentCompiler, make Visual Studio Extension work without having Anx Framework installed.
Make ContentCompilerGui compatible to recent changes in pipeline and did
some usability changes.
Make the Visual Studio Extension work even if the ANX Framework is not
installed additionally..
Improve that the path for assembly refernces in a content project
doesn't get automatically updated, only if the reference is actually
saved, this is so you can specify a relative path yourself.
Fix missing icon for ContentProject when it was opened with Visual
Studio.
Made create_shaders.bat directly executable under windows by fixing the
directory separators.
2015-09-03 23:43:55 +02:00

583 lines
21 KiB
C#

/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Build.Utilities;
using System.Runtime.Versioning;
using System.Collections.Generic;
namespace Microsoft.VisualStudio.Project
{
public class AssemblyReferenceNode : ReferenceNode
{
#region fieds
/// <summary>
/// The name of the assembly this refernce represents
/// </summary>
private System.Reflection.AssemblyName assemblyName;
private AssemblyName resolvedAssemblyName;
private string fallbackName;
private string assemblyPath = String.Empty;
/// <summary>
/// Defines the listener that would listen on file changes on the nested project node.
/// </summary>
private FileChangeManager fileChangeListener;
/// <summary>
/// A flag for specifying if the object was disposed.
/// </summary>
private bool isDisposed;
#endregion
#region properties
/// <summary>
/// The name of the assembly this reference represents.
/// </summary>
/// <value></value>
public System.Reflection.AssemblyName AssemblyName
{
get
{
return this.assemblyName;
}
}
/// <summary>
/// Returns the name of the assembly this reference refers to on this specific
/// machine. It can be different from the AssemblyName property because it can
/// be more specific.
/// </summary>
public System.Reflection.AssemblyName ResolvedAssembly
{
get { return resolvedAssemblyName; }
}
public override string Url
{
get
{
return this.assemblyPath;
}
}
public override string Caption
{
get
{
if (this.assemblyName == null)
return this.fallbackName;
return this.assemblyName.Name;
}
}
private Automation.OAAssemblyReference assemblyRef;
public override object Object
{
get
{
if (null == assemblyRef)
{
assemblyRef = new Automation.OAAssemblyReference(this);
}
return assemblyRef;
}
}
#endregion
#region ctors
/// <summary>
/// Constructor for the ReferenceNode
/// </summary>
public AssemblyReferenceNode(ProjectNode root, ProjectElement element)
: base(root, element)
{
this.GetPathNameFromProjectFile();
this.InitializeFileChangeEvents();
if (File.Exists(assemblyPath))
{
this.fileChangeListener.ObserveItem(this.assemblyPath);
}
string include = this.ItemNode.GetMetadata(ProjectFileConstants.Include);
this.CreateFromAssemblyName(new System.Reflection.AssemblyName(include));
}
/// <summary>
/// Constructor for the AssemblyReferenceNode
/// </summary>
public AssemblyReferenceNode(ProjectNode root, string name, string assemblyPath)
: base(root)
{
// Validate the input parameters.
if (null == root)
{
throw new ArgumentNullException("root");
}
fallbackName = name;
this.InitializeFileChangeEvents();
assemblyPath = ResolveAssemblyPath(assemblyPath);
// The assemblyPath variable can be an actual path on disk or a generic assembly name.
if (File.Exists(assemblyPath))
{
this.assemblyPath = assemblyPath;
// The assemblyPath parameter is an actual file on disk; try to load it.
this.assemblyName = System.Reflection.AssemblyName.GetAssemblyName(assemblyPath);
// We register with listeningto chnages onteh path here. The rest of teh cases will call into resolving the assembly and registration is done there.
this.fileChangeListener.ObserveItem(this.assemblyPath);
}
else if (!string.IsNullOrEmpty(assemblyPath))
{
// The file does not exist on disk. This can be because the file / path is not
// correct or because this is not a path, but an assembly name.
// Try to resolve the reference as an assembly name.
try
{
this.CreateFromAssemblyName(new System.Reflection.AssemblyName(assemblyPath));
}
catch (Exception exc)
{
this.assemblyPath = assemblyPath;
Console.WriteLine(exc.Message);
}
}
}
#endregion
#region methods
/// <summary>
/// Closes the node.
/// </summary>
/// <returns></returns>
public override void Close()
{
try
{
this.Dispose(true);
}
finally
{
base.Close();
}
}
protected virtual string ResolveAssemblyPath(string assemblyPath)
{
return assemblyPath;
}
/// <summary>
/// Links a reference node to the project and hierarchy.
/// </summary>
protected override void BindReferenceData()
{
Debug.Assert(this.assemblyName != null, "The AssemblyName field has not been initialized");
// If the item has not been set correctly like in case of a new reference added it now.
// The constructor for the AssemblyReference node will create a default project item. In that case the Item is null.
// We need to specify here the correct project element.
if (this.ItemNode == null || this.ItemNode is VirtualProjectElement)
{
this.ItemNode = new MsBuildProjectElement(this.ProjectMgr, this.assemblyName.FullName, ProjectFileConstants.Reference);
}
// Set the basic information we know about
this.ItemNode.SetMetadata(ProjectFileConstants.Name, this.assemblyName.Name);
this.ItemNode.SetMetadata(ProjectFileConstants.AssemblyName, Path.GetFileName(this.assemblyPath));
this.SetReferenceProperties();
}
/// <summary>
/// Disposes the node
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
try
{
this.UnregisterFromFileChangeService();
}
finally
{
base.Dispose(disposing);
this.isDisposed = true;
}
}
private void CreateFromAssemblyName(AssemblyName name)
{
this.assemblyName = name;
// Use MsBuild to resolve the assemblyname
this.ResolveAssemblyReference();
if (String.IsNullOrEmpty(this.assemblyPath) && (this.ItemNode is MsBuildProjectElement))
{
// Try to get the assmbly name from the hintpath.
this.GetPathNameFromProjectFile();
if (this.assemblyPath == null)
{
// Try to get the assembly name from the path
this.assemblyName = System.Reflection.AssemblyName.GetAssemblyName(this.assemblyPath);
}
}
if (null == resolvedAssemblyName)
{
resolvedAssemblyName = assemblyName;
}
}
/// <summary>
/// Checks if an assembly is already added. The method parses all references and compares the full assemblynames, or the location of the assemblies to decide whether two assemblies are the same.
/// </summary>
/// <returns>true if the assembly has already been added.</returns>
protected override bool IsAlreadyAdded()
{
ReferenceContainerNode referencesFolder = this.ProjectMgr.GetReferenceContainer() as ReferenceContainerNode;
Debug.Assert(referencesFolder != null, "Could not find the References node");
bool shouldCheckPath = !string.IsNullOrEmpty(this.Url);
for (HierarchyNode n = referencesFolder.FirstChild; n != null; n = n.NextSibling)
{
AssemblyReferenceNode assemblyRefererenceNode = n as AssemblyReferenceNode;
if (null != assemblyRefererenceNode)
{
// We will check if the full assemblynames are the same or if the Url of the assemblies is the same.
if (String.Compare(assemblyRefererenceNode.AssemblyName.FullName, this.assemblyName.FullName, StringComparison.OrdinalIgnoreCase) == 0 ||
(shouldCheckPath && CommonUtils.IsSamePath(assemblyRefererenceNode.Url, this.Url)))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Determines if this is node a valid node for painting the default reference icon.
/// </summary>
/// <returns></returns>
protected override bool CanShowDefaultIcon()
{
return IsValid;
}
public bool IsValid
{
get
{
if (String.IsNullOrEmpty(this.assemblyPath) || !File.Exists(this.assemblyPath))
{
return false;
}
return true;
}
}
private void GetPathNameFromProjectFile()
{
string result = this.ItemNode.GetMetadata(ProjectFileConstants.HintPath);
if (String.IsNullOrEmpty(result))
{
result = this.ItemNode.GetMetadata(ProjectFileConstants.AssemblyName);
if (String.IsNullOrEmpty(result))
{
this.assemblyPath = String.Empty;
}
else if (!result.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
{
result += ".dll";
this.assemblyPath = result;
}
}
else
{
this.assemblyPath = CommonUtils.GetAbsoluteFilePath(this.ProjectMgr.ProjectHome, result);
}
}
protected override void ResolveReference()
{
this.ResolveAssemblyReference();
}
private void SetHintPathAndPrivateValue()
{
// Private means local copy; we want to know if it is already set to not override the default
string privateValue = this.ItemNode.GetMetadata(ProjectFileConstants.Private);
// Get the list of items which require HintPath
var references = this.ProjectMgr.CurrentConfig.GetItems(MsBuildGeneratedItemType.ReferenceCopyLocalPaths);
// Now loop through the generated References to find the corresponding one
foreach (var reference in references)
{
string fileName = Path.GetFileNameWithoutExtension(reference.EvaluatedInclude);
if (String.Compare(fileName, this.assemblyName.Name, StringComparison.OrdinalIgnoreCase) == 0)
{
// We found it, now set some properties based on this.
// Remove the HintPath, we will re-add it below if it is needed
if (!String.IsNullOrEmpty(this.assemblyPath))
{
this.ItemNode.SetMetadata(ProjectFileConstants.HintPath, null);
}
string hintPath = reference.GetMetadataValue(ProjectFileConstants.HintPath);
if (!String.IsNullOrEmpty(hintPath))
{
hintPath = CommonUtils.GetRelativeFilePath(this.ProjectMgr.ProjectHome, hintPath);
this.ItemNode.SetMetadata(ProjectFileConstants.HintPath, hintPath);
// If this is not already set, we default to true
if (String.IsNullOrEmpty(privateValue))
{
this.ItemNode.SetMetadata(ProjectFileConstants.Private, true.ToString());
}
}
break;
}
}
}
/// <summary>
/// This function ensures that some properies of the reference are set.
/// </summary>
private void SetReferenceProperties()
{
// Set a default HintPath for msbuild to be able to resolve the reference.
this.ItemNode.SetMetadata(ProjectFileConstants.HintPath, this.assemblyPath);
// Resolve assembly referernces. This is needed to make sure that properties like the full path
// to the assembly or the hint path are set.
if (!ProjectMgr.BuildProject.Targets.ContainsKey(MsBuildTarget.ResolveAssemblyReferences))
{
return;
}
if (this.ProjectMgr.Build(MsBuildTarget.ResolveAssemblyReferences) != MSBuildResult.Successful)
{
return;
}
// Check if we have to resolve again the path to the assembly.
if (string.IsNullOrEmpty(this.assemblyPath))
{
ResolveReference();
}
// Make sure that the hint path if set (if needed).
SetHintPathAndPrivateValue();
}
/// <summary>
/// Does the actual job of resolving an assembly reference. We need a private method that does not violate
/// calling virtual method from the constructor.
/// </summary>
private void ResolveAssemblyReference()
{
if (this.ProjectMgr == null || this.ProjectMgr.IsClosed)
{
return;
}
var group = this.ProjectMgr.CurrentConfig.GetItems(ProjectFileConstants.ReferencePath);
foreach (var item in group)
{
string fullPath = CommonUtils.GetAbsoluteFilePath(this.ProjectMgr.ProjectHome, item.EvaluatedInclude);
System.Reflection.AssemblyName name = System.Reflection.AssemblyName.GetAssemblyName(fullPath);
// Try with full assembly name and then with weak assembly name.
if (String.Equals(name.FullName, this.assemblyName.FullName, StringComparison.OrdinalIgnoreCase) || String.Equals(name.Name, this.assemblyName.Name, StringComparison.OrdinalIgnoreCase))
{
if (!CommonUtils.IsSamePath(fullPath, this.assemblyPath))
{
// set the full path now.
this.assemblyPath = fullPath;
// We have a new item to listen too, since the assembly reference is resolved from a different place.
this.fileChangeListener.ObserveItem(this.assemblyPath);
}
this.resolvedAssemblyName = name;
// No hint path is needed since the assembly path will always be resolved.
return;
}
}
if (this.IsFrameworkAssembly)
{
this.resolvedAssemblyName = this.assemblyName;
this.assemblyPath = GetPathOfFrameworkAssembly(this.resolvedAssemblyName);
}
}
public bool IsFrameworkAssembly
{
get
{
var service = this.GetService(typeof(SVsFrameworkMultiTargeting)) as IVsFrameworkMultiTargeting;
Debug.Assert(service != null, "Didn't find service to determine if assembly is part of framework.");
if (service == null)
return false;
if (this.AssemblyName == null)
return false;
bool result;
ErrorHandler.ThrowOnFailure(service.IsReferenceableInTargetFx(this.AssemblyName.FullName, this.ProjectMgr.TargetFrameworkMoniker.FullName, out result));
return result;
/*Array assemblies;
service.GetFrameworkAssemblies(this.ProjectMgr.TargetFrameworkMoniker.FullName, (uint)__VSFRAMEWORKASSEMBLYTYPE.VSFRAMEWORKASSEMBLYTYPE_ALL, out assemblies);
return assemblies.Cast<string>().Any((x) => x == this.FullPath);*/
//return VsUtilities.IsFrameworkAssembly(this.FullPath, this.ProjectMgr.TargetFrameworkMoniker);
}
}
private string GetPathOfFrameworkAssembly(AssemblyName assemblyName)
{
if (assemblyName == null)
throw new ArgumentNullException("assemblyName");
var service = this.GetService(typeof(SVsFrameworkMultiTargeting)) as IVsFrameworkMultiTargeting;
string path;
ErrorHandler.ThrowOnFailure(service.ResolveAssemblyPath(assemblyName.FullName, this.ProjectMgr.TargetFrameworkMoniker.FullName, out path));
return path;
}
/// <summary>
/// Registers with File change events
/// </summary>
private void InitializeFileChangeEvents()
{
this.fileChangeListener = new FileChangeManager(this.ProjectMgr.Site);
this.fileChangeListener.FileChangedOnDisk += this.OnAssemblyReferenceChangedOnDisk;
}
/// <summary>
/// Unregisters this node from file change notifications.
/// </summary>
private void UnregisterFromFileChangeService()
{
this.fileChangeListener.FileChangedOnDisk -= this.OnAssemblyReferenceChangedOnDisk;
this.fileChangeListener.Dispose();
}
/// <summary>
/// Event callback. Called when one of the assembly file is changed.
/// </summary>
/// <param name="sender">The FileChangeManager object.</param>
/// <param name="e">Event args containing the file name that was updated.</param>
protected virtual void OnAssemblyReferenceChangedOnDisk(object sender, FileChangedOnDiskEventArgs e)
{
Debug.Assert(e != null, "No event args specified for the FileChangedOnDisk event");
// We only care about file deletes, so check for one before enumerating references.
if ((e.FileChangeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Del) == 0)
{
return;
}
if (CommonUtils.IsSamePath(e.FileName, this.assemblyPath))
{
ProjectMgr.OnInvalidateItems(this.Parent);
}
}
/// <summary>
/// Overridden method. The method updates the build dependency list before removing the node from the hierarchy.
/// </summary>
public override void Remove(bool removeFromStorage)
{
if (this.ProjectMgr == null)
{
return;
}
base.RemoveNonDocument(removeFromStorage);
this.ItemNode.RemoveFromProjectFile();
// Notify hierarchy event listeners that items have been invalidated
ProjectMgr.OnInvalidateItems(this);
// Dispose the node now that is deleted.
Dispose(true);
}
#endregion
#if DEV11_OR_LATER
public override bool Equals(IVsReference other)
{
if (other is IVsAssemblyReference)
{
IVsAssemblyReference assemblyReference = (IVsAssemblyReference)other;
return assemblyReference.Name == this.Name && assemblyReference.FullPath == this.FullPath;
}
else if (other is IVsFileReference)
{
IVsFileReference fileReference = (IVsFileReference)other;
return fileReference.Name == Path.GetFileName(this.FullPath) && fileReference.FullPath == this.FullPath;
}
return false;
}
#endif
public override string ReferenceType
{
get { return ProjectFileConstants.Reference; }
}
}
}