GodotBuildLogger.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. using System;
  2. using System.IO;
  3. using System.Security;
  4. using Microsoft.Build.Framework;
  5. namespace GodotTools.BuildLogger
  6. {
  7. public class GodotBuildLogger : ILogger
  8. {
  9. public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location);
  10. public string Parameters { get; set; }
  11. public LoggerVerbosity Verbosity { get; set; }
  12. public void Initialize(IEventSource eventSource)
  13. {
  14. if (null == Parameters)
  15. throw new LoggerException("Log directory was not set.");
  16. var parameters = Parameters.Split(new[] { ';' });
  17. string logDir = parameters[0];
  18. if (string.IsNullOrEmpty(logDir))
  19. throw new LoggerException("Log directory was not set.");
  20. if (parameters.Length > 1)
  21. throw new LoggerException("Too many parameters passed.");
  22. string logFile = Path.Combine(logDir, "msbuild_log.txt");
  23. string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
  24. try
  25. {
  26. if (!Directory.Exists(logDir))
  27. Directory.CreateDirectory(logDir);
  28. logStreamWriter = new StreamWriter(logFile);
  29. issuesStreamWriter = new StreamWriter(issuesFile);
  30. }
  31. catch (Exception ex)
  32. {
  33. if (ex is UnauthorizedAccessException
  34. || ex is ArgumentNullException
  35. || ex is PathTooLongException
  36. || ex is DirectoryNotFoundException
  37. || ex is NotSupportedException
  38. || ex is ArgumentException
  39. || ex is SecurityException
  40. || ex is IOException)
  41. {
  42. throw new LoggerException("Failed to create log file: " + ex.Message);
  43. }
  44. else
  45. {
  46. // Unexpected failure
  47. throw;
  48. }
  49. }
  50. eventSource.ProjectStarted += eventSource_ProjectStarted;
  51. eventSource.TaskStarted += eventSource_TaskStarted;
  52. eventSource.MessageRaised += eventSource_MessageRaised;
  53. eventSource.WarningRaised += eventSource_WarningRaised;
  54. eventSource.ErrorRaised += eventSource_ErrorRaised;
  55. eventSource.ProjectFinished += eventSource_ProjectFinished;
  56. }
  57. void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
  58. {
  59. string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
  60. if (e.ProjectFile.Length > 0)
  61. line += $" [{e.ProjectFile}]";
  62. WriteLine(line);
  63. string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
  64. $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}";
  65. issuesStreamWriter.WriteLine(errorLine);
  66. }
  67. void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
  68. {
  69. string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
  70. if (!string.IsNullOrEmpty(e.ProjectFile))
  71. line += $" [{e.ProjectFile}]";
  72. WriteLine(line);
  73. string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
  74. $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
  75. issuesStreamWriter.WriteLine(warningLine);
  76. }
  77. private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
  78. {
  79. // BuildMessageEventArgs adds Importance to BuildEventArgs
  80. // Let's take account of the verbosity setting we've been passed in deciding whether to log the message
  81. if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)
  82. || e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)
  83. || e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
  84. {
  85. WriteLineWithSenderAndMessage(string.Empty, e);
  86. }
  87. }
  88. private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
  89. {
  90. // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
  91. // To keep this log clean, this logger will ignore these events.
  92. }
  93. private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
  94. {
  95. WriteLine(e.Message);
  96. indent++;
  97. }
  98. private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
  99. {
  100. indent--;
  101. WriteLine(e.Message);
  102. }
  103. /// <summary>
  104. /// Write a line to the log, adding the SenderName
  105. /// </summary>
  106. private void WriteLineWithSender(string line, BuildEventArgs e)
  107. {
  108. if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
  109. {
  110. // Well, if the sender name is MSBuild, let's leave it out for prettiness
  111. WriteLine(line);
  112. }
  113. else
  114. {
  115. WriteLine(e.SenderName + ": " + line);
  116. }
  117. }
  118. /// <summary>
  119. /// Write a line to the log, adding the SenderName and Message
  120. /// (these parameters are on all MSBuild event argument objects)
  121. /// </summary>
  122. private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
  123. {
  124. if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
  125. {
  126. // Well, if the sender name is MSBuild, let's leave it out for prettiness
  127. WriteLine(line + e.Message);
  128. }
  129. else
  130. {
  131. WriteLine(e.SenderName + ": " + line + e.Message);
  132. }
  133. }
  134. private void WriteLine(string line)
  135. {
  136. for (int i = indent; i > 0; i--)
  137. {
  138. logStreamWriter.Write("\t");
  139. }
  140. logStreamWriter.WriteLine(line);
  141. }
  142. public void Shutdown()
  143. {
  144. logStreamWriter.Close();
  145. issuesStreamWriter.Close();
  146. }
  147. private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
  148. {
  149. return Verbosity >= checkVerbosity;
  150. }
  151. private StreamWriter logStreamWriter;
  152. private StreamWriter issuesStreamWriter;
  153. private int indent;
  154. }
  155. internal static class StringExtensions
  156. {
  157. public static string CsvEscape(this string value, char delimiter = ',')
  158. {
  159. bool hasSpecialChar = value.IndexOfAny(new[] { '\"', '\n', '\r', delimiter }) != -1;
  160. if (hasSpecialChar)
  161. return "\"" + value.Replace("\"", "\"\"") + "\"";
  162. return value;
  163. }
  164. }
  165. }