mirror of
https://github.com/danbulant/Cosmos
synced 2026-05-19 12:30:32 +00:00
842 lines
38 KiB
C#
842 lines
38 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.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.VisualStudio.Project.Automation;
|
|
using Microsoft.VisualStudio.Shell;
|
|
using Microsoft.VisualStudio.Shell.Interop;
|
|
using MSBuild = Microsoft.Build.Evaluation;
|
|
|
|
namespace Microsoft.VisualStudio.Project
|
|
{
|
|
|
|
public abstract class ProjectContainerNode : ProjectNode,
|
|
IVsParentProject,
|
|
IBuildDependencyOnProjectContainer
|
|
{
|
|
#region fields
|
|
|
|
/// <summary>
|
|
/// Setting this flag to true will build all nested project when building this project
|
|
/// </summary>
|
|
private bool buildNestedProjectsOnBuild = true;
|
|
|
|
private ProjectElement nestedProjectElement;
|
|
|
|
/// <summary>
|
|
/// Defines the listener that would listen on file changes on the nested project node.
|
|
/// </summary>
|
|
///<devremark>
|
|
///This might need a refactoring when nested projects can be added and removed by demand.
|
|
/// </devremark>
|
|
private FileChangeManager nestedProjectNodeReloader;
|
|
#endregion
|
|
|
|
#region ctors
|
|
protected ProjectContainerNode()
|
|
{
|
|
}
|
|
#endregion
|
|
|
|
#region properties
|
|
/// <summary>
|
|
/// Returns teh object that handles listening to file changes on the nested project files.
|
|
/// </summary>
|
|
internal FileChangeManager NestedProjectNodeReloader
|
|
{
|
|
get
|
|
{
|
|
if(this.nestedProjectNodeReloader == null)
|
|
{
|
|
this.nestedProjectNodeReloader = new FileChangeManager(this.Site);
|
|
this.nestedProjectNodeReloader.FileChangedOnDisk += this.OnNestedProjectFileChangedOnDisk;
|
|
}
|
|
|
|
return this.nestedProjectNodeReloader;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region overridden properties
|
|
/// <summary>
|
|
/// This is the object that will be returned by EnvDTE.Project.Object for this project
|
|
/// </summary>
|
|
internal override object Object
|
|
{
|
|
get { return new OASolutionFolder<ProjectContainerNode>(this); }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public overridden methods
|
|
/// <summary>
|
|
/// Gets the nested hierarchy.
|
|
/// </summary>
|
|
/// <param name="itemId">The item id.</param>
|
|
/// <param name="iidHierarchyNested">Identifier of the interface to be returned in ppHierarchyNested.</param>
|
|
/// <param name="ppHierarchyNested">Pointer to the interface whose identifier was passed in iidHierarchyNested.</param>
|
|
/// <param name="pItemId">Pointer to an item identifier of the root node of the nested hierarchy.</param>
|
|
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. If ITEMID is not a nested hierarchy, this method returns E_FAIL.</returns>
|
|
|
|
public override int GetNestedHierarchy(UInt32 itemId, ref Guid iidHierarchyNested, out IntPtr ppHierarchyNested, out uint pItemId)
|
|
{
|
|
pItemId = VSConstants.VSITEMID_ROOT;
|
|
ppHierarchyNested = IntPtr.Zero;
|
|
if(this.FirstChild != null)
|
|
{
|
|
for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
|
|
{
|
|
NestedProjectNode p = n as NestedProjectNode;
|
|
|
|
if(p != null && p.ID == itemId)
|
|
{
|
|
if(p.NestedHierarchy != null)
|
|
{
|
|
IntPtr iunknownPtr = IntPtr.Zero;
|
|
int returnValue = VSConstants.S_OK;
|
|
try
|
|
{
|
|
iunknownPtr = Marshal.GetIUnknownForObject(p.NestedHierarchy);
|
|
Marshal.QueryInterface(iunknownPtr, ref iidHierarchyNested, out ppHierarchyNested);
|
|
}
|
|
catch(COMException e)
|
|
{
|
|
returnValue = e.ErrorCode;
|
|
}
|
|
finally
|
|
{
|
|
if(iunknownPtr != IntPtr.Zero)
|
|
{
|
|
Marshal.Release(iunknownPtr);
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return VSConstants.E_FAIL;
|
|
}
|
|
|
|
public override int IsItemDirty(uint itemId, IntPtr punkDocData, out int pfDirty)
|
|
{
|
|
HierarchyNode hierNode = this.NodeFromItemId(itemId);
|
|
Debug.Assert(hierNode != null, "Hierarchy node not found");
|
|
if(hierNode != this)
|
|
{
|
|
return ErrorHandler.ThrowOnFailure(hierNode.IsItemDirty(itemId, punkDocData, out pfDirty));
|
|
}
|
|
else
|
|
{
|
|
return ErrorHandler.ThrowOnFailure(base.IsItemDirty(itemId, punkDocData, out pfDirty));
|
|
}
|
|
}
|
|
|
|
public override int SaveItem(VSSAVEFLAGS dwSave, string silentSaveAsName, uint itemid, IntPtr punkDocData, out int pfCancelled)
|
|
{
|
|
HierarchyNode hierNode = this.NodeFromItemId(itemid);
|
|
Debug.Assert(hierNode != null, "Hierarchy node not found");
|
|
if(hierNode != this)
|
|
{
|
|
return ErrorHandler.ThrowOnFailure(hierNode.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
|
|
}
|
|
else
|
|
{
|
|
return ErrorHandler.ThrowOnFailure(base.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
|
|
}
|
|
}
|
|
|
|
protected override bool FilterItemTypeToBeAddedToHierarchy(string itemType)
|
|
{
|
|
if(String.Compare(itemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
return base.FilterItemTypeToBeAddedToHierarchy(itemType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called to reload a project item.
|
|
/// Reloads a project and its nested project nodes.
|
|
/// </summary>
|
|
/// <param name="itemId">Specifies itemid from VSITEMID.</param>
|
|
/// <param name="reserved">Reserved.</param>
|
|
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
|
|
public override int ReloadItem(uint itemId, uint reserved)
|
|
{
|
|
#region precondition
|
|
if(this.IsClosed)
|
|
{
|
|
return VSConstants.E_FAIL;
|
|
}
|
|
#endregion
|
|
|
|
NestedProjectNode node = this.NodeFromItemId(itemId) as NestedProjectNode;
|
|
|
|
if(node != null)
|
|
{
|
|
object propertyAsObject = node.GetProperty((int)__VSHPROPID.VSHPROPID_HandlesOwnReload);
|
|
|
|
if(propertyAsObject != null && (bool)propertyAsObject)
|
|
{
|
|
node.ReloadItem(reserved);
|
|
}
|
|
else
|
|
{
|
|
this.ReloadNestedProjectNode(node);
|
|
}
|
|
|
|
return VSConstants.S_OK;
|
|
}
|
|
|
|
return base.ReloadItem(itemId, reserved);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reloads a project and its nested project nodes.
|
|
/// </summary>
|
|
protected override void Reload()
|
|
{
|
|
base.Reload();
|
|
this.CreateNestedProjectNodes();
|
|
}
|
|
#endregion
|
|
|
|
#region IVsParentProject
|
|
public virtual int OpenChildren()
|
|
{
|
|
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
|
|
|
|
Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");
|
|
if(solution == null)
|
|
{
|
|
return VSConstants.E_FAIL;
|
|
}
|
|
|
|
IntPtr iUnKnownForSolution = IntPtr.Zero;
|
|
int returnValue = VSConstants.S_OK; // be optimistic.
|
|
|
|
try
|
|
{
|
|
this.DisableQueryEdit = true;
|
|
this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents | ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
|
|
iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);
|
|
|
|
// notify SolutionEvents listeners that we are about to add children
|
|
IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeOpeningChildren(this));
|
|
|
|
this.AddVirtualProjects();
|
|
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterOpeningChildren(this));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// Exceptions are digested by the caller but we want then to be shown if not a ComException and if not in automation.
|
|
if(!(e is COMException) && !Utilities.IsInAutomationFunction(this.Site))
|
|
{
|
|
string title = null;
|
|
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
|
|
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
|
|
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
|
|
VsShellUtilities.ShowMessageBox(this.Site, title, e.Message, icon, buttons, defaultButton);
|
|
}
|
|
|
|
Trace.WriteLine("Exception : " + e.Message);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
this.DisableQueryEdit = false;
|
|
|
|
if(iUnKnownForSolution != IntPtr.Zero)
|
|
{
|
|
Marshal.Release(iUnKnownForSolution);
|
|
}
|
|
|
|
this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
public virtual int CloseChildren()
|
|
{
|
|
int returnValue = VSConstants.S_OK; // be optimistic.
|
|
|
|
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
|
|
Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");
|
|
|
|
if(solution == null)
|
|
{
|
|
return VSConstants.E_FAIL;
|
|
}
|
|
|
|
IntPtr iUnKnownForSolution = IntPtr.Zero;
|
|
|
|
try
|
|
{
|
|
iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);
|
|
|
|
// notify SolutionEvents listeners that we are about to close children
|
|
IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeClosingChildren(this));
|
|
|
|
// If the removal crashes we never fire the close children event. IS that a problem?
|
|
this.RemoveNestedProjectNodes();
|
|
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterClosingChildren(this));
|
|
}
|
|
finally
|
|
{
|
|
if(iUnKnownForSolution != IntPtr.Zero)
|
|
{
|
|
Marshal.Release(iUnKnownForSolution);
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
#endregion
|
|
|
|
#region IBuildDependencyOnProjectContainerNode
|
|
/// <summary>
|
|
/// Defines whether nested projects should be build with the parent project
|
|
/// </summary>
|
|
public virtual bool BuildNestedProjectsOnBuild
|
|
{
|
|
get { return this.buildNestedProjectsOnBuild; }
|
|
set { this.buildNestedProjectsOnBuild = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerates the nested hierachies that should be added to the build dependency list.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual IVsHierarchy[] EnumNestedHierachiesForBuildDependency()
|
|
{
|
|
List<IVsHierarchy> nestedProjectList = new List<IVsHierarchy>();
|
|
// Add all nested project among projectContainer child nodes
|
|
for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
|
|
{
|
|
NestedProjectNode nestedProjectNode = child as NestedProjectNode;
|
|
if(nestedProjectNode != null)
|
|
{
|
|
nestedProjectList.Add(nestedProjectNode.NestedHierarchy);
|
|
}
|
|
}
|
|
|
|
return nestedProjectList.ToArray();
|
|
}
|
|
#endregion
|
|
|
|
#region helper methods
|
|
|
|
internal protected void RemoveNestedProjectNodes()
|
|
{
|
|
for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
|
|
{
|
|
NestedProjectNode p = n as NestedProjectNode;
|
|
if(p != null)
|
|
{
|
|
p.CloseNestedProjectNode();
|
|
}
|
|
}
|
|
|
|
// We do not care of file changes after this.
|
|
this.NestedProjectNodeReloader.FileChangedOnDisk -= this.OnNestedProjectFileChangedOnDisk;
|
|
this.NestedProjectNodeReloader.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is used when loading the project to loop through all the items
|
|
/// and for each SubProject it finds, it create the project and a node
|
|
/// in our Hierarchy to hold the project.
|
|
/// </summary>
|
|
internal protected void CreateNestedProjectNodes()
|
|
{
|
|
// 1. Create a ProjectElement with the found item and then Instantiate a new Nested project with this ProjectElement.
|
|
// 2. Link into the hierarchy.
|
|
// Read subprojects from from msbuildmodel
|
|
__VSCREATEPROJFLAGS creationFlags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT;
|
|
|
|
if(this.IsNewProject)
|
|
{
|
|
creationFlags |= __VSCREATEPROJFLAGS.CPF_CLONEFILE;
|
|
}
|
|
else
|
|
{
|
|
creationFlags |= __VSCREATEPROJFLAGS.CPF_OPENFILE;
|
|
}
|
|
|
|
foreach (MSBuild.ProjectItem item in this.BuildProject.Items)
|
|
{
|
|
if(String.Compare(item.ItemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
this.nestedProjectElement = new ProjectElement(this, item, false);
|
|
|
|
if(!this.IsNewProject)
|
|
{
|
|
AddExistingNestedProject(null, creationFlags);
|
|
}
|
|
else
|
|
{
|
|
// If we are creating the subproject from a vstemplate/vsz file
|
|
bool isVsTemplate = Utilities.IsTemplateFile(GetProjectTemplatePath(null));
|
|
if(isVsTemplate)
|
|
{
|
|
RunVsTemplateWizard(null, true);
|
|
}
|
|
else
|
|
{
|
|
// We are cloning the specified project file
|
|
AddNestedProjectFromTemplate(null, creationFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.nestedProjectElement = null;
|
|
}
|
|
/// <summary>
|
|
/// Add an existing project as a nested node of our hierarchy.
|
|
/// This is used while loading the project and can also be used
|
|
/// to add an existing project to our hierarchy.
|
|
/// </summary>
|
|
protected internal virtual NestedProjectNode AddExistingNestedProject(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
|
|
{
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
throw new ArgumentNullException("element");
|
|
}
|
|
|
|
string filename = elementToUse.GetFullPathForElement();
|
|
// Delegate to AddNestedProjectFromTemplate. Because we pass flags that specify open project rather then clone, this will works.
|
|
Debug.Assert((creationFlags & __VSCREATEPROJFLAGS.CPF_OPENFILE) == __VSCREATEPROJFLAGS.CPF_OPENFILE, "__VSCREATEPROJFLAGS.CPF_OPENFILE should have been specified, did you mean to call AddNestedProjectFromTemplate?");
|
|
return AddNestedProjectFromTemplate(filename, Path.GetDirectoryName(filename), Path.GetFileName(filename), elementToUse, creationFlags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Let the wizard code execute and provide us the information we need.
|
|
/// Our SolutionFolder automation object should be called back with the
|
|
/// details at which point it will call back one of our method to add
|
|
/// a nested project.
|
|
/// If you are trying to add a new subproject this is probably the
|
|
/// method you want to call. If you are just trying to clone a template
|
|
/// project file, then AddNestedProjectFromTemplate is what you want.
|
|
/// </summary>
|
|
/// <param name="element">The project item to use as the base of the nested project.</param>
|
|
/// <param name="silent">true if the wizard should run silently, otherwise false.</param>
|
|
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Vs")]
|
|
protected internal void RunVsTemplateWizard(ProjectElement element, bool silent)
|
|
{
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
throw new ArgumentNullException("element");
|
|
}
|
|
this.nestedProjectElement = elementToUse;
|
|
|
|
Automation.OAProject oaProject = GetAutomationObject() as Automation.OAProject;
|
|
if(oaProject == null || oaProject.ProjectItems == null)
|
|
throw new System.InvalidOperationException(SR.GetString(SR.InvalidAutomationObject, CultureInfo.CurrentUICulture));
|
|
Debug.Assert(oaProject.Object != null, "The project automation object should have set the Object to the SolutionFolder");
|
|
Automation.OASolutionFolder<ProjectContainerNode> folder = oaProject.Object as Automation.OASolutionFolder<ProjectContainerNode>;
|
|
|
|
// Prepare the parameters to pass to RunWizardFile
|
|
string destination = elementToUse.GetFullPathForElement();
|
|
string template = this.GetProjectTemplatePath(elementToUse);
|
|
|
|
object[] wizParams = new object[7];
|
|
wizParams[0] = EnvDTE.Constants.vsWizardAddSubProject;
|
|
wizParams[1] = Path.GetFileNameWithoutExtension(destination);
|
|
wizParams[2] = oaProject.ProjectItems;
|
|
wizParams[3] = Path.GetDirectoryName(destination);
|
|
wizParams[4] = Path.GetFileNameWithoutExtension(destination);
|
|
wizParams[5] = Path.GetDirectoryName(folder.DTE.FullName); //VS install dir
|
|
wizParams[6] = silent;
|
|
|
|
IVsDetermineWizardTrust wizardTrust = this.GetService(typeof(SVsDetermineWizardTrust)) as IVsDetermineWizardTrust;
|
|
if(wizardTrust != null)
|
|
{
|
|
Guid guidProjectAdding = Guid.Empty;
|
|
|
|
// In case of a project template an empty guid should be added as the guid parameter. See env\msenv\core\newtree.h IsTrustedTemplate method definition.
|
|
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardInitiated(template, ref guidProjectAdding));
|
|
}
|
|
|
|
try
|
|
{
|
|
// Make the call to execute the wizard. This should cause AddNestedProjectFromTemplate to be
|
|
// called back with the correct set of parameters.
|
|
EnvDTE.IVsExtensibility extensibilityService = (EnvDTE.IVsExtensibility)GetService(typeof(EnvDTE.IVsExtensibility));
|
|
EnvDTE.wizardResult result = extensibilityService.RunWizardFile(template, 0, ref wizParams);
|
|
if(result == EnvDTE.wizardResult.wizardResultFailure)
|
|
throw new COMException();
|
|
}
|
|
finally
|
|
{
|
|
if(wizardTrust != null)
|
|
{
|
|
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardCompleted());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will clone a template project file and add it as a
|
|
/// subproject to our hierarchy.
|
|
/// If you want to create a project for which there exist a
|
|
/// vstemplate, consider using RunVsTemplateWizard instead.
|
|
/// </summary>
|
|
protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
|
|
{
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
throw new ArgumentNullException("element");
|
|
}
|
|
string destination = elementToUse.GetFullPathForElement();
|
|
string template = this.GetProjectTemplatePath(elementToUse);
|
|
|
|
return this.AddNestedProjectFromTemplate(template, Path.GetDirectoryName(destination), Path.GetFileName(destination), elementToUse, creationFlags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This can be called directly or through RunVsTemplateWizard.
|
|
/// This will clone a template project file and add it as a
|
|
/// subproject to our hierarchy.
|
|
/// If you want to create a project for which there exist a
|
|
/// vstemplate, consider using RunVsTemplateWizard instead.
|
|
/// </summary>
|
|
protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(string fileName, string destination, string projectName, ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
|
|
{
|
|
// If this is project creation and the template specified a subproject in its project file, this.nestedProjectElement will be used
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
// If this is null, this means MSBuild does not know anything about our subproject so add an MSBuild item for it
|
|
elementToUse = new ProjectElement(this, fileName, ProjectFileConstants.SubProject);
|
|
}
|
|
|
|
NestedProjectNode node = CreateNestedProjectNode(elementToUse);
|
|
node.Init(fileName, destination, projectName, creationFlags);
|
|
|
|
// In case that with did not have an existing element, or the nestedProjectelement was null
|
|
// and since Init computes the final path, set the MSBuild item to that path
|
|
if(this.nestedProjectElement == null)
|
|
{
|
|
string relativePath = node.Url;
|
|
if(Path.IsPathRooted(relativePath))
|
|
{
|
|
relativePath = this.ProjectFolder;
|
|
if(!relativePath.EndsWith("/\\", StringComparison.Ordinal))
|
|
{
|
|
relativePath += Path.DirectorySeparatorChar;
|
|
}
|
|
|
|
relativePath = new Url(relativePath).MakeRelative(new Url(node.Url));
|
|
}
|
|
|
|
elementToUse.Rename(relativePath);
|
|
}
|
|
|
|
this.AddChild(node);
|
|
return node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this method if you want to provide your own type of nodes.
|
|
/// This would be the case if you derive a class from NestedProjectNode
|
|
/// </summary>
|
|
protected virtual NestedProjectNode CreateNestedProjectNode(ProjectElement element)
|
|
{
|
|
return new NestedProjectNode(this, element);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links the nested project nodes to the solution. The default implementation parses all nested project nodes and calles AddVirtualProjectEx on them.
|
|
/// </summary>
|
|
protected virtual void AddVirtualProjects()
|
|
{
|
|
for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
|
|
{
|
|
NestedProjectNode nestedProjectNode = child as NestedProjectNode;
|
|
if(nestedProjectNode != null)
|
|
{
|
|
nestedProjectNode.AddVirtualProject();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Based on the Template and TypeGuid properties of the
|
|
/// element, generate the full template path.
|
|
///
|
|
/// TypeGuid should be the Guid of a registered project factory.
|
|
/// Template can be a full path, a project template (for projects
|
|
/// that support VsTemplates) or a relative path (for other projects).
|
|
/// </summary>
|
|
protected virtual string GetProjectTemplatePath(ProjectElement element)
|
|
{
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
throw new ArgumentNullException("element");
|
|
}
|
|
|
|
string templateFile = elementToUse.GetMetadata(ProjectFileConstants.Template);
|
|
Debug.Assert(!String.IsNullOrEmpty(templateFile), "No template file has been specified in the template attribute in the project file");
|
|
|
|
string fullPath = templateFile;
|
|
if(!Path.IsPathRooted(templateFile))
|
|
{
|
|
RegisteredProjectType registeredProjectType = this.GetRegisteredProject(elementToUse);
|
|
|
|
// This is not a full path
|
|
Debug.Assert(registeredProjectType != null && (!String.IsNullOrEmpty(registeredProjectType.DefaultProjectExtensionValue) || !String.IsNullOrEmpty(registeredProjectType.WizardTemplatesDirValue)), " Registered wizard directory value not set in the registry.");
|
|
|
|
// See if this specify a VsTemplate file
|
|
fullPath = registeredProjectType.GetVsTemplateFile(templateFile);
|
|
if(String.IsNullOrEmpty(fullPath))
|
|
{
|
|
// Default to using the WizardTemplateDir to calculate the absolute path
|
|
fullPath = Path.Combine(registeredProjectType.WizardTemplatesDirValue, templateFile);
|
|
}
|
|
}
|
|
|
|
return fullPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get information from the registry based for the project
|
|
/// factory corresponding to the TypeGuid of the element
|
|
/// </summary>
|
|
private RegisteredProjectType GetRegisteredProject(ProjectElement element)
|
|
{
|
|
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
|
|
|
|
if(elementToUse == null)
|
|
{
|
|
throw new ArgumentNullException("element");
|
|
}
|
|
|
|
// Get the project type guid from project elementToUse
|
|
string typeGuidString = elementToUse.GetMetadataAndThrow(ProjectFileConstants.TypeGuid, new Exception());
|
|
Guid projectFactoryGuid = new Guid(typeGuidString);
|
|
|
|
EnvDTE.DTE dte = this.ProjectMgr.Site.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
|
|
Debug.Assert(dte != null, "Could not get the automation object from the services exposed by this project");
|
|
|
|
if(dte == null)
|
|
throw new InvalidOperationException();
|
|
|
|
RegisteredProjectType registeredProjectType = RegisteredProjectType.CreateRegisteredProjectType(projectFactoryGuid);
|
|
Debug.Assert(registeredProjectType != null, "Could not read the registry setting associated to this project.");
|
|
if(registeredProjectType == null)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
return registeredProjectType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reloads a nested project node by deleting it and readding it.
|
|
/// </summary>
|
|
/// <param name="node">The node to reload.</param>
|
|
protected virtual void ReloadNestedProjectNode(NestedProjectNode node)
|
|
{
|
|
if(node == null)
|
|
{
|
|
throw new ArgumentNullException("node");
|
|
}
|
|
|
|
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
|
|
|
|
if(solution == null)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
NestedProjectNode newNode = null;
|
|
try
|
|
{
|
|
// (VS 2005 UPDATE) When deleting and re-adding the nested project,
|
|
// we do not want SCC to see this as a delete and add operation.
|
|
this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
|
|
|
|
// notify SolutionEvents listeners that we are about to add children
|
|
IVsFireSolutionEvents fireSolutionEvents = solution as IVsFireSolutionEvents;
|
|
|
|
if(fireSolutionEvents == null)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeUnloadProject(node.NestedHierarchy));
|
|
|
|
int isDirtyAsInt = 0;
|
|
this.IsDirty(out isDirtyAsInt);
|
|
|
|
bool isDirty = (isDirtyAsInt == 0) ? false : true;
|
|
|
|
ProjectElement element = node.ItemNode;
|
|
node.CloseNestedProjectNode();
|
|
|
|
// Remove from the solution
|
|
this.RemoveChild(node);
|
|
|
|
// Now readd it
|
|
try
|
|
{
|
|
__VSCREATEPROJFLAGS flags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT | __VSCREATEPROJFLAGS.CPF_OPENFILE;
|
|
newNode = this.AddExistingNestedProject(element, flags);
|
|
newNode.AddVirtualProject();
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
// We get a System.Exception if anything failed, thus we have no choice but catch it.
|
|
// Exceptions are digested by VS. Show the error if not in automation.
|
|
if(!Utilities.IsInAutomationFunction(this.Site))
|
|
{
|
|
string message = (String.IsNullOrEmpty(e.Message)) ? SR.GetString(SR.NestedProjectFailedToReload, CultureInfo.CurrentUICulture) : e.Message;
|
|
string title = string.Empty;
|
|
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
|
|
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
|
|
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
|
|
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
|
|
}
|
|
|
|
// Do not digest exception. let the caller handle it. If in a later stage this exception is not digested then the above messagebox is not needed.
|
|
throw;
|
|
}
|
|
|
|
#if DEBUG
|
|
IVsHierarchy nestedHierarchy;
|
|
ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(newNode.GetMkDocument(), out nestedHierarchy));
|
|
Debug.Assert(nestedHierarchy != null && Utilities.IsSameComObject(nestedHierarchy, newNode.NestedHierarchy), "The nested hierrachy was not reloaded correctly.");
|
|
#endif
|
|
this.SetProjectFileDirty(isDirty);
|
|
|
|
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterLoadProject(newNode.NestedHierarchy));
|
|
}
|
|
finally
|
|
{
|
|
// In this scenario the nested project failed to unload or reload the nested project. We will unload the whole project, otherwise the nested project is lost.
|
|
// This is similar to the scenario when one wants to open a project and the nested project cannot be loaded because for example the project file has xml errors.
|
|
// We should note that we rely here that if the unload fails then exceptions are not digested and are shown to the user.
|
|
if(newNode == null || newNode.NestedHierarchy == null)
|
|
{
|
|
ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement((uint)__VSSLNCLOSEOPTIONS.SLNCLOSEOPT_UnloadProject | (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, this.InteropSafeIVsHierarchy, 0));
|
|
}
|
|
else
|
|
{
|
|
this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event callback. Called when one of the nested project files is changed.
|
|
/// </summary>
|
|
/// <param name="sender">The FileChangeManager object.</param>
|
|
/// <param name="e">Event args containing the file name that was updated.</param>
|
|
private void OnNestedProjectFileChangedOnDisk(object sender, FileChangedOnDiskEventArgs e)
|
|
{
|
|
#region Pre-condition validation
|
|
Debug.Assert(e != null, "No event args specified for the FileChangedOnDisk event");
|
|
|
|
// We care only about time change for reload.
|
|
if((e.FileChangeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Time) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// test if we actually have a document for this id.
|
|
string moniker;
|
|
this.GetMkDocument(e.ItemID, out moniker);
|
|
Debug.Assert(NativeMethods.IsSamePath(moniker, e.FileName), " The file + " + e.FileName + " has changed but we could not retrieve the path for the item id associated to the path.");
|
|
#endregion
|
|
|
|
bool reload = true;
|
|
if(!Utilities.IsInAutomationFunction(this.Site))
|
|
{
|
|
// Prompt to reload the nested project file. We use the moniker here since the filename from the event arg is canonicalized.
|
|
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.QueryReloadNestedProject, CultureInfo.CurrentUICulture), moniker);
|
|
string title = string.Empty;
|
|
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_INFO;
|
|
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
|
|
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
|
|
reload = (VsShellUtilities.ShowMessageBox(this.Site, message, title, icon, buttons, defaultButton) == NativeMethods.IDYES);
|
|
}
|
|
|
|
if(reload)
|
|
{
|
|
// We have to use here the interface method call, since it might be that specialized project nodes like the project container item
|
|
// is owerwriting the default functionality.
|
|
this.ReloadItem(e.ItemID, 0);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|