336 lines
12 KiB
C#
336 lines
12 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.Specialized;
|
||
|
using System.Diagnostics;
|
||
|
using System.IO;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Security;
|
||
|
using Microsoft.Build.Framework;
|
||
|
|
||
|
namespace GodotSharpTools.Build
|
||
|
{
|
||
|
public class BuildInstance : IDisposable
|
||
|
{
|
||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||
|
internal extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||
|
internal extern static string godot_icall_BuildInstance_get_MSBuildPath();
|
||
|
|
||
|
private static string MSBuildPath
|
||
|
{
|
||
|
get { return godot_icall_BuildInstance_get_MSBuildPath(); }
|
||
|
}
|
||
|
|
||
|
private string solution;
|
||
|
private string config;
|
||
|
|
||
|
private Process process;
|
||
|
|
||
|
private int exitCode;
|
||
|
public int ExitCode { get { return exitCode; } }
|
||
|
|
||
|
public bool IsRunning { get { return process != null && !process.HasExited; } }
|
||
|
|
||
|
public BuildInstance(string solution, string config)
|
||
|
{
|
||
|
this.solution = solution;
|
||
|
this.config = config;
|
||
|
}
|
||
|
|
||
|
public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
|
||
|
{
|
||
|
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
|
||
|
|
||
|
ProcessStartInfo startInfo = new ProcessStartInfo(MSBuildPath, compilerArgs);
|
||
|
|
||
|
// No console output, thanks
|
||
|
startInfo.RedirectStandardOutput = true;
|
||
|
startInfo.RedirectStandardError = true;
|
||
|
startInfo.UseShellExecute = false;
|
||
|
|
||
|
// Needed when running from Developer Command Prompt for VS
|
||
|
RemovePlatformVariable(startInfo.EnvironmentVariables);
|
||
|
|
||
|
using (Process process = new Process())
|
||
|
{
|
||
|
process.StartInfo = startInfo;
|
||
|
|
||
|
process.Start();
|
||
|
|
||
|
process.BeginOutputReadLine();
|
||
|
process.BeginErrorReadLine();
|
||
|
|
||
|
process.WaitForExit();
|
||
|
|
||
|
exitCode = process.ExitCode;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
|
||
|
{
|
||
|
if (process != null)
|
||
|
throw new InvalidOperationException("Already in use");
|
||
|
|
||
|
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
|
||
|
|
||
|
ProcessStartInfo startInfo = new ProcessStartInfo("msbuild", compilerArgs);
|
||
|
|
||
|
// No console output, thanks
|
||
|
startInfo.RedirectStandardOutput = true;
|
||
|
startInfo.RedirectStandardError = true;
|
||
|
startInfo.UseShellExecute = false;
|
||
|
|
||
|
// Needed when running from Developer Command Prompt for VS
|
||
|
RemovePlatformVariable(startInfo.EnvironmentVariables);
|
||
|
|
||
|
process = new Process();
|
||
|
process.StartInfo = startInfo;
|
||
|
process.EnableRaisingEvents = true;
|
||
|
process.Exited += new EventHandler(BuildProcess_Exited);
|
||
|
|
||
|
process.Start();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties)
|
||
|
{
|
||
|
string arguments = string.Format("{0} /v:normal /t:Build /p:{1} /l:{2},{3};{4}",
|
||
|
solution,
|
||
|
"Configuration=" + config,
|
||
|
typeof(GodotBuildLogger).FullName,
|
||
|
loggerAssemblyPath,
|
||
|
loggerOutputDir
|
||
|
);
|
||
|
|
||
|
if (customProperties != null)
|
||
|
{
|
||
|
foreach (string customProperty in customProperties)
|
||
|
{
|
||
|
arguments += " /p:" + customProperty;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return arguments;
|
||
|
}
|
||
|
|
||
|
private void RemovePlatformVariable(StringDictionary environmentVariables)
|
||
|
{
|
||
|
// EnvironmentVariables is case sensitive? Seriously?
|
||
|
|
||
|
List<string> platformEnvironmentVariables = new List<string>();
|
||
|
|
||
|
foreach (string env in environmentVariables.Keys)
|
||
|
{
|
||
|
if (env.ToUpper() == "PLATFORM")
|
||
|
platformEnvironmentVariables.Add(env);
|
||
|
}
|
||
|
|
||
|
foreach (string env in platformEnvironmentVariables)
|
||
|
environmentVariables.Remove(env);
|
||
|
}
|
||
|
|
||
|
private void BuildProcess_Exited(object sender, System.EventArgs e)
|
||
|
{
|
||
|
exitCode = process.ExitCode;
|
||
|
|
||
|
godot_icall_BuildInstance_ExitCallback(solution, config, exitCode);
|
||
|
|
||
|
Dispose();
|
||
|
}
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
if (process != null)
|
||
|
{
|
||
|
process.Dispose();
|
||
|
process = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class GodotBuildLogger : ILogger
|
||
|
{
|
||
|
public string Parameters { get; set; }
|
||
|
public LoggerVerbosity Verbosity { get; set; }
|
||
|
|
||
|
public void Initialize(IEventSource eventSource)
|
||
|
{
|
||
|
if (null == Parameters)
|
||
|
throw new LoggerException("Log directory was not set.");
|
||
|
|
||
|
string[] parameters = Parameters.Split(';');
|
||
|
|
||
|
string logDir = parameters[0];
|
||
|
|
||
|
if (String.IsNullOrEmpty(logDir))
|
||
|
throw new LoggerException("Log directory was not set.");
|
||
|
|
||
|
if (parameters.Length > 1)
|
||
|
throw new LoggerException("Too many parameters passed.");
|
||
|
|
||
|
string logFile = Path.Combine(logDir, "msbuild_log.txt");
|
||
|
string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!Directory.Exists(logDir))
|
||
|
Directory.CreateDirectory(logDir);
|
||
|
|
||
|
this.logStreamWriter = new StreamWriter(logFile);
|
||
|
this.issuesStreamWriter = new StreamWriter(issuesFile);
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
if
|
||
|
(
|
||
|
ex is UnauthorizedAccessException
|
||
|
|| ex is ArgumentNullException
|
||
|
|| ex is PathTooLongException
|
||
|
|| ex is DirectoryNotFoundException
|
||
|
|| ex is NotSupportedException
|
||
|
|| ex is ArgumentException
|
||
|
|| ex is SecurityException
|
||
|
|| ex is IOException
|
||
|
)
|
||
|
{
|
||
|
throw new LoggerException("Failed to create log file: " + ex.Message);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Unexpected failure
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
|
||
|
eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
|
||
|
eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
|
||
|
eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
|
||
|
eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
|
||
|
eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
|
||
|
}
|
||
|
|
||
|
void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
|
||
|
{
|
||
|
string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message);
|
||
|
|
||
|
if (e.ProjectFile.Length > 0)
|
||
|
line += string.Format(" [{0}]", e.ProjectFile);
|
||
|
|
||
|
WriteLine(line);
|
||
|
|
||
|
string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}",
|
||
|
e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
|
||
|
e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape());
|
||
|
issuesStreamWriter.WriteLine(errorLine);
|
||
|
}
|
||
|
|
||
|
void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
|
||
|
{
|
||
|
string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile);
|
||
|
|
||
|
if (e.ProjectFile != null && e.ProjectFile.Length > 0)
|
||
|
line += string.Format(" [{0}]", e.ProjectFile);
|
||
|
|
||
|
WriteLine(line);
|
||
|
|
||
|
string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}",
|
||
|
e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
|
||
|
e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty);
|
||
|
issuesStreamWriter.WriteLine(warningLine);
|
||
|
}
|
||
|
|
||
|
void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
|
||
|
{
|
||
|
// BuildMessageEventArgs adds Importance to BuildEventArgs
|
||
|
// Let's take account of the verbosity setting we've been passed in deciding whether to log the message
|
||
|
if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
|
||
|
|| (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
|
||
|
|| (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
|
||
|
)
|
||
|
{
|
||
|
WriteLineWithSenderAndMessage(String.Empty, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
|
||
|
{
|
||
|
// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
|
||
|
// To keep this log clean, this logger will ignore these events.
|
||
|
}
|
||
|
|
||
|
void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
|
||
|
{
|
||
|
WriteLine(e.Message);
|
||
|
indent++;
|
||
|
}
|
||
|
|
||
|
void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
|
||
|
{
|
||
|
indent--;
|
||
|
WriteLine(e.Message);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Write a line to the log, adding the SenderName
|
||
|
/// </summary>
|
||
|
private void WriteLineWithSender(string line, BuildEventArgs e)
|
||
|
{
|
||
|
if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
|
||
|
{
|
||
|
// Well, if the sender name is MSBuild, let's leave it out for prettiness
|
||
|
WriteLine(line);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteLine(e.SenderName + ": " + line);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Write a line to the log, adding the SenderName and Message
|
||
|
/// (these parameters are on all MSBuild event argument objects)
|
||
|
/// </summary>
|
||
|
private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
|
||
|
{
|
||
|
if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
|
||
|
{
|
||
|
// Well, if the sender name is MSBuild, let's leave it out for prettiness
|
||
|
WriteLine(line + e.Message);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteLine(e.SenderName + ": " + line + e.Message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void WriteLine(string line)
|
||
|
{
|
||
|
for (int i = indent; i > 0; i--)
|
||
|
{
|
||
|
logStreamWriter.Write("\t");
|
||
|
}
|
||
|
logStreamWriter.WriteLine(line);
|
||
|
}
|
||
|
|
||
|
public void Shutdown()
|
||
|
{
|
||
|
logStreamWriter.Close();
|
||
|
issuesStreamWriter.Close();
|
||
|
}
|
||
|
|
||
|
public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
|
||
|
{
|
||
|
return this.Verbosity >= checkVerbosity;
|
||
|
}
|
||
|
|
||
|
private StreamWriter logStreamWriter;
|
||
|
private StreamWriter issuesStreamWriter;
|
||
|
private int indent;
|
||
|
}
|
||
|
}
|