blob: 5262b03febdd940221219fa3849af120dd63a572 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
namespace NativeClientVSAddIn
{
using System;
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.VCProjectEngine;
using System.Collections.Generic;
using System.Diagnostics;
/// <summary>The object for implementing an Add-in.</summary>
/// <seealso class='IDTExtensibility2' />
public class Connect : IDTExtensibility2
{
/// <summary>
/// The main Visual Studio interface.
/// </summary>
private DTE2 dte_;
/// <summary>
/// Receives events related to starting/stopping debugging.
/// </summary>
private DebuggerEvents debuggerEvents_;
/// <summary>
/// Receives all generic events from Visual Studio.
/// </summary>
private CommandEvents commandEvents_;
/// <summary>
/// Holds methods related to debugging.
/// </summary>
private PluginDebuggerBase debugger_;
/// <summary>
/// The web server launched during debugging.
/// </summary>
private WebServer webServer_;
/// <summary>
/// Visual Studio output window pane that captures output from the web server, and displays
/// other web-server related information.
/// </summary>
private OutputWindowPane webServerOutputPane_;
/// <summary>
/// Implements the OnConnection method of the IDTExtensibility2 interface.
/// Receives notification that the Add-in is being loaded.
/// </summary>
/// <param name="application">Root object of the host application.</param>
/// <param name="connectMode">
/// Describes how the Add-in is being loaded (e.g. command line or UI). This is unused since
/// the add-in functions the same regardless of how it was loaded.
/// </param>
/// <param name="addInInst">Object representing this Add-in.</param>
/// <param name="custom">Unused, but could contain host specific data for the add-in.</param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(
object application,
ext_ConnectMode connectMode,
object addInInst,
ref Array custom)
{
dte_ = (DTE2)application;
debuggerEvents_ = dte_.Events.DebuggerEvents;
debuggerEvents_.OnEnterDesignMode += DebuggerOnEnterDesignMode;
debuggerEvents_.OnEnterRunMode += DebuggerOnEnterRunMode;
commandEvents_ = dte_.Events.CommandEvents;
commandEvents_.AfterExecute += CommandEventsAfterExecute;
try
{
webServerOutputPane_ = dte_.ToolWindows.OutputWindow.OutputWindowPanes.Item(
Strings.WebServerOutputWindowTitle);
}
catch (ArgumentException)
{
// This exception is expected if the window pane hasn't been created yet.
webServerOutputPane_ = dte_.ToolWindows.OutputWindow.OutputWindowPanes.Add(
Strings.WebServerOutputWindowTitle);
}
}
/// <summary>
/// Implements the OnDisconnection method of the IDTExtensibility2
/// interface. Receives notification that the Add-in is being unloaded.
/// </summary>
/// <param name='disconnectMode'>Describes how the Add-in is being unloaded.</param>
/// <param name='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
}
/// <summary>
/// Implements the OnAddInsUpdate method of the IDTExtensibility2 interface.
/// Receives notification when the collection of Add-ins has changed.
/// </summary>
/// <param name='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnAddInsUpdate(ref Array custom)
{
}
/// <summary>
/// Implements the OnStartupComplete method of the IDTExtensibility2 interface.
/// Receives notification that the host application has completed loading.
/// </summary>
/// <param name='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnStartupComplete(ref Array custom)
{
}
/// <summary>
/// Implements the OnBeginShutdown method of the IDTExtensibility2 interface.
/// Receives notification that the host application is being unloaded.
/// </summary>
/// <param name='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnBeginShutdown(ref Array custom)
{
}
/// <summary>
/// Receives notification after any generic VS command has executed.
/// Here we capture the SolutionPlatform event which indicates the solution platform has
/// changed. We use this event to trigger a modification of property settings since this
/// event happens immediately after the platforms are added. See PerformPropertyModifications()
/// for what sort of modifications we are doing.
/// </summary>
/// <param name="guid">Guid of the command grouping.</param>
/// <param name="id">ID of the command within its grouping.</param>
/// <param name="customIn">Command specific input.</param>
/// <param name="customOut">Command specific parameter.</param>
private void CommandEventsAfterExecute(string guid, int id, object customIn, object customOut)
{
const string VSStd2KCmdIDEnumGuid = "{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}";
if (guid.Equals(VSStd2KCmdIDEnumGuid, StringComparison.OrdinalIgnoreCase))
{
// If loading a NaCl or Pepper platform, perform property modifications.
if (id == (int)VSConstants.VSStd2KCmdID.SolutionPlatform)
{
string platform = customOut as string;
if (Strings.NaClPlatformName.Equals(platform) ||
Strings.PepperPlatformName.Equals(platform))
{
PerformPropertyModifications();
}
}
}
}
/// <summary>
/// Goes through all projects in the solution and updates their properties with necessary
/// modifications if they are NaCl or Pepper configurations. We add the version information
/// here so that the version is stored directly in the project file. The call to
/// PerformPropertyFixes() performs a work around on the property pages to force Visual Studio
/// to save some specific properties into the project file to get around issue 140162.
/// </summary>
private void PerformPropertyModifications()
{
string naclAddInVersion = GetAddInVersionFromDescription();
var configs = Utility.GetPlatformVCConfigurations(dte_, Strings.PepperPlatformName);
configs.AddRange(Utility.GetPlatformVCConfigurations(dte_, Strings.NaClPlatformName));
var properties = new PropertyManager();
foreach (VCConfiguration config in configs)
{
properties.SetTarget(config);
if (properties.NaClAddInVersion != naclAddInVersion)
{
Debug.WriteLine("Modifying Config: " + config.Name);
// Set the NaCl add-in version so that it is stored in the project file.
properties.SetProperty("ConfigurationGeneral", "NaClAddInVersion", naclAddInVersion);
// Expand the CHROME_PATH variable to its full path.
string expandedChrome = properties.GetProperty(
"WindowsLocalDebugger", "LocalDebuggerCommand");
properties.SetProperty("WindowsLocalDebugger", "LocalDebuggerCommand", expandedChrome);
// Change the library includes to have the appropriate extension.
string libs = properties.GetProperty("Link", "AdditionalDependencies");
if (properties.ProjectPlatform == PropertyManager.ProjectPlatformType.NaCl)
{
libs = libs.Replace(".lib", string.Empty);
}
else if (properties.ProjectPlatform == PropertyManager.ProjectPlatformType.Pepper)
{
string[] libsList = libs.Split(';');
libs = string.Empty;
foreach (string lib in libsList)
{
string baseLibName = lib.Replace(".lib", string.Empty);
if (!string.IsNullOrWhiteSpace(lib))
{
libs = string.Concat(libs, baseLibName, ".lib;");
}
}
}
properties.SetProperty("Link", "AdditionalDependencies", libs);
// Work around for issue 140162. Forces some properties to save to the project file.
PerformPropertyFixes(config);
}
}
}
/// <summary>
/// Takes a project configuration and sets values in the project file to work around some
/// problems in Visual Studio. This is a work around for issue 140162.
/// </summary>
/// <param name="config">A configuration that needs modification.</param>
private void PerformPropertyFixes(VCConfiguration config)
{
IVCRulePropertyStorage debugger = config.Rules.Item("WindowsLocalDebugger");
string arguments = debugger.GetUnevaluatedPropertyValue("LocalDebuggerCommandArguments");
debugger.SetPropertyValue("LocalDebuggerCommandArguments", arguments);
// NaCl Platform Specific:
if (config.Platform.Name == Strings.NaClPlatformName)
{
IVCRulePropertyStorage general = config.Rules.Item("ConfigurationGeneral");
string[] keys = {"VSNaClSDKRoot", "OutDir", "IntDir"};
Dictionary<string, string> values = new Dictionary<string, string>();
foreach (var key in keys)
{
values[key] = general.GetUnevaluatedPropertyValue(key);
general.DeleteProperty(key);
}
foreach (var key in keys)
{
general.SetPropertyValue(key, values[key]);
}
}
IVCRulePropertyStorage directories = config.Rules.Item("ConfigurationDirectories");
string includePath = directories.GetUnevaluatedPropertyValue("IncludePath");
string libraryPath = directories.GetUnevaluatedPropertyValue("LibraryPath");
directories.DeleteProperty("IncludePath");
directories.DeleteProperty("LibraryPath");
directories.SetPropertyValue("IncludePath", includePath);
directories.SetPropertyValue("LibraryPath", libraryPath);
// Pepper specific:
if (config.Platform.Name == Strings.PepperPlatformName)
{
string executablePath = directories.GetUnevaluatedPropertyValue("ExecutablePath");
directories.SetPropertyValue("ExecutablePath", executablePath);
}
// NaCl Platform Specific:
if (config.Platform.Name == Strings.NaClPlatformName)
{
IVCRulePropertyStorage general = config.Rules.Item("ConfigurationGeneral");
string outdir = general.GetUnevaluatedPropertyValue("OutDir");
string intdir = general.GetUnevaluatedPropertyValue("IntDir");
general.SetPropertyValue("OutDir", outdir);
general.SetPropertyValue("IntDir", intdir);
}
}
/// <summary>
/// During the build process we dynamically put the add-in version number into the add-in
/// description. This function extracts that version number.
/// </summary>
/// <returns>The add-in version number.</returns>
private string GetAddInVersionFromDescription()
{
string naclAddinVersion = "missing";
foreach (AddIn addin in dte_.AddIns)
{
if (addin.Name.Equals(Strings.AddInName))
{
string identifier = "Version: [";
int start = addin.Description.IndexOf(identifier) + identifier.Length;
int end = addin.Description.LastIndexOf(']');
if (start >= 0 && end >= 0)
{
naclAddinVersion = addin.Description.Substring(start, end - start);
break;
}
}
}
return naclAddinVersion;
}
/// <summary>
/// Called when Visual Studio ends a debugging session.
/// Shuts down the web server and debugger.
/// </summary>
/// <param name="reason">The parameter is not used.</param>
private void DebuggerOnEnterDesignMode(dbgEventReason reason)
{
if (debugger_ != null)
{
debugger_.Dispose();
debugger_ = null;
}
if (webServer_ != null)
{
webServer_.Dispose();
webServer_ = null;
}
}
/// <summary>
/// Called when Visual Studio starts a debugging session.
/// Here we kick off the debugger and web server if appropriate.
/// </summary>
/// <param name="reason">Indicates how we are entering run mode (breakpoint or launch).</param>
private void DebuggerOnEnterRunMode(dbgEventReason reason)
{
// If we are starting debugging (not re-entering from a breakpoint)
// then load project settings and start the debugger-helper.
if (reason == dbgEventReason.dbgEventReasonLaunchProgram)
{
PropertyManager properties = new PropertyManager();
properties.SetTargetToActive(dte_);
if (properties.ProjectPlatform == PropertyManager.ProjectPlatformType.NaCl)
{
debugger_ = new PluginDebuggerGDB(dte_, properties);
webServer_ = new WebServer(webServerOutputPane_, properties);
}
else if (properties.ProjectPlatform == PropertyManager.ProjectPlatformType.Pepper)
{
debugger_ = new PluginDebuggerVS(dte_, properties);
webServer_ = new WebServer(webServerOutputPane_, properties);
}
}
}
}
}