Konstantin Koch 8287c54432 Included the Visual Studio extension and made the necessary changes to make it run.
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.
2015-04-08 14:50:03 +02:00

614 lines
23 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.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Windows.Forms.Design;
using System.Windows.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
namespace Microsoft.VisualStudio.Project
{
/// <summary>
/// This class implements an MSBuild logger that output events to VS outputwindow and tasklist.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "IDE")]
public class IDEBuildLogger : Logger
{
#region fields
// TODO: Remove these constants when we have a version that suppoerts getting the verbosity using automation.
private string buildVerbosityRegistryRoot = @"Software\Microsoft\VisualStudio\10.0";
private const string buildVerbosityRegistrySubKey = @"General";
private const string buildVerbosityRegistryKey = "MSBuildLoggerVerbosity";
private int currentIndent;
private IVsOutputWindowPane outputWindowPane;
private string errorString = SR.GetString(SR.Error, CultureInfo.CurrentUICulture);
private string warningString = SR.GetString(SR.Warning, CultureInfo.CurrentUICulture);
private TaskProvider taskProvider;
private IVsHierarchy hierarchy;
private IServiceProvider serviceProvider;
private Dispatcher dispatcher;
private bool haveCachedVerbosity = false;
// Queues to manage Tasks and Error output plus message logging
private ConcurrentQueue<Func<ErrorTask>> taskQueue;
private ConcurrentQueue<string> outputQueue;
#endregion
#region properties
public IServiceProvider ServiceProvider
{
get { return this.serviceProvider; }
}
public string WarningString
{
get { return this.warningString; }
set { this.warningString = value; }
}
public string ErrorString
{
get { return this.errorString; }
set { this.errorString = value; }
}
/// <summary>
/// When the build is not a "design time" (background or secondary) build this is True
/// </summary>
/// <remarks>
/// The only known way to detect an interactive build is to check this.outputWindowPane for null.
/// </remarks>
protected bool InteractiveBuild
{
get { return this.outputWindowPane != null; }
}
/// <summary>
/// When building from within VS, setting this will
/// enable the logger to retrive the verbosity from
/// the correct registry hive.
/// </summary>
public string BuildVerbosityRegistryRoot
{
get { return this.buildVerbosityRegistryRoot; }
set
{
this.buildVerbosityRegistryRoot = value;
}
}
/// <summary>
/// Set to null to avoid writing to the output window
/// </summary>
public IVsOutputWindowPane OutputWindowPane
{
get { return this.outputWindowPane; }
set { this.outputWindowPane = value; }
}
#endregion
#region ctors
/// <summary>
/// Constructor. Inititialize member data.
/// </summary>
public IDEBuildLogger(IVsOutputWindowPane output, TaskProvider taskProvider, IVsHierarchy hierarchy)
{
if (taskProvider == null)
throw new ArgumentNullException("taskProvider");
if (hierarchy == null)
throw new ArgumentNullException("hierarchy");
Trace.WriteLineIf(Thread.CurrentThread.GetApartmentState() != ApartmentState.STA, "WARNING: IDEBuildLogger constructor running on the wrong thread.");
IOleServiceProvider site;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hierarchy.GetSite(out site));
this.taskProvider = taskProvider;
this.outputWindowPane = output;
this.hierarchy = hierarchy;
this.serviceProvider = new ServiceProvider(site);
this.dispatcher = Dispatcher.CurrentDispatcher;
}
#endregion
#region overridden methods
/// <summary>
/// Overridden from the Logger class.
/// </summary>
public override void Initialize(IEventSource eventSource)
{
if (null == eventSource)
{
throw new ArgumentNullException("eventSource");
}
this.taskQueue = new ConcurrentQueue<Func<ErrorTask>>();
this.outputQueue = new ConcurrentQueue<string>();
eventSource.BuildStarted += new BuildStartedEventHandler(BuildStartedHandler);
eventSource.BuildFinished += new BuildFinishedEventHandler(BuildFinishedHandler);
eventSource.ProjectStarted += new ProjectStartedEventHandler(ProjectStartedHandler);
eventSource.ProjectFinished += new ProjectFinishedEventHandler(ProjectFinishedHandler);
eventSource.TargetStarted += new TargetStartedEventHandler(TargetStartedHandler);
eventSource.TargetFinished += new TargetFinishedEventHandler(TargetFinishedHandler);
eventSource.TaskStarted += new TaskStartedEventHandler(TaskStartedHandler);
eventSource.TaskFinished += new TaskFinishedEventHandler(TaskFinishedHandler);
eventSource.CustomEventRaised += new CustomBuildEventHandler(CustomHandler);
eventSource.ErrorRaised += new BuildErrorEventHandler(ErrorHandler);
eventSource.WarningRaised += new BuildWarningEventHandler(WarningHandler);
eventSource.MessageRaised += new BuildMessageEventHandler(MessageHandler);
}
#endregion
#region event delegates
/// <summary>
/// This is the delegate for BuildStartedHandler events.
/// </summary>
protected virtual void BuildStartedHandler(object sender, BuildStartedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
ClearCachedVerbosity();
ClearQueuedOutput();
ClearQueuedTasks();
QueueOutputEvent(MessageImportance.Low, buildEvent);
}
/// <summary>
/// This is the delegate for BuildFinishedHandler events.
/// </summary>
/// <param name="sender"></param>
/// <param name="buildEvent"></param>
protected virtual void BuildFinishedHandler(object sender, BuildFinishedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
MessageImportance importance = buildEvent.Succeeded ? MessageImportance.Low : MessageImportance.High;
QueueOutputText(importance, Environment.NewLine);
QueueOutputEvent(importance, buildEvent);
// flush output and error queues
ReportQueuedOutput();
ReportQueuedTasks();
}
/// <summary>
/// This is the delegate for ProjectStartedHandler events.
/// </summary>
protected virtual void ProjectStartedHandler(object sender, ProjectStartedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(MessageImportance.Low, buildEvent);
}
/// <summary>
/// This is the delegate for ProjectFinishedHandler events.
/// </summary>
protected virtual void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(buildEvent.Succeeded ? MessageImportance.Low : MessageImportance.High, buildEvent);
}
/// <summary>
/// This is the delegate for TargetStartedHandler events.
/// </summary>
protected virtual void TargetStartedHandler(object sender, TargetStartedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(MessageImportance.Low, buildEvent);
IndentOutput();
}
/// <summary>
/// This is the delegate for TargetFinishedHandler events.
/// </summary>
protected virtual void TargetFinishedHandler(object sender, TargetFinishedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
UnindentOutput();
QueueOutputEvent(MessageImportance.Low, buildEvent);
}
/// <summary>
/// This is the delegate for TaskStartedHandler events.
/// </summary>
protected virtual void TaskStartedHandler(object sender, TaskStartedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(MessageImportance.Low, buildEvent);
IndentOutput();
}
/// <summary>
/// This is the delegate for TaskFinishedHandler events.
/// </summary>
protected virtual void TaskFinishedHandler(object sender, TaskFinishedEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
UnindentOutput();
QueueOutputEvent(MessageImportance.Low, buildEvent);
}
/// <summary>
/// This is the delegate for CustomHandler events.
/// </summary>
/// <param name="sender"></param>
/// <param name="buildEvent"></param>
protected virtual void CustomHandler(object sender, CustomBuildEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(MessageImportance.High, buildEvent);
}
/// <summary>
/// This is the delegate for error events.
/// </summary>
protected virtual void ErrorHandler(object sender, BuildErrorEventArgs errorEvent)
{
// NOTE: This may run on a background thread!
QueueOutputText(GetFormattedErrorMessage(errorEvent.File, errorEvent.LineNumber, errorEvent.ColumnNumber, false, errorEvent.Code, errorEvent.Message));
QueueTaskEvent(errorEvent);
}
/// <summary>
/// This is the delegate for warning events.
/// </summary>
protected virtual void WarningHandler(object sender, BuildWarningEventArgs warningEvent)
{
// NOTE: This may run on a background thread!
QueueOutputText(MessageImportance.High, GetFormattedErrorMessage(warningEvent.File, warningEvent.LineNumber, warningEvent.ColumnNumber, true, warningEvent.Code, warningEvent.Message));
QueueTaskEvent(warningEvent);
}
/// <summary>
/// This is the delegate for Message event types
/// </summary>
protected virtual void MessageHandler(object sender, BuildMessageEventArgs messageEvent)
{
// NOTE: This may run on a background thread!
QueueOutputEvent(messageEvent.Importance, messageEvent);
}
#endregion
#region output queue
protected void QueueOutputEvent(MessageImportance importance, BuildEventArgs buildEvent)
{
// NOTE: This may run on a background thread!
if (LogAtImportance(importance) && !string.IsNullOrEmpty(buildEvent.Message))
{
StringBuilder message = new StringBuilder(this.currentIndent + buildEvent.Message.Length);
if (this.currentIndent > 0)
{
message.Append('\t', this.currentIndent);
}
message.AppendLine(buildEvent.Message);
QueueOutputText(message.ToString());
}
}
protected void QueueOutputText(MessageImportance importance, string text)
{
// NOTE: This may run on a background thread!
if (LogAtImportance(importance))
{
QueueOutputText(text);
}
}
protected void QueueOutputText(string text)
{
// NOTE: This may run on a background thread!
if (this.OutputWindowPane != null)
{
// Enqueue the output text
this.outputQueue.Enqueue(text);
// We want to interactively report the output. But we dont want to dispatch
// more than one at a time, otherwise we might overflow the main thread's
// message queue. So, we only report the output if the queue was empty.
if (this.outputQueue.Count == 1)
{
ReportQueuedOutput();
}
}
}
private void IndentOutput()
{
// NOTE: This may run on a background thread!
this.currentIndent++;
}
private void UnindentOutput()
{
// NOTE: This may run on a background thread!
this.currentIndent--;
}
private void ReportQueuedOutput()
{
// NOTE: This may run on a background thread!
// We need to output this on the main thread. We must use BeginInvoke because the main thread may not be pumping events yet.
BeginInvokeWithErrorMessage(this.serviceProvider, this.dispatcher, () => {
if (this.OutputWindowPane != null)
{
string outputString;
while (this.outputQueue.TryDequeue(out outputString))
{
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(this.OutputWindowPane.OutputString(outputString));
}
}
});
}
private void ClearQueuedOutput()
{
// NOTE: This may run on a background thread!
this.outputQueue = new ConcurrentQueue<string>();
}
#endregion output queue
#region task queue
protected void QueueTaskEvent(BuildEventArgs errorEvent)
{
this.taskQueue.Enqueue(() => {
ErrorTask task = new ErrorTask();
if (errorEvent is BuildErrorEventArgs)
{
BuildErrorEventArgs errorArgs = (BuildErrorEventArgs)errorEvent;
task.Document = errorArgs.File;
task.ErrorCategory = TaskErrorCategory.Error;
task.Line = errorArgs.LineNumber - 1; // The task list does +1 before showing this number.
task.Column = errorArgs.ColumnNumber;
task.Priority = TaskPriority.High;
}
else if (errorEvent is BuildWarningEventArgs)
{
BuildWarningEventArgs warningArgs = (BuildWarningEventArgs)errorEvent;
task.Document = warningArgs.File;
task.ErrorCategory = TaskErrorCategory.Warning;
task.Line = warningArgs.LineNumber - 1; // The task list does +1 before showing this number.
task.Column = warningArgs.ColumnNumber;
task.Priority = TaskPriority.Normal;
}
task.Text = errorEvent.Message;
task.Category = TaskCategory.BuildCompile;
task.HierarchyItem = hierarchy;
return task;
});
// NOTE: Unlike output we dont want to interactively report the tasks. So we never queue
// call ReportQueuedTasks here. We do this when the build finishes.
}
private void ReportQueuedTasks()
{
// NOTE: This may run on a background thread!
// We need to output this on the main thread. We must use BeginInvoke because the main thread may not be pumping events yet.
BeginInvokeWithErrorMessage(this.serviceProvider, this.dispatcher, () => {
this.taskProvider.SuspendRefresh();
try
{
Func<ErrorTask> taskFunc;
while (this.taskQueue.TryDequeue(out taskFunc))
{
// Create the error task
ErrorTask task = taskFunc();
// Log the task
this.taskProvider.Tasks.Add(task);
}
}
finally
{
this.taskProvider.ResumeRefresh();
}
});
}
private void ClearQueuedTasks()
{
// NOTE: This may run on a background thread!
this.taskQueue = new ConcurrentQueue<Func<ErrorTask>>();
if (this.InteractiveBuild)
{
// We need to clear this on the main thread. We must use BeginInvoke because the main thread may not be pumping events yet.
BeginInvokeWithErrorMessage(this.serviceProvider, this.dispatcher, () => {
this.taskProvider.Tasks.Clear();
});
}
}
#endregion task queue
#region helpers
/// <summary>
/// This method takes a MessageImportance and returns true if messages
/// at importance i should be loggeed. Otherwise return false.
/// </summary>
private bool LogAtImportance(MessageImportance importance)
{
// If importance is too low for current settings, ignore the event
bool logIt = false;
this.SetVerbosity();
switch (this.Verbosity)
{
case LoggerVerbosity.Quiet:
logIt = false;
break;
case LoggerVerbosity.Minimal:
logIt = (importance == MessageImportance.High);
break;
case LoggerVerbosity.Normal:
// Falling through...
case LoggerVerbosity.Detailed:
logIt = (importance != MessageImportance.Low);
break;
case LoggerVerbosity.Diagnostic:
logIt = true;
break;
default:
Debug.Fail("Unknown Verbosity level. Ignoring will cause everything to be logged");
break;
}
return logIt;
}
/// <summary>
/// Format error messages for the task list
/// </summary>
private string GetFormattedErrorMessage(
string fileName,
int line,
int column,
bool isWarning,
string errorNumber,
string errorText)
{
string errorCode = isWarning ? this.WarningString : this.ErrorString;
StringBuilder message = new StringBuilder();
if (!string.IsNullOrEmpty(fileName))
{
message.AppendFormat(CultureInfo.CurrentCulture, "{0}({1},{2}):", fileName, line, column);
}
message.AppendFormat(CultureInfo.CurrentCulture, " {0} {1}: {2}", errorCode, errorNumber, errorText);
message.AppendLine();
return message.ToString();
}
/// <summary>
/// Sets the verbosity level.
/// </summary>
private void SetVerbosity()
{
// TODO: This should be replaced when we have a version that supports automation.
if (!this.haveCachedVerbosity)
{
string verbosityKey = String.Format(CultureInfo.InvariantCulture, @"{0}\{1}", BuildVerbosityRegistryRoot, buildVerbosityRegistrySubKey);
using (RegistryKey subKey = Registry.CurrentUser.OpenSubKey(verbosityKey))
{
if (subKey != null)
{
object valueAsObject = subKey.GetValue(buildVerbosityRegistryKey);
if (valueAsObject != null)
{
this.Verbosity = (LoggerVerbosity)((int)valueAsObject);
}
}
}
this.haveCachedVerbosity = true;
}
}
/// <summary>
/// Clear the cached verbosity, so that it will be re-evaluated from the build verbosity registry key.
/// </summary>
private void ClearCachedVerbosity()
{
this.haveCachedVerbosity = false;
}
#endregion helpers
#region exception handling helpers
/// <summary>
/// Call Dispatcher.BeginInvoke, showing an error message if there was a non-critical exception.
/// </summary>
/// <param name="serviceProvider">service provider</param>
/// <param name="dispatcher">dispatcher</param>
/// <param name="action">action to invoke</param>
private static void BeginInvokeWithErrorMessage(IServiceProvider serviceProvider, Dispatcher dispatcher, Action action)
{
dispatcher.BeginInvoke(new Action(() => CallWithErrorMessage(serviceProvider, action)));
}
/// <summary>
/// Show error message if exception is caught when invoking a method
/// </summary>
/// <param name="serviceProvider">service provider</param>
/// <param name="action">action to invoke</param>
private static void CallWithErrorMessage(IServiceProvider serviceProvider, Action action)
{
try
{
action();
}
catch (Exception ex)
{
if (Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex))
{
throw;
}
ShowErrorMessage(serviceProvider, ex);
}
}
/// <summary>
/// Show error window about the exception
/// </summary>
/// <param name="serviceProvider">service provider</param>
/// <param name="exception">exception</param>
private static void ShowErrorMessage(IServiceProvider serviceProvider, Exception exception)
{
IUIService UIservice = (IUIService)serviceProvider.GetService(typeof(IUIService));
if (UIservice != null && exception != null)
{
UIservice.ShowError(exception);
}
}
#endregion exception handling helpers
}
}