GodotBuildLogger.cs 6.3 KB

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