850 lines
32 KiB
C#
850 lines
32 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.Collections.Generic;
|
||
|
using System.Diagnostics;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Globalization;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using Microsoft.VisualStudio;
|
||
|
using Microsoft.VisualStudio.OLE.Interop;
|
||
|
using Microsoft.VisualStudio.Shell;
|
||
|
//#define ConfigTrace
|
||
|
using Microsoft.VisualStudio.Shell.Interop;
|
||
|
using MSBuildConstruction = Microsoft.Build.Construction;
|
||
|
using MSBuildExecution = Microsoft.Build.Execution;
|
||
|
|
||
|
namespace Microsoft.VisualStudio.Project
|
||
|
{
|
||
|
[ComVisible(true)]
|
||
|
public abstract class Config :
|
||
|
IVsCfg,
|
||
|
IVsProjectCfg,
|
||
|
IVsProjectCfg2,
|
||
|
IVsProjectFlavorCfg,
|
||
|
IVsDebuggableProjectCfg,
|
||
|
ISpecifyPropertyPages,
|
||
|
IVsSpecifyProjectDesignerPages,
|
||
|
IVsCfgBrowseObject
|
||
|
{
|
||
|
public const string Debug = "Debug";
|
||
|
public const string AnyCPU = "AnyCPU";
|
||
|
|
||
|
internal string configName;
|
||
|
private string platformName;
|
||
|
private ProjectNode project;
|
||
|
private MSBuildExecution.ProjectInstance currentConfig;
|
||
|
private IVsProjectFlavorCfg flavoredCfg;
|
||
|
private List<OutputGroup> outputGroups;
|
||
|
private BuildableProjectConfig buildableCfg;
|
||
|
|
||
|
#region properties
|
||
|
public ProjectNode ProjectMgr
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.project;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual string ConfigName
|
||
|
{
|
||
|
get { return configName; }
|
||
|
}
|
||
|
|
||
|
public virtual string PlatformName
|
||
|
{
|
||
|
get { return platformName; }
|
||
|
}
|
||
|
|
||
|
protected IList<OutputGroup> OutputGroups
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (null == this.outputGroups)
|
||
|
{
|
||
|
// Initialize output groups
|
||
|
this.outputGroups = new List<OutputGroup>();
|
||
|
|
||
|
// Get the list of group names from the project.
|
||
|
// The main reason we get it from the project is to make it easier for someone to modify
|
||
|
// it by simply overriding that method and providing the correct MSBuild target(s).
|
||
|
IList<KeyValuePair<string, string>> groupNames = project.GetOutputGroupNames();
|
||
|
|
||
|
if (groupNames != null)
|
||
|
{
|
||
|
// Populate the output array
|
||
|
foreach (KeyValuePair<string, string> group in groupNames)
|
||
|
{
|
||
|
OutputGroup outputGroup = CreateOutputGroup(project, group);
|
||
|
this.outputGroups.Add(outputGroup);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
return this.outputGroups;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region ctors
|
||
|
public Config(ProjectNode project, string configuration, string platform)
|
||
|
{
|
||
|
this.project = project;
|
||
|
this.configName = configuration;
|
||
|
this.platformName = platform;
|
||
|
|
||
|
var flavoredCfgProvider = ProjectMgr.GetOuterInterface<IVsProjectFlavorCfgProvider>();
|
||
|
VsUtilities.ArgumentNotNull("flavoredCfgProvider", flavoredCfgProvider);
|
||
|
ErrorHandler.ThrowOnFailure(flavoredCfgProvider.CreateProjectFlavorCfg(this, out flavoredCfg));
|
||
|
VsUtilities.ArgumentNotNull("flavoredCfg", flavoredCfg);
|
||
|
|
||
|
// if the flavored object support XML fragment, initialize it
|
||
|
IPersistXMLFragment persistXML = flavoredCfg as IPersistXMLFragment;
|
||
|
if (null != persistXML)
|
||
|
{
|
||
|
this.project.LoadXmlFragment(persistXML, ConfigName, PlatformName);
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region methods
|
||
|
|
||
|
public virtual OutputGroup CreateOutputGroup(ProjectNode project, KeyValuePair<string, string> group)
|
||
|
{
|
||
|
OutputGroup outputGroup = new OutputGroup(group.Key, group.Value, project, this);
|
||
|
return outputGroup;
|
||
|
}
|
||
|
public void PrepareBuild(uint vsopts, bool clean)
|
||
|
{
|
||
|
project.PrepareBuild(vsopts, this.ConfigName, this.PlatformName, clean);
|
||
|
}
|
||
|
|
||
|
public virtual string GetConfigurationProperty(string propertyName, bool resetCache)
|
||
|
{
|
||
|
MSBuildExecution.ProjectPropertyInstance property = GetMsBuildProperty(propertyName, resetCache);
|
||
|
if (property == null)
|
||
|
return null;
|
||
|
|
||
|
return property.EvaluatedValue;
|
||
|
}
|
||
|
|
||
|
public virtual void SetConfigurationProperty(string propertyName, string propertyValue)
|
||
|
{
|
||
|
if (!this.project.QueryEditProjectFile(false))
|
||
|
{
|
||
|
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
|
||
|
}
|
||
|
|
||
|
string condition = String.Format(CultureInfo.InvariantCulture, ConfigProvider.configString, this.ConfigName);
|
||
|
|
||
|
SetPropertyUnderCondition(propertyName, propertyValue, condition);
|
||
|
|
||
|
// property cache will need to be updated
|
||
|
this.currentConfig = null;
|
||
|
|
||
|
this.project.SetProjectFileDirty(true);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Emulates the behavior of SetProperty(name, value, condition) on the old MSBuild object model.
|
||
|
/// This finds a property group with the specified condition (or creates one if necessary) then sets the property in there.
|
||
|
/// </summary>
|
||
|
private void SetPropertyUnderCondition(string propertyName, string propertyValue, string condition)
|
||
|
{
|
||
|
string conditionTrimmed = (condition == null) ? String.Empty : condition.Trim();
|
||
|
|
||
|
if (conditionTrimmed.Length == 0)
|
||
|
{
|
||
|
this.project.BuildProject.SetProperty(propertyName, propertyValue);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// New OM doesn't have a convenient equivalent for setting a property with a particular property group condition.
|
||
|
// So do it ourselves.
|
||
|
MSBuildConstruction.ProjectPropertyGroupElement newGroup = null;
|
||
|
|
||
|
foreach (MSBuildConstruction.ProjectPropertyGroupElement group in this.project.BuildProject.Xml.PropertyGroups)
|
||
|
{
|
||
|
if (String.Equals(group.Condition.Trim(), conditionTrimmed, StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
newGroup = group;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (newGroup == null)
|
||
|
{
|
||
|
newGroup = this.project.BuildProject.Xml.AddPropertyGroup(); // Adds after last existing PG, else at start of project
|
||
|
newGroup.Condition = condition;
|
||
|
}
|
||
|
|
||
|
foreach (MSBuildConstruction.ProjectPropertyElement property in newGroup.PropertiesReversed) // If there's dupes, pick the last one so we win
|
||
|
{
|
||
|
if (String.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase) && property.Condition.Length == 0)
|
||
|
{
|
||
|
property.Value = propertyValue;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
newGroup.AddProperty(propertyName, propertyValue);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// If flavored, and if the flavor config can be dirty, ask it if it is dirty
|
||
|
/// </summary>
|
||
|
/// <param name="storageType">Project file or user file</param>
|
||
|
/// <returns>0 = not dirty</returns>
|
||
|
public int IsFlavorDirty(_PersistStorageType storageType)
|
||
|
{
|
||
|
int isDirty = 0;
|
||
|
if (this.flavoredCfg != null && this.flavoredCfg is IPersistXMLFragment)
|
||
|
{
|
||
|
ErrorHandler.ThrowOnFailure(((IPersistXMLFragment)this.flavoredCfg).IsFragmentDirty((uint)storageType, out isDirty));
|
||
|
}
|
||
|
return isDirty;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// If flavored, ask the flavor if it wants to provide an XML fragment
|
||
|
/// </summary>
|
||
|
/// <param name="flavor">Guid of the flavor</param>
|
||
|
/// <param name="storageType">Project file or user file</param>
|
||
|
/// <param name="fragment">Fragment that the flavor wants to save</param>
|
||
|
/// <returns>HRESULT</returns>
|
||
|
public int GetXmlFragment(Guid flavor, _PersistStorageType storageType, out string fragment)
|
||
|
{
|
||
|
fragment = null;
|
||
|
int hr = VSConstants.S_OK;
|
||
|
if (this.flavoredCfg != null && this.flavoredCfg is IPersistXMLFragment)
|
||
|
{
|
||
|
Guid flavorGuid = flavor;
|
||
|
hr = ((IPersistXMLFragment)this.flavoredCfg).Save(ref flavorGuid, (uint)storageType, out fragment, 1);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsSpecifyPropertyPages
|
||
|
|
||
|
/// <summary>
|
||
|
/// Retrieves the configuration dependent property pages.
|
||
|
/// </summary>
|
||
|
/// <param name="pages">The pages to return.</param>
|
||
|
public virtual void GetPages(CAUUID[] pages)
|
||
|
{
|
||
|
// We do not check whether the supportsProjectDesigner is set to true on the ProjectNode.
|
||
|
// We rely that the caller knows what to call on us.
|
||
|
VsUtilities.ArgumentNotNull("pages", pages);
|
||
|
|
||
|
|
||
|
if (pages.Length == 0)
|
||
|
{
|
||
|
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "pages");
|
||
|
}
|
||
|
|
||
|
// Retrive the list of guids from hierarchy properties.
|
||
|
// Because a flavor could modify that list we must make sure we are calling the outer most implementation of IVsHierarchy
|
||
|
string guidsList = String.Empty;
|
||
|
IVsHierarchy hierarchy = project.GetOuterInterface<IVsHierarchy>();
|
||
|
object variant = null;
|
||
|
ErrorHandler.ThrowOnFailure(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList, out variant), new int[] { VSConstants.DISP_E_MEMBERNOTFOUND, VSConstants.E_NOTIMPL });
|
||
|
guidsList = (string)variant;
|
||
|
|
||
|
Guid[] guids = VsUtilities.GuidsArrayFromSemicolonDelimitedStringOfGuids(guidsList);
|
||
|
if (guids == null || guids.Length == 0)
|
||
|
{
|
||
|
pages[0] = new CAUUID();
|
||
|
pages[0].cElems = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pages[0] = PackageUtilities.CreateCAUUIDFromGuidArray(guids);
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsSpecifyProjectDesignerPages
|
||
|
/// <summary>
|
||
|
/// Implementation of the IVsSpecifyProjectDesignerPages. It will retun the pages that are configuration dependent.
|
||
|
/// </summary>
|
||
|
/// <param name="pages">The pages to return.</param>
|
||
|
/// <returns>VSConstants.S_OK</returns>
|
||
|
public virtual int GetProjectDesignerPages(CAUUID[] pages)
|
||
|
{
|
||
|
// We do not check whether the supportsProjectDesigner is set to true on the ProjectNode.
|
||
|
// We rely that the caller knows what to call on us.
|
||
|
VsUtilities.ArgumentNotNull("pages", pages);
|
||
|
|
||
|
|
||
|
if (pages.Length == 0)
|
||
|
{
|
||
|
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "pages");
|
||
|
}
|
||
|
|
||
|
// Retrive the list of guids from hierarchy properties.
|
||
|
// Because a flavor could modify that list we must make sure we are calling the outer most implementation of IVsHierarchy
|
||
|
string guidsList = String.Empty;
|
||
|
IVsHierarchy hierarchy = project.GetOuterInterface<IVsHierarchy>();
|
||
|
object variant = null;
|
||
|
ErrorHandler.ThrowOnFailure(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID2.VSHPROPID_CfgPropertyPagesCLSIDList, out variant), new int[] { VSConstants.DISP_E_MEMBERNOTFOUND, VSConstants.E_NOTIMPL });
|
||
|
guidsList = (string)variant;
|
||
|
|
||
|
Guid[] guids = VsUtilities.GuidsArrayFromSemicolonDelimitedStringOfGuids(guidsList);
|
||
|
if (guids == null || guids.Length == 0)
|
||
|
{
|
||
|
pages[0] = new CAUUID();
|
||
|
pages[0].cElems = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pages[0] = PackageUtilities.CreateCAUUIDFromGuidArray(guids);
|
||
|
}
|
||
|
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsCfg methods
|
||
|
/// <summary>
|
||
|
/// The display name is a two part item
|
||
|
/// first part is the config name, 2nd part is the platform name
|
||
|
/// </summary>
|
||
|
public virtual int get_DisplayName(out string name)
|
||
|
{
|
||
|
name = DisplayName;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
private string DisplayName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.ConfigName + "|" + this.PlatformName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual int get_IsDebugOnly(out int fDebug)
|
||
|
{
|
||
|
fDebug = 0;
|
||
|
if (this.ConfigName == "Debug")
|
||
|
{
|
||
|
fDebug = 1;
|
||
|
}
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
public virtual int get_IsReleaseOnly(out int fRelease)
|
||
|
{
|
||
|
fRelease = 0;
|
||
|
if (this.ConfigName == "Release")
|
||
|
{
|
||
|
fRelease = 1;
|
||
|
}
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsProjectCfg methods
|
||
|
public virtual int EnumOutputs(out IVsEnumOutputs eo)
|
||
|
{
|
||
|
eo = null;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
public virtual int get_BuildableProjectCfg(out IVsBuildableProjectCfg pb)
|
||
|
{
|
||
|
if (buildableCfg == null) {
|
||
|
buildableCfg = new BuildableProjectConfig(this);
|
||
|
}
|
||
|
pb = buildableCfg;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
public virtual int get_CanonicalName(out string name)
|
||
|
{
|
||
|
name = ConfigName;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_IsPackaged(out int pkgd)
|
||
|
{
|
||
|
pkgd = 0;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_IsSpecifyingOutputSupported(out int f)
|
||
|
{
|
||
|
f = 1;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_Platform(out Guid platform)
|
||
|
{
|
||
|
platform = Guid.Empty;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
public virtual int get_ProjectCfgProvider(out IVsProjectCfgProvider p)
|
||
|
{
|
||
|
p = null;
|
||
|
IVsCfgProvider cfgProvider = null;
|
||
|
this.project.GetCfgProvider(out cfgProvider);
|
||
|
if (cfgProvider != null)
|
||
|
{
|
||
|
p = cfgProvider as IVsProjectCfgProvider;
|
||
|
}
|
||
|
|
||
|
return (null == p) ? VSConstants.E_NOTIMPL : VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_RootURL(out string root)
|
||
|
{
|
||
|
root = null;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_TargetCodePage(out uint target)
|
||
|
{
|
||
|
target = (uint)System.Text.Encoding.Default.CodePage;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_UpdateSequenceNumber(ULARGE_INTEGER[] li)
|
||
|
{
|
||
|
VsUtilities.ArgumentNotNull("li", li);
|
||
|
|
||
|
li[0] = new ULARGE_INTEGER();
|
||
|
li[0].QuadPart = 0;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int OpenOutput(string name, out IVsOutput output)
|
||
|
{
|
||
|
output = null;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsProjectCfg2 Members
|
||
|
|
||
|
public virtual int OpenOutputGroup(string szCanonicalName, out IVsOutputGroup ppIVsOutputGroup)
|
||
|
{
|
||
|
ppIVsOutputGroup = null;
|
||
|
// Search through our list of groups to find the one they are looking forgroupName
|
||
|
foreach (OutputGroup group in OutputGroups)
|
||
|
{
|
||
|
string groupName;
|
||
|
group.get_CanonicalName(out groupName);
|
||
|
if (String.Compare(groupName, szCanonicalName, StringComparison.OrdinalIgnoreCase) == 0)
|
||
|
{
|
||
|
ppIVsOutputGroup = group;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return (ppIVsOutputGroup != null) ? VSConstants.S_OK : VSConstants.E_FAIL;
|
||
|
}
|
||
|
|
||
|
public virtual int OutputsRequireAppRoot(out int pfRequiresAppRoot)
|
||
|
{
|
||
|
pfRequiresAppRoot = 0;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
public virtual int get_CfgType(ref Guid iidCfg, out IntPtr ppCfg)
|
||
|
{
|
||
|
// Delegate to the flavored configuration (to enable a flavor to take control)
|
||
|
// Since we can be asked for Configuration we don't support, avoid throwing and return the HRESULT directly
|
||
|
int hr = flavoredCfg.get_CfgType(ref iidCfg, out ppCfg);
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
public virtual int get_IsPrivate(out int pfPrivate)
|
||
|
{
|
||
|
pfPrivate = 0;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_OutputGroups(uint celt, IVsOutputGroup[] rgpcfg, uint[] pcActual)
|
||
|
{
|
||
|
// Are they only asking for the number of groups?
|
||
|
if (celt == 0)
|
||
|
{
|
||
|
if ((null == pcActual) || (0 == pcActual.Length))
|
||
|
{
|
||
|
throw new ArgumentNullException("pcActual");
|
||
|
}
|
||
|
pcActual[0] = (uint)OutputGroups.Count;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
// Check that the array of output groups is not null
|
||
|
if ((null == rgpcfg) || (rgpcfg.Length == 0))
|
||
|
{
|
||
|
throw new ArgumentNullException("rgpcfg");
|
||
|
}
|
||
|
|
||
|
// Fill the array with our output groups
|
||
|
uint count = 0;
|
||
|
foreach (OutputGroup group in OutputGroups)
|
||
|
{
|
||
|
if (rgpcfg.Length > count && celt > count && group != null)
|
||
|
{
|
||
|
rgpcfg[count] = group;
|
||
|
++count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pcActual != null && pcActual.Length > 0)
|
||
|
pcActual[0] = count;
|
||
|
|
||
|
// If the number asked for does not match the number returned, return S_FALSE
|
||
|
return (count == celt) ? VSConstants.S_OK : VSConstants.S_FALSE;
|
||
|
}
|
||
|
|
||
|
public virtual int get_VirtualRoot(out string pbstrVRoot)
|
||
|
{
|
||
|
pbstrVRoot = null;
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region IVsDebuggableProjectCfg methods
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called by the vs shell to start debugging (managed or unmanaged).
|
||
|
/// Override this method to support other debug engines.
|
||
|
/// </summary>
|
||
|
/// <param name="grfLaunch">A flag that determines the conditions under which to start the debugger. For valid grfLaunch values, see __VSDBGLAUNCHFLAGS</param>
|
||
|
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code</returns>
|
||
|
public abstract int DebugLaunch(uint grfLaunch);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether the debugger can be launched, given the state of the launch flags.
|
||
|
/// </summary>
|
||
|
/// <param name="flags">Flags that determine the conditions under which to launch the debugger.
|
||
|
/// For valid grfLaunch values, see __VSDBGLAUNCHFLAGS or __VSDBGLAUNCHFLAGS2.</param>
|
||
|
/// <param name="fCanLaunch">true if the debugger can be launched, otherwise false</param>
|
||
|
/// <returns>S_OK if the method succeeds, otherwise an error code</returns>
|
||
|
public virtual int QueryDebugLaunch(uint flags, out int fCanLaunch)
|
||
|
{
|
||
|
string assembly = this.project.GetAssemblyName(this.ConfigName, this.PlatformName);
|
||
|
fCanLaunch = (assembly != null && assembly.ToUpperInvariant().EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) ? 1 : 0;
|
||
|
if (fCanLaunch == 0)
|
||
|
{
|
||
|
string property = GetConfigurationProperty("StartProgram", true);
|
||
|
fCanLaunch = (property != null && property.Length > 0) ? 1 : 0;
|
||
|
}
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region IVsCfgBrowseObject
|
||
|
|
||
|
/// <summary>
|
||
|
/// Maps back to the configuration corresponding to the browse object.
|
||
|
/// </summary>
|
||
|
/// <param name="cfg">The IVsCfg object represented by the browse object</param>
|
||
|
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
|
||
|
public virtual int GetCfg(out IVsCfg cfg)
|
||
|
{
|
||
|
cfg = this;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Maps back to the hierarchy or project item object corresponding to the browse object.
|
||
|
/// </summary>
|
||
|
/// <param name="hier">Reference to the hierarchy object.</param>
|
||
|
/// <param name="itemid">Reference to the project item.</param>
|
||
|
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
|
||
|
public virtual int GetProjectItem(out IVsHierarchy hier, out uint itemid)
|
||
|
{
|
||
|
VsUtilities.CheckNotNull(this.project);
|
||
|
VsUtilities.CheckNotNull(this.project.NodeProperties);
|
||
|
|
||
|
return this.project.NodeProperties.GetProjectItem(out hier, out itemid);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region helper methods
|
||
|
|
||
|
private MSBuildExecution.ProjectPropertyInstance GetMsBuildProperty(string propertyName, bool resetCache)
|
||
|
{
|
||
|
if (resetCache || this.currentConfig == null)
|
||
|
{
|
||
|
// Get properties for current configuration from project file and cache it
|
||
|
this.project.SetConfiguration(this.ConfigName, this.PlatformName);
|
||
|
this.project.BuildProject.ReevaluateIfNecessary();
|
||
|
// Create a snapshot of the evaluated project in its current state
|
||
|
this.currentConfig = this.project.BuildProject.CreateProjectInstance();
|
||
|
|
||
|
// Restore configuration
|
||
|
project.SetCurrentConfiguration();
|
||
|
}
|
||
|
|
||
|
if (this.currentConfig == null)
|
||
|
throw new Exception("Failed to retrieve properties");
|
||
|
|
||
|
// return property asked for
|
||
|
return this.currentConfig.GetProperty(propertyName);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region IVsProjectFlavorCfg Members
|
||
|
/// <summary>
|
||
|
/// This is called to let the flavored config let go
|
||
|
/// of any reference it may still be holding to the base config
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
int IVsProjectFlavorCfg.Close()
|
||
|
{
|
||
|
// This is used to release the reference the flavored config is holding
|
||
|
// on the base config, but in our scenario these 2 are the same object
|
||
|
// so we have nothing to do here.
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Actual implementation of get_CfgType.
|
||
|
/// When not flavored or when the flavor delegate to use
|
||
|
/// we end up creating the requested config if we support it.
|
||
|
/// </summary>
|
||
|
/// <param name="iidCfg">IID representing the type of config object we should create</param>
|
||
|
/// <param name="ppCfg">Config object that the method created</param>
|
||
|
/// <returns>HRESULT</returns>
|
||
|
int IVsProjectFlavorCfg.get_CfgType(ref Guid iidCfg, out IntPtr ppCfg)
|
||
|
{
|
||
|
ppCfg = IntPtr.Zero;
|
||
|
|
||
|
// See if this is an interface we support
|
||
|
if (iidCfg == typeof(IVsDebuggableProjectCfg).GUID)
|
||
|
{
|
||
|
ppCfg = Marshal.GetComInterfaceForObject(this, typeof(IVsDebuggableProjectCfg));
|
||
|
} else if (iidCfg == typeof(IVsBuildableProjectCfg).GUID) {
|
||
|
IVsBuildableProjectCfg buildableConfig;
|
||
|
this.get_BuildableProjectCfg(out buildableConfig);
|
||
|
ppCfg = Marshal.GetComInterfaceForObject(buildableConfig, typeof(IVsBuildableProjectCfg));
|
||
|
}
|
||
|
|
||
|
// If not supported
|
||
|
if (ppCfg == IntPtr.Zero)
|
||
|
return VSConstants.E_NOINTERFACE;
|
||
|
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
|
||
|
[ComVisible(true)]
|
||
|
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Buildable")]
|
||
|
public class BuildableProjectConfig : IVsBuildableProjectCfg {
|
||
|
#region fields
|
||
|
Config config = null;
|
||
|
EventSinkCollection callbacks = new EventSinkCollection();
|
||
|
uint vsopts = 0;
|
||
|
#endregion
|
||
|
|
||
|
#region ctors
|
||
|
public BuildableProjectConfig(Config config) {
|
||
|
this.config = config;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region IVsBuildableProjectCfg methods
|
||
|
|
||
|
public virtual int AdviseBuildStatusCallback(IVsBuildStatusCallback callback, out uint cookie) {
|
||
|
cookie = callbacks.Add(callback);
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int get_ProjectCfg(out IVsProjectCfg p) {
|
||
|
p = config;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int QueryStartBuild(uint options, int[] supported, int[] ready) {
|
||
|
if (supported != null && supported.Length > 0)
|
||
|
supported[0] = 1;
|
||
|
if (ready != null && ready.Length > 0)
|
||
|
ready[0] = (this.config.ProjectMgr.BuildInProgress) ? 0 : 1;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int QueryStartClean(uint options, int[] supported, int[] ready) {
|
||
|
//config.PrepareBuild(false);
|
||
|
if (supported != null && supported.Length > 0)
|
||
|
supported[0] = 1;
|
||
|
if (ready != null && ready.Length > 0)
|
||
|
ready[0] = (this.config.ProjectMgr.BuildInProgress) ? 0 : 1;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int QueryStartUpToDateCheck(uint options, int[] supported, int[] ready) {
|
||
|
//config.PrepareBuild(false);
|
||
|
if (supported != null && supported.Length > 0)
|
||
|
supported[0] = 0; // TODO:
|
||
|
if (ready != null && ready.Length > 0)
|
||
|
ready[0] = (this.config.ProjectMgr.BuildInProgress) ? 0 : 1;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int QueryStatus(out int done) {
|
||
|
done = (this.config.ProjectMgr.BuildInProgress) ? 0 : 1;
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int StartBuild(IVsOutputWindowPane pane, uint options) {
|
||
|
this.vsopts = options;
|
||
|
|
||
|
config.PrepareBuild(options, false);
|
||
|
|
||
|
// Current version of MSBuild wish to be called in an STA
|
||
|
uint flags = VSConstants.VS_BUILDABLEPROJECTCFGOPTS_REBUILD;
|
||
|
|
||
|
// If we are not asked for a rebuild, then we build the default target (by passing null)
|
||
|
this.Build(options, pane, ((options & flags) != 0) ? MsBuildTarget.Rebuild : null, null);
|
||
|
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int StartClean(IVsOutputWindowPane pane, uint options) {
|
||
|
this.vsopts = options;
|
||
|
|
||
|
config.PrepareBuild(options, true);
|
||
|
// Current version of MSBuild wish to be called in an STA
|
||
|
this.Build(options, pane, MsBuildTarget.Clean, null);
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int StartUpToDateCheck(IVsOutputWindowPane pane, uint options) {
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
public virtual int Stop(int fsync)
|
||
|
{
|
||
|
return this.config.ProjectMgr.StopBuild(this.vsopts, fsync != 0);
|
||
|
}
|
||
|
|
||
|
public virtual int UnadviseBuildStatusCallback(uint cookie) {
|
||
|
callbacks.RemoveAt(cookie);
|
||
|
return VSConstants.S_OK;
|
||
|
}
|
||
|
|
||
|
public virtual int Wait(uint ms, int fTickWhenMessageQNotEmpty) {
|
||
|
return VSConstants.E_NOTIMPL;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region helpers
|
||
|
|
||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||
|
private bool NotifyBuildBegin() {
|
||
|
int shouldContinue = 1;
|
||
|
foreach (IVsBuildStatusCallback cb in callbacks) {
|
||
|
try {
|
||
|
ErrorHandler.ThrowOnFailure(cb.BuildBegin(ref shouldContinue));
|
||
|
if (shouldContinue == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
} catch (Exception e) {
|
||
|
// If those who ask for status have bugs in their code it should not prevent the build/notification from happening
|
||
|
Debug.Fail(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.BuildEventError, CultureInfo.CurrentUICulture), e.Message));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||
|
private void NotifyBuildEnd(MSBuildResult result, string buildTarget) {
|
||
|
int success = ((result == MSBuildResult.Successful) ? 1 : 0);
|
||
|
|
||
|
foreach (IVsBuildStatusCallback cb in callbacks) {
|
||
|
OnNotifyBuildEnd(result, buildTarget, cb, success);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void OnNotifyBuildEnd(MSBuildResult result, string buildTarget, IVsBuildStatusCallback cb, int success)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
ErrorHandler.ThrowOnFailure(cb.BuildEnd(success));
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
// If those who ask for status have bugs in their code it should not prevent the build/notification from happening
|
||
|
Debug.Fail(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.BuildEventError, CultureInfo.CurrentUICulture), e.Message));
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
// We want to refresh the references if we are building with the Build or Rebuild target or if the project was opened for browsing only.
|
||
|
bool shouldRepaintReferences = (buildTarget == null || buildTarget == MsBuildTarget.Build || buildTarget == MsBuildTarget.Rebuild);
|
||
|
|
||
|
// Now repaint references if that is needed.
|
||
|
// We hardly rely here on the fact the ResolveAssemblyReferences target has been run as part of the build.
|
||
|
// One scenario to think at is when an assembly reference is renamed on disk thus becomming unresolvable,
|
||
|
// but msbuild can actually resolve it.
|
||
|
// Another one if the project was opened only for browsing and now the user chooses to build or rebuild.
|
||
|
if (shouldRepaintReferences && (result == MSBuildResult.Successful))
|
||
|
{
|
||
|
this.RefreshReferences();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Build(uint options, IVsOutputWindowPane output, string target, IEnumerable<string> files) {
|
||
|
if (!this.NotifyBuildBegin()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
config.ProjectMgr.BuildAsync(options, this.config.ConfigName, this.config.PlatformName, output, target, files, (result, buildTarget) => this.NotifyBuildEnd(result, buildTarget));
|
||
|
} catch (Exception e) {
|
||
|
Trace.WriteLine("Exception : " + e.Message);
|
||
|
ErrorHandler.ThrowOnFailure(output.OutputStringThreadSafe("Unhandled Exception:" + e.Message + "\n"));
|
||
|
this.NotifyBuildEnd(MSBuildResult.Failed, target);
|
||
|
throw;
|
||
|
} finally {
|
||
|
ErrorHandler.ThrowOnFailure(output.FlushToTaskList());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Refreshes references and redraws them correctly.
|
||
|
/// </summary>
|
||
|
protected virtual void RefreshReferences() {
|
||
|
// Refresh the reference container node for assemblies that could be resolved.
|
||
|
IReferenceContainer referenceContainer = this.config.ProjectMgr.GetReferenceContainer();
|
||
|
if (referenceContainer != null) {
|
||
|
foreach (ReferenceNode referenceNode in referenceContainer.EnumReferences()) {
|
||
|
referenceNode.RefreshReference();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
}
|
||
|
|
||
|
}
|