Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
IDEBuildLogger.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.CodeDom.Compiler;
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.InteropServices;
8 using System.Text;
9 using Microsoft.Build.Framework;
11 using Microsoft.VisualStudio;
12 using Microsoft.VisualStudio.Shell;
13 using Microsoft.VisualStudio.Shell.Interop;
14 using Microsoft.VisualStudio.TextManager.Interop;
15 using Microsoft.Win32;
16 using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
17 
18 namespace SiliconStudio.Paradox.VisualStudio.BuildEngine
19 {
20 
21  /// <summary>
22  /// This class implements an MSBuild logger that output events to VS outputwindow and tasklist.
23  /// </summary>
24  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable"), ComVisible(true)]
25  internal sealed class IDEBuildLogger : Logger
26  {
27  #region fields
28  // TODO: Remove these constants when we have a version that suppoerts getting the verbosity using automation.
29  private static RegistryKey userRegistryRoot;
30  private const string buildVerbosityRegistrySubKey = @"General";
31  private const string buildVerbosityRegistryKey = "MSBuildLoggerVerbosity";
32  // TODO: Re-enable this constants when we have a version that suppoerts getting the verbosity using automation.
33  //private const string EnvironmentCategory = "Environment";
34  //private const string ProjectsAndSolutionSubCategory = "ProjectsAndSolution";
35  //private const string BuildAndRunPage = "BuildAndRun";
36 
37  private int currentIndent;
38  private IVsOutputWindowPane outputWindowPane;
39  private string errorString = "error";
40  private string warningString = "warning";
41  private bool isLogTaskDone;
42  private TaskProvider taskProvider;
43  private IVsHierarchy hierarchy;
44  private IServiceProvider serviceProvider;
45 
46  #endregion
47 
48  #region properties
49  public string WarningString
50  {
51  get { return this.warningString; }
52  set { this.warningString = value; }
53  }
54  public string ErrorString
55  {
56  get { return this.errorString; }
57  set { this.errorString = value; }
58  }
59  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
60  public bool IsLogTaskDone
61  {
62  get { return this.isLogTaskDone; }
63  set { this.isLogTaskDone = value; }
64  }
65  /// <summary>
66  /// When building from within VS, setting this will
67  /// enable the logger to retrive the verbosity from
68  /// the correct registry hive.
69  /// </summary>
70  internal static RegistryKey UserRegistryRoot
71  {
72  get { return userRegistryRoot; }
73  set { userRegistryRoot = value; }
74  }
75  /// <summary>
76  /// Set to null to avoid writing to the output window
77  /// </summary>
78  internal IVsOutputWindowPane OutputWindowPane
79  {
80  get { return outputWindowPane; }
81  set { outputWindowPane = value; }
82  }
83  #endregion
84 
85  #region ctors
86  /// <summary>
87  /// Constructor. Inititialize member data.
88  /// </summary>
89  public IDEBuildLogger(IVsOutputWindowPane output, TaskProvider taskProvider, IVsHierarchy hierarchy)
90  {
91  if (taskProvider == null)
92  throw new ArgumentNullException("taskProvider");
93  if (hierarchy == null)
94  throw new ArgumentNullException("hierarchy");
95 
96  this.taskProvider = taskProvider;
97  this.outputWindowPane = output;
98  this.hierarchy = hierarchy;
100  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hierarchy.GetSite(out site));
101  this.serviceProvider = new ServiceProvider(site);
102  }
103  #endregion
104 
105  #region overridden methods
106  /// <summary>
107  /// Overridden from the Logger class.
108  /// </summary>
109  public override void Initialize(IEventSource eventSource)
110  {
111  if (null == eventSource)
112  {
113  throw new ArgumentNullException("eventSource");
114  }
115  eventSource.BuildStarted += new BuildStartedEventHandler(BuildStartedHandler);
116  eventSource.BuildFinished += new BuildFinishedEventHandler(BuildFinishedHandler);
117  eventSource.ProjectStarted += new ProjectStartedEventHandler(ProjectStartedHandler);
118  eventSource.ProjectFinished += new ProjectFinishedEventHandler(ProjectFinishedHandler);
119  eventSource.TargetStarted += new TargetStartedEventHandler(TargetStartedHandler);
120  eventSource.TargetFinished += new TargetFinishedEventHandler(TargetFinishedHandler);
121  eventSource.TaskStarted += new TaskStartedEventHandler(TaskStartedHandler);
122  eventSource.TaskFinished += new TaskFinishedEventHandler(TaskFinishedHandler);
123  eventSource.CustomEventRaised += new CustomBuildEventHandler(CustomHandler);
124  eventSource.ErrorRaised += new BuildErrorEventHandler(ErrorHandler);
125  eventSource.WarningRaised += new BuildWarningEventHandler(WarningHandler);
126  eventSource.MessageRaised += new BuildMessageEventHandler(MessageHandler);
127  }
128  #endregion
129 
130  #region event delegates
131  /// <summary>
132  /// This is the delegate for error events.
133  /// </summary>
134  private void ErrorHandler(object sender, BuildErrorEventArgs errorEvent)
135  {
136  AddToErrorList(
137  errorEvent,
138  errorEvent.Code,
139  errorEvent.File,
140  errorEvent.LineNumber,
141  errorEvent.ColumnNumber);
142  }
143 
144  /// <summary>
145  /// This is the delegate for warning events.
146  /// </summary>
147  private void WarningHandler(object sender, BuildWarningEventArgs errorEvent)
148  {
149  AddToErrorList(
150  errorEvent,
151  errorEvent.Code,
152  errorEvent.File,
153  errorEvent.LineNumber,
154  errorEvent.ColumnNumber);
155  }
156 
157  /// <summary>
158  /// Add the error/warning to the error list and potentially to the output window.
159  /// </summary>
160  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
161  private void AddToErrorList(
162  BuildEventArgs errorEvent,
163  string errorCode,
164  string file,
165  int line,
166  int column)
167  {
168  TaskPriority priority = (errorEvent is BuildErrorEventArgs) ? TaskPriority.High : TaskPriority.Normal;
169  if (OutputWindowPane != null
170  && (this.Verbosity != LoggerVerbosity.Quiet || errorEvent is BuildErrorEventArgs))
171  {
172  // Format error and output it to the output window
173  string message = FormatMessage(errorEvent.Message);
174  CompilerError e = new CompilerError(file,
175  line,
176  column,
177  errorCode,
178  message);
179  e.IsWarning = (errorEvent is BuildWarningEventArgs);
180 
181  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(OutputWindowPane.OutputStringThreadSafe(GetFormattedErrorMessage(e)));
182  }
183 
184  // Add error to task list
185  ErrorTask task = new ErrorTask();
186  task.Document = file;
187  task.Line = line - 1; // The task list does +1 before showing this number.
188  task.Column = column;
189  task.Text = errorEvent.Message;
190  task.Priority = priority;
191  task.Category = TaskCategory.BuildCompile;
192  task.HierarchyItem = hierarchy;
193  task.Navigate += new EventHandler(NavigateTo);
194  if (errorEvent is BuildWarningEventArgs)
195  task.ErrorCategory = TaskErrorCategory.Warning;
196  this.taskProvider.Tasks.Add(task);
197  }
198 
199 
200  /// <summary>
201  /// This is the delegate for Message event types
202  /// </summary>
203  private void MessageHandler(object sender, BuildMessageEventArgs messageEvent)
204  {
205  if (LogAtImportance(messageEvent.Importance))
206  {
207  LogEvent(sender, messageEvent);
208  }
209  }
210 
211  private void NavigateTo(object sender, EventArgs arguments)
212  {
213  Microsoft.VisualStudio.Shell.Task task = sender as Microsoft.VisualStudio.Shell.Task;
214  if (task == null)
215  throw new ArgumentException("Sender is not a Microsoft.VisualStudio.Shell.Task", "sender");
216 
217  // Get the doc data for the task's document
218  if (String.IsNullOrEmpty(task.Document))
219  return;
220 
221  IVsUIShellOpenDocument openDoc = serviceProvider.GetService(typeof(IVsUIShellOpenDocument)) as IVsUIShellOpenDocument;
222  if (openDoc == null)
223  return;
224 
225  IVsWindowFrame frame;
227  IVsUIHierarchy hier;
228  uint itemid;
229  Guid logicalView = VSConstants.LOGVIEWID_Code;
230 
231  if (Microsoft.VisualStudio.ErrorHandler.Failed(openDoc.OpenDocumentViaProject(task.Document, ref logicalView, out sp, out hier, out itemid, out frame)) || frame == null)
232  return;
233 
234  object docData;
235  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData));
236 
237  // Get the VsTextBuffer
238  VsTextBuffer buffer = docData as VsTextBuffer;
239  if (buffer == null)
240  {
241  IVsTextBufferProvider bufferProvider = docData as IVsTextBufferProvider;
242  if (bufferProvider != null)
243  {
244  IVsTextLines lines;
245  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer(out lines));
246  buffer = lines as VsTextBuffer;
247  Debug.Assert(buffer != null, "IVsTextLines does not implement IVsTextBuffer");
248  if (buffer == null)
249  return;
250  }
251  }
252 
253  // Finally, perform the navigation.
254  IVsTextManager mgr = serviceProvider.GetService(typeof(VsTextManagerClass)) as IVsTextManager;
255  if (mgr == null)
256  return;
257 
258  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(mgr.NavigateToLineAndColumn(buffer, ref logicalView, task.Line, task.Column, task.Line, task.Column));
259  }
260 
261  /// <summary>
262  /// This is the delegate for BuildStartedHandler events.
263  /// </summary>
264  private void BuildStartedHandler(object sender, BuildStartedEventArgs buildEvent)
265  {
266  if (LogAtImportance(MessageImportance.Low))
267  {
268  LogEvent(sender, buildEvent);
269  }
270  // Remove all errors and warnings since we are rebuilding
271  taskProvider.Tasks.Clear();
272  }
273 
274  /// <summary>
275  /// This is the delegate for BuildFinishedHandler events.
276  /// </summary>
277  /// <param name="sender"></param>
278  /// <param name="buildEvent"></param>
279  private void BuildFinishedHandler(object sender, BuildFinishedEventArgs buildEvent)
280  {
281  if (LogAtImportance(buildEvent.Succeeded ? MessageImportance.Low :
282  MessageImportance.High))
283  {
284  if (this.outputWindowPane != null)
285  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(this.outputWindowPane.OutputStringThreadSafe(Environment.NewLine));
286  LogEvent(sender, buildEvent);
287  }
288  }
289 
290 
291  /// <summary>
292  /// This is the delegate for ProjectStartedHandler events.
293  /// </summary>
294  private void ProjectStartedHandler(object sender, ProjectStartedEventArgs buildEvent)
295  {
296  if (LogAtImportance(MessageImportance.Low))
297  {
298  LogEvent(sender, buildEvent);
299  }
300  }
301 
302  /// <summary>
303  /// This is the delegate for ProjectFinishedHandler events.
304  /// </summary>
305  private void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs buildEvent)
306  {
307  if (LogAtImportance(buildEvent.Succeeded ? MessageImportance.Low
308  : MessageImportance.High))
309  {
310  LogEvent(sender, buildEvent);
311  }
312  }
313 
314  /// <summary>
315  /// This is the delegate for TargetStartedHandler events.
316  /// </summary>
317  private void TargetStartedHandler(object sender, TargetStartedEventArgs buildEvent)
318  {
319  if (LogAtImportance(MessageImportance.Normal))
320  {
321  LogEvent(sender, buildEvent);
322  }
323  ++this.currentIndent;
324  }
325 
326 
327  /// <summary>
328  /// This is the delegate for TargetFinishedHandler events.
329  /// </summary>
330  private void TargetFinishedHandler(object sender, TargetFinishedEventArgs buildEvent)
331  {
332  --this.currentIndent;
333  if ((IsLogTaskDone) &&
334  LogAtImportance(buildEvent.Succeeded ? MessageImportance.Low
335  : MessageImportance.High))
336  {
337  LogEvent(sender, buildEvent);
338  }
339  }
340 
341 
342  /// <summary>
343  /// This is the delegate for TaskStartedHandler events.
344  /// </summary>
345  private void TaskStartedHandler(object sender, TaskStartedEventArgs buildEvent)
346  {
347  if (LogAtImportance(MessageImportance.Normal))
348  {
349  LogEvent(sender, buildEvent);
350  }
351  ++this.currentIndent;
352  }
353 
354 
355  /// <summary>
356  /// This is the delegate for TaskFinishedHandler events.
357  /// </summary>
358  private void TaskFinishedHandler(object sender, TaskFinishedEventArgs buildEvent)
359  {
360  --this.currentIndent;
361  if ((IsLogTaskDone) &&
362  LogAtImportance(buildEvent.Succeeded ? MessageImportance.Normal
363  : MessageImportance.High))
364  {
365  LogEvent(sender, buildEvent);
366  }
367  }
368 
369 
370  /// <summary>
371  /// This is the delegate for CustomHandler events.
372  /// </summary>
373  /// <param name="sender"></param>
374  /// <param name="buildEvent"></param>
375  private void CustomHandler(object sender, CustomBuildEventArgs buildEvent)
376  {
377  LogEvent(sender, buildEvent);
378  }
379 
380  #endregion
381 
382  #region helpers
383  /// <summary>
384  /// This method takes a MessageImportance and returns true if messages
385  /// at importance i should be loggeed. Otherwise return false.
386  /// </summary>
387  private bool LogAtImportance(MessageImportance importance)
388  {
389  // If importance is too low for current settings, ignore the event
390  bool logIt = false;
391 
392  this.SetVerbosity();
393 
394  switch (this.Verbosity)
395  {
396  case LoggerVerbosity.Quiet:
397  logIt = false;
398  break;
399  case LoggerVerbosity.Minimal:
400  logIt = (importance == MessageImportance.High);
401  break;
402  case LoggerVerbosity.Normal:
403  // Falling through...
404  case LoggerVerbosity.Detailed:
405  logIt = (importance != MessageImportance.Low);
406  break;
407  case LoggerVerbosity.Diagnostic:
408  logIt = true;
409  break;
410  default:
411  Debug.Fail("Unknown Verbosity level. Ignoring will cause everything to be logged");
412  break;
413  }
414 
415  return logIt;
416  }
417 
418  /// <summary>
419  /// This is the method that does the main work of logging an event
420  /// when one is sent to this logger.
421  /// </summary>
422  private void LogEvent(object sender, BuildEventArgs buildEvent)
423  {
424  // Fill in the Message text
425  if (OutputWindowPane != null && !String.IsNullOrEmpty(buildEvent.Message))
426  {
427  StringBuilder msg = new StringBuilder(this.currentIndent + buildEvent.Message.Length + 1);
428  if (this.currentIndent > 0)
429  {
430  msg.Append('\t', this.currentIndent);
431  }
432  msg.AppendLine(buildEvent.Message);
433  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(this.OutputWindowPane.OutputStringThreadSafe(msg.ToString()));
434  }
435  }
436 
437  /// <summary>
438  /// Format error messages for the task list
439  /// </summary>
440  /// <param name="e"></param>
441  /// <returns></returns>
442  private string GetFormattedErrorMessage(CompilerError e)
443  {
444  if (e == null) return String.Empty;
445 
446  string errCode = (e.IsWarning) ? this.WarningString : this.ErrorString;
447  StringBuilder fileRef = new StringBuilder();
448 
449  if (!string.IsNullOrEmpty(e.FileName))
450  {
451  fileRef.AppendFormat(CultureInfo.CurrentUICulture, "{0}({1},{2}):",
452  e.FileName, e.Line, e.Column);
453  }
454  fileRef.AppendFormat(CultureInfo.CurrentUICulture, " {0} {1}: {2}", errCode, e.ErrorNumber, e.ErrorText);
455 
456  return fileRef.ToString();
457  }
458 
459  /// <summary>
460  /// Formats the message that is to be output.
461  /// </summary>
462  /// <param name="message">The message string.</param>
463  /// <returns>The new message</returns>
464  private static string FormatMessage(string message)
465  {
466  if (string.IsNullOrEmpty(message))
467  {
468  return Environment.NewLine;
469  }
470 
471  StringBuilder sb = new StringBuilder(message.Length + Environment.NewLine.Length);
472 
473  sb.AppendLine(message);
474  return sb.ToString();
475  }
476 
477  /// <summary>
478  /// Sets the verbosity level.
479  /// </summary>
480  private void SetVerbosity()
481  {
482  // TODO: This should be replaced when we have a version that supports automation.
483 
484  if (userRegistryRoot != null)
485  {
486  using (RegistryKey subKey = userRegistryRoot.OpenSubKey(buildVerbosityRegistrySubKey))
487  {
488  if (subKey != null)
489  {
490  object valueAsObject = subKey.GetValue(buildVerbosityRegistryKey);
491  if (valueAsObject != null)
492  {
493  this.Verbosity = (LoggerVerbosity)((int)valueAsObject);
494  }
495  }
496  }
497  }
498 
499  // TODO: Continue this code to get the Verbosity when we have a version that supports automation to get the Verbosity.
500  //EnvDTE.DTE dte = this.serviceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
501  //EnvDTE.Properties properties = dte.get_Properties(EnvironmentCategory, ProjectsAndSolutionSubCategory);
502  }
503  #endregion
504  }
505 
506 }
Microsoft.VisualStudio.OLE.Interop.IServiceProvider IOleServiceProvider
System.Threading.Tasks.Task Task
SiliconStudio.Core.Utilities Utilities
Definition: Texture.cs:29