Replaced the old VS templates with ones that offer more flexiblity. Started replacing the Content Project for the samples with our custom project type. Inlcuded a basic not yet working AssimpImporter.
578 lines
20 KiB
C#
578 lines
20 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.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.VisualStudio.Shell;
|
|
using Microsoft.VisualStudio.Shell.Interop;
|
|
using Microsoft.VisualStudio;
|
|
|
|
namespace Microsoft.VisualStudio.Project
|
|
{
|
|
public class ProjectReferenceNode : ReferenceNode
|
|
{
|
|
#region fields
|
|
/// <summary>
|
|
/// The name of the assembly this refernce represents
|
|
/// </summary>
|
|
private Guid referencedProjectGuid;
|
|
|
|
private string referencedProjectName = String.Empty;
|
|
|
|
private string referencedProjectRelativePath = String.Empty;
|
|
|
|
private string referencedProjectFullPath = String.Empty;
|
|
|
|
private BuildDependency buildDependency;
|
|
|
|
/// <summary>
|
|
/// This is a reference to the automation object for the referenced project.
|
|
/// </summary>
|
|
private EnvDTE.Project referencedProject;
|
|
|
|
/// <summary>
|
|
/// This state is controlled by the solution events.
|
|
/// The state is set to false by OnBeforeUnloadProject.
|
|
/// The state is set to true by OnBeforeCloseProject event.
|
|
/// </summary>
|
|
private bool canRemoveReference = true;
|
|
|
|
private FileSystemWatcher fileWatcher;
|
|
|
|
public bool IgnoreFileChanges = false;
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
|
|
public override string Url
|
|
{
|
|
get
|
|
{
|
|
return this.referencedProjectFullPath;
|
|
}
|
|
}
|
|
|
|
public virtual string RelativeUrl
|
|
{
|
|
get
|
|
{
|
|
return this.referencedProjectRelativePath;
|
|
}
|
|
}
|
|
|
|
public override string Caption
|
|
{
|
|
get
|
|
{
|
|
return this.referencedProjectName;
|
|
}
|
|
}
|
|
|
|
public Guid ReferencedProjectGuid
|
|
{
|
|
get
|
|
{
|
|
return this.referencedProjectGuid;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Possiblity to shortcut and set the dangling project reference icon.
|
|
/// It is ussually manipulated by solution listsneres who handle reference updates.
|
|
/// </summary>
|
|
public bool IsValid
|
|
{
|
|
get
|
|
{
|
|
return this.ReferencedProjectObject != null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls the state whether this reference can be removed or not. Think of the project unload scenario where the project reference should not be deleted.
|
|
/// </summary>
|
|
public bool CanRemoveReference
|
|
{
|
|
get
|
|
{
|
|
return this.canRemoveReference;
|
|
}
|
|
set
|
|
{
|
|
this.canRemoveReference = value;
|
|
}
|
|
}
|
|
|
|
public string ReferencedProjectName
|
|
{
|
|
get { return this.referencedProjectName; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the automation object for the referenced project.
|
|
/// </summary>
|
|
public EnvDTE.Project ReferencedProjectObject
|
|
{
|
|
get
|
|
{
|
|
// If the referenced project is null then re-read.
|
|
if (this.referencedProject == null)
|
|
{
|
|
this.referencedProject = VsUtilities.GetProject(this.ProjectMgr.Site, referencedProjectFullPath);
|
|
}
|
|
|
|
return this.referencedProject;
|
|
}
|
|
set
|
|
{
|
|
this.referencedProject = value;
|
|
}
|
|
}
|
|
|
|
public string UniqueName
|
|
{
|
|
get
|
|
{
|
|
return this.ReferencedProjectGuid.ToString("B") + "|" + CommonUtils.TrimUpPaths(this.referencedProjectRelativePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the full path to the assembly generated by this project.
|
|
/// </summary>
|
|
public string ReferencedProjectOutputPath
|
|
{
|
|
get
|
|
{
|
|
// Make sure that the referenced project implements the automation object.
|
|
if (null == this.ReferencedProjectObject)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
EnvDTE.ConfigurationManager confManager = null;
|
|
// Get the configuration manager from the project.
|
|
try
|
|
{
|
|
confManager = this.ReferencedProjectObject.ConfigurationManager;
|
|
}
|
|
catch (COMException)
|
|
{
|
|
//The project could be not available.
|
|
}
|
|
|
|
if (null == confManager)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the active configuration.
|
|
EnvDTE.Configuration config = confManager.ActiveConfiguration;
|
|
if (null == config)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (null == config.Properties)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Get the output path for the current configuration.
|
|
EnvDTE.Property outputPathProperty = config.Properties.Item("OutputPath");
|
|
if (null == outputPathProperty || outputPathProperty.Value == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Usually the output path is relative to the project path. If it is set as an
|
|
// absolute path, this call has no effect.
|
|
string outputPath = CommonUtils.GetAbsoluteDirectoryPath(
|
|
Path.GetDirectoryName(referencedProjectFullPath),
|
|
outputPathProperty.Value.ToString());
|
|
|
|
// Now get the name of the assembly from the project.
|
|
// Some project system throw if the property does not exist. We expect an ArgumentException.
|
|
EnvDTE.Property assemblyNameProperty = null;
|
|
try
|
|
{
|
|
assemblyNameProperty = this.ReferencedProjectObject.Properties.Item("OutputFileName");
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
|
|
if (null == assemblyNameProperty)
|
|
{
|
|
return null;
|
|
}
|
|
// build the full path adding the name of the assembly to the output path.
|
|
outputPath = Path.Combine(outputPath, assemblyNameProperty.Value.ToString());
|
|
|
|
return outputPath;
|
|
}
|
|
}
|
|
|
|
public string AssemblyName
|
|
{
|
|
get
|
|
{
|
|
// Now get the name of the assembly from the project.
|
|
// Some project system throw if the property does not exist. We expect an ArgumentException.
|
|
EnvDTE.Property assemblyNameProperty = null;
|
|
if (ReferencedProjectObject != null &&
|
|
!(ReferencedProjectObject is Automation.OAProject)) // our own projects don't have assembly names
|
|
{
|
|
try
|
|
{
|
|
assemblyNameProperty = this.ReferencedProjectObject.Properties.Item(ProjectFileConstants.AssemblyName);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
if (assemblyNameProperty != null)
|
|
{
|
|
return assemblyNameProperty.Value.ToString();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private Automation.OAProjectReference projectReference;
|
|
public override object Object
|
|
{
|
|
get
|
|
{
|
|
if (null == projectReference)
|
|
{
|
|
projectReference = new Automation.OAProjectReference(this);
|
|
}
|
|
return projectReference;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ctors
|
|
/// <summary>
|
|
/// Constructor for the ReferenceNode. It is called when the project is reloaded, when the project element representing the refernce exists.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
|
|
public ProjectReferenceNode(ProjectNode root, ProjectElement element)
|
|
: base(root, element)
|
|
{
|
|
this.referencedProjectRelativePath = this.ItemNode.GetMetadata(ProjectFileConstants.Include);
|
|
Debug.Assert(!String.IsNullOrEmpty(this.referencedProjectRelativePath), "Could not retrieve referenced project path form project file");
|
|
|
|
string guidString = this.ItemNode.GetMetadata(ProjectFileConstants.Project);
|
|
|
|
// Continue even if project setttings cannot be read.
|
|
try
|
|
{
|
|
this.referencedProjectGuid = new Guid(guidString);
|
|
|
|
this.buildDependency = new BuildDependency(this.ProjectMgr, this.referencedProjectGuid);
|
|
this.ProjectMgr.AddBuildDependency(this.buildDependency);
|
|
}
|
|
finally
|
|
{
|
|
Debug.Assert(this.referencedProjectGuid != Guid.Empty, "Could not retrive referenced project guidproject file");
|
|
|
|
this.referencedProjectName = this.ItemNode.GetMetadata(ProjectFileConstants.Name);
|
|
|
|
Debug.Assert(!String.IsNullOrEmpty(this.referencedProjectName), "Could not retrive referenced project name form project file");
|
|
}
|
|
|
|
try
|
|
{
|
|
this.referencedProjectFullPath = CommonUtils.GetAbsoluteFilePath(this.ProjectMgr.ProjectHome, this.referencedProjectRelativePath);
|
|
}
|
|
catch { } //Just make an invalid reference.
|
|
}
|
|
|
|
/// <summary>
|
|
/// constructor for the ProjectReferenceNode
|
|
/// </summary>
|
|
public ProjectReferenceNode(ProjectNode root, string referencedProjectName, string projectPath, string projectReference)
|
|
: base(root)
|
|
{
|
|
if (projectReference == null)
|
|
{
|
|
throw new ArgumentNullException("projectReference");
|
|
}
|
|
|
|
this.referencedProjectName = referencedProjectName;
|
|
|
|
int indexOfSeparator = projectReference.IndexOf('|');
|
|
|
|
|
|
string fileName = String.Empty;
|
|
|
|
// Unfortunately we cannot use the path part of the projectReference string since it is not resolving correctly relative pathes.
|
|
if (indexOfSeparator != -1)
|
|
{
|
|
string projectGuid = projectReference.Substring(0, indexOfSeparator);
|
|
this.referencedProjectGuid = new Guid(projectGuid);
|
|
if (indexOfSeparator + 1 < projectReference.Length)
|
|
{
|
|
string remaining = projectReference.Substring(indexOfSeparator + 1);
|
|
indexOfSeparator = remaining.IndexOf('|');
|
|
|
|
if (indexOfSeparator == -1)
|
|
{
|
|
fileName = remaining;
|
|
}
|
|
else
|
|
{
|
|
fileName = remaining.Substring(0, indexOfSeparator);
|
|
}
|
|
}
|
|
}
|
|
|
|
string justTheFileName = Path.GetFileName(fileName);
|
|
|
|
try
|
|
{
|
|
this.referencedProjectFullPath = CommonUtils.GetAbsoluteFilePath(Path.GetDirectoryName(projectPath), justTheFileName);
|
|
// TODO: Maybe referenced projects should be relative to ProjectDir?
|
|
this.referencedProjectRelativePath = CommonUtils.GetRelativeFilePath(this.ProjectMgr.ProjectHome, this.referencedProjectFullPath);
|
|
}
|
|
catch { } //Make an invalid reference.
|
|
|
|
this.buildDependency = new BuildDependency(this.ProjectMgr, this.referencedProjectGuid);
|
|
this.ProjectMgr.AddBuildDependency(this.buildDependency);
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region methods
|
|
protected override NodeProperties CreatePropertiesObject()
|
|
{
|
|
return new ProjectReferencesProperties(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The node is added to the hierarchy and then updates the build dependency list.
|
|
/// </summary>
|
|
public override void AddReference()
|
|
{
|
|
if (this.ProjectMgr == null)
|
|
{
|
|
return;
|
|
}
|
|
base.AddReference();
|
|
this.ProjectMgr.AddBuildDependency(this.buildDependency);
|
|
return;
|
|
}
|
|
|
|
/// <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 || !this.CanRemoveReference)
|
|
{
|
|
return;
|
|
}
|
|
this.ProjectMgr.RemoveBuildDependency(this.buildDependency);
|
|
base.Remove(removeFromStorage);
|
|
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links a reference node to the project file.
|
|
/// </summary>
|
|
protected override void BindReferenceData()
|
|
{
|
|
Debug.Assert(!String.IsNullOrEmpty(this.referencedProjectName), "The referencedProjectName field has not been initialized");
|
|
Debug.Assert(this.referencedProjectGuid != Guid.Empty, "The referencedProjectName field has not been initialized");
|
|
|
|
this.ItemNode = new MsBuildProjectElement(this.ProjectMgr, this.referencedProjectRelativePath, ProjectFileConstants.ProjectReference);
|
|
|
|
this.ItemNode.SetMetadata(ProjectFileConstants.Name, this.referencedProjectName);
|
|
this.ItemNode.SetMetadata(ProjectFileConstants.Project, this.referencedProjectGuid.ToString("B"));
|
|
this.ItemNode.SetMetadata(ProjectFileConstants.Private, true.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines whether this node is valid node for painting the refererence icon.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected override bool CanShowDefaultIcon()
|
|
{
|
|
if (this.referencedProjectGuid == Guid.Empty || this.ProjectMgr == null || this.ProjectMgr.IsClosed || !this.IsValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IVsHierarchy hierarchy = null;
|
|
|
|
hierarchy = VsShellUtilities.GetHierarchy(this.ProjectMgr.Site, this.referencedProjectGuid);
|
|
|
|
if (hierarchy == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
//If the Project is unloaded return false
|
|
if (this.ReferencedProjectObject == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (!String.IsNullOrEmpty(this.referencedProjectFullPath) && File.Exists(this.referencedProjectFullPath));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a project reference can be added to the hierarchy. It calls base to see if the reference is not already there, then checks for circular references.
|
|
/// </summary>
|
|
/// <param name="errorHandler">The error handler delegate to return</param>
|
|
/// <returns></returns>
|
|
protected override bool CanAddReference(out CannotAddReferenceErrorMessage errorHandler)
|
|
{
|
|
// When this method is called this refererence has not yet been added to the hierarchy, only instantiated.
|
|
if (!base.CanAddReference(out errorHandler))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
errorHandler = null;
|
|
if (this.IsThisProjectReferenceInCycle())
|
|
{
|
|
errorHandler = new CannotAddReferenceErrorMessage(ShowCircularReferenceErrorMessage);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool IsThisProjectReferenceInCycle()
|
|
{
|
|
return IsReferenceInCycle(this.referencedProjectGuid);
|
|
}
|
|
|
|
private void ShowCircularReferenceErrorMessage()
|
|
{
|
|
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.ProjectContainsCircularReferences, CultureInfo.CurrentUICulture), this.referencedProjectName);
|
|
string title = string.Empty;
|
|
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
|
|
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
|
|
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
|
|
VsShellUtilities.ShowMessageBox(this.ProjectMgr.Site, title, message, icon, buttons, defaultButton);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursively search if this project reference guid is in cycle.
|
|
/// </summary>
|
|
private bool IsReferenceInCycle(Guid projectGuid)
|
|
{
|
|
// TODO: This has got to be wrong, it doesn't work w/ other project types.
|
|
IVsHierarchy hierarchy = VsShellUtilities.GetHierarchy(this.ProjectMgr.Site, projectGuid);
|
|
|
|
IReferenceContainerProvider provider = hierarchy.GetProject().GetCommonProject() as IReferenceContainerProvider;
|
|
if (provider != null)
|
|
{
|
|
IReferenceContainer referenceContainer = provider.GetReferenceContainer();
|
|
|
|
Debug.Assert(referenceContainer != null, "Could not found the References virtual node");
|
|
|
|
foreach (ReferenceNode refNode in referenceContainer.EnumReferences())
|
|
{
|
|
ProjectReferenceNode projRefNode = refNode as ProjectReferenceNode;
|
|
if (projRefNode != null)
|
|
{
|
|
if (projRefNode.ReferencedProjectGuid == this.ProjectMgr.ProjectIDGuid)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (this.IsReferenceInCycle(projRefNode.ReferencedProjectGuid))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
protected override void ResolveReference()
|
|
{
|
|
string oldOutputPath = this.ReferencedProjectOutputPath;
|
|
|
|
base.ResolveReference();
|
|
|
|
string outputPath = this.ReferencedProjectOutputPath;
|
|
if (!string.IsNullOrEmpty(outputPath) && (oldOutputPath != outputPath || fileWatcher == null))
|
|
{
|
|
if (fileWatcher != null)
|
|
fileWatcher.Dispose();
|
|
|
|
fileWatcher = new FileSystemWatcher(Path.GetDirectoryName(outputPath));
|
|
fileWatcher.Changed += fileWatcher_Changed;
|
|
fileWatcher.EnableRaisingEvents = true;
|
|
}
|
|
}
|
|
|
|
void fileWatcher_Changed(object sender, FileSystemEventArgs e)
|
|
{
|
|
if (e.ChangeType == WatcherChangeTypes.Changed && IgnoreFileChanges == false && e.FullPath == this.ReferencedProjectOutputPath)
|
|
{
|
|
System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
this.RefreshReference(true);
|
|
});
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (fileWatcher != null)
|
|
{
|
|
fileWatcher.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#if DEV11_OR_LATER
|
|
public override bool Equals(IVsReference other)
|
|
{
|
|
if (other is IVsProjectReference)
|
|
{
|
|
IVsProjectReference projectReference = (IVsProjectReference)other;
|
|
|
|
return projectReference.Name == this.Name && projectReference.FullPath == this.FullPath &&
|
|
projectReference.Identity == this.ReferencedProjectGuid.ToString("B") && projectReference.ReferenceSpecification == this.UniqueName;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
public override string ReferenceType
|
|
{
|
|
get { return ProjectFileConstants.ProjectReference; }
|
|
}
|
|
}
|
|
}
|